feat(schema): derive JSON output schema from Rust source of truth#374
Closed
BartWaardenburg wants to merge 3 commits into
Closed
feat(schema): derive JSON output schema from Rust source of truth#374BartWaardenburg wants to merge 3 commits into
BartWaardenburg wants to merge 3 commits into
Conversation
Add `#[derive(JsonSchema)]` to the per-finding and duplication result structs behind a new `schema` cargo feature on fallow-types and fallow-core, plus a `fallow-schema-emit` dev binary on fallow-cli (gated by a `schema-emit` feature) that regenerates `docs/output-schema.json#/definitions` from the Rust source of truth. New module `crates/types/src/output.rs` types the JSON-layer augmentations (`IssueAction`, `FixAction`, `SuppressLineAction`, `SuppressFileAction`, `AddToConfigAction`, and their kind discriminant + payload subtypes) so a future PR can route `crates/cli/src/report/json.rs` through them instead of `serde_json::json!` builders. Five drift tests catch field renames, additions, removals, and required-flag drift between the Rust source and the committed schema; a sixth walks every `$ref` in the merged document and asserts no dangling targets. A strict structural variant (descriptions, integer formats, nullable union shape) ships `#[ignore]`d pending prose migration. CI runs the drift tests and `--tests` clippy on the schema-emit binary so the gate is enforced on every push. Also fixes two latent drift issues the new gate surfaced: - `UnresolvedImport.specifier_col` was on the Rust struct since v2.39 but missing from the public schema, so AJV-strict consumers rejected every unresolved-import finding. - `MisconfiguredDependencyOverride.target_package` was emitted whenever the override key was syntactically valid but missing from the schema. `CONTRIBUTING.md` documents the new derive-then-emit flow. The VS Code extension's generated TS types (`editors/vscode/src/generated/output-contract.d.ts` and `npm/fallow/types/output-contract.d.ts`) regenerate cleanly against the updated schema, and one VS Code test fixture gains the new required `specifier_col` field. Phase 4 (health subtree), Phase 5 (typed envelope structs), and Phase 8 (prose migration into Rust doc comments) are intentionally deferred to follow-up PRs. Refs #338
Extends the schema-emit drift gate to cover the fallow-cli health output subtree (Phase 4 of #338). The health types live on fallow-cli, so this adds a sibling `schema` cargo feature there alongside the existing `fallow-types/schema` + `fallow-core/schema`. `JsonSchema` derives now attach to HealthFinding, HealthSummary, HealthScore + HealthScorePenalties, VitalSigns + VitalSignsCounts + RiskProfile, HotspotEntry + HotspotSummary + OwnershipMetrics + ContributorEntry, RefactoringTarget + TargetThresholds, HealthTrend + TrendCount, FileHealthScore, LargeFunctionEntry, CoverageGaps + CoverageGapSummary + UntestedFile + UntestedExport, RuntimeCoverageReport and the protocol-derived signal/verdict/watermark/confidence/risk-band enums, plus ChurnTrend (transitively). Per-finding action wrappers (HealthFindingAction, HotspotAction, RefactoringTargetAction) ship as schema-only types in crates/types/src/output_health.rs so the drift gate covers the action arrays without forcing a json.rs refactor in the same PR. The schema-emit binary's augment_finding_definition takes a per-finding FindingAugmentation now (actions_item_ref + include_introduced), so HotspotEntry and RefactoringTarget correctly skip the audit-only `introduced` flag while HealthFinding keeps it. Reconciles three docs/output-schema.json fields that the JSON layer was already emitting (HotspotEntry.is_test_path, OwnershipMetrics.suggested_reviewers, four VitalSignsCounts fields) and tightens RuntimeCoverageReport.required to include blast_radius + importance to match the wire. Drops `unowned` from OwnershipMetrics.required to match the Option<bool> source. Strips an unreachable `placement` field from RefactoringTargetAction (the JSON layer never emits placement for refactoring targets; consumers that want placement metadata should follow target.evidence back to the matching HealthFinding action). Also adds an augment_runtime_coverage_report helper that grafts schema_version onto the derived RuntimeCoverageReport schema, mirroring what inject_runtime_coverage_report_schema_version adds to the wire. Refs #338
Two reviewer BLOCKs against the Phase 4 health-subtree derive: (1) coverage_gaps.files[].actions and coverage_gaps.exports[].actions were emitted by inject_health_actions but undocumented in the schema and generated TS bindings; (2) RuntimeCoverageReport's helper structs and enums were in the committed schema and inserted via the dangling-ref fallback, but were not part of derived_definition_names, so a future Rust field change in those helpers would not fail the drift gate. Add typed UntestedFileAction + UntestedExportAction wrappers (and their discriminant enums) to crates/types/src/output_health.rs, mirroring the shape inject_health_actions emits today (add-tests / suppress-file for files, add-test-import / suppress-file for exports). Add UntestedFile and UntestedExport to finding_definition_names so augment_finding_definition grafts the typed actions[] array onto each, without the audit-only introduced flag. Extend derived_definition_names with the runtime-coverage helper subtree: RuntimeCoverageAction, RuntimeCoverageBlastRadiusEntry, RuntimeCoverageCaptureQuality, RuntimeCoverageConfidence, RuntimeCoverageEvidence, RuntimeCoverageFinding, RuntimeCoverageHotPath, RuntimeCoverageImportanceEntry, RuntimeCoverageMessage, RuntimeCoverageReportVerdict, RuntimeCoverageRiskBand, RuntimeCoverageSignal, RuntimeCoverageSummary, RuntimeCoverageVerdict, RuntimeCoverageWatermark. Drift gate now fires on field rename / addition across the full runtime-coverage subtree, not just the top-level report. Two follow-on drift fires resolved: RuntimeCoverageFinding.actions and RuntimeCoverageHotPath.actions gain schemars(default) to match their serde skip_serializing_if = "Vec::is_empty" wire shape, and RuntimeCoverageSummary.last_received_at drops from required in the committed schema to match its Option<String> Rust source (same precedent as OwnershipMetrics.unowned). Phase 6 wire-vs-schema verification on benchmarks/fixtures/real-world/zod (13 untested files, 11 untested exports): every emitted actions[] entry matches UntestedFileAction / UntestedExportAction. Drift gate green: 5/5 active tests pass. Refs #338
76a9a32 to
8c79092
Compare
4 tasks
Collaborator
Author
|
Superseded by #382, which contains this PR's 3 foundation commits ( The new PR rebases cleanly on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1+2+3+6+7+9 of #338: the per-finding and duplication result structs now derive
JsonSchema(behind a newschemacargo feature onfallow-types+fallow-core), and a newfallow-schema-emitdev binary regeneratesdocs/output-schema.json#/definitionsfrom the Rust source of truth.cargo test -p fallow-cli --features schema-emit --bin fallow-schema-emitcatches Rust → schema drift: property renames, additions, removals, required-flag changes, and dangling$reftargets in the merged document. CI runs the gate + a--testsclippy on the schema-emit binary so the contract is enforced on every push.IssueActionenum hierarchy in new modulecrates/types/src/output.rs(IssueAction,FixAction,FixActionType,SuppressLineAction,SuppressFileAction,AddToConfigAction, plus the singleton kind discriminants). Schema-only today; a follow-up will routecrates/cli/src/report/json.rsthrough them instead ofserde_json::json!builders.UnresolvedImport.specifier_colwas on the Rust struct since v2.39 but missing from the public schema, so AJV-strict consumers rejected every unresolved-import finding.MisconfiguredDependencyOverride.target_packagewas emitted onempty-valuefindings but had no schema declaration.editors/vscode/src/generated/output-contract.d.ts+npm/fallow/types/output-contract.d.ts); one test fixture gained the new requiredspecifier_colfield.CONTRIBUTING.mddocuments the new derive-then-emit flow;.claude/rules/vscode-extension.mdupdated with the Rust → schema → TS chain.Phase 4 (health subtree), Phase 5 (typed envelope structs replacing the
serde_json::json!builders), and Phase 8 (prose migration into Rust doc comments) are intentionally deferred to follow-up PRs and listed under "Layer 2: hand-written sections" in CONTRIBUTING.md.Test plan
cargo check --workspacecleancargo clippy --workspace --all-targets -- -D warningscleancargo clippy -p fallow-cli --features schema-emit --bin fallow-schema-emit --tests -- -D warningscleancargo clippy -p fallow-types --features schema -- -D warningsandcargo clippy -p fallow-core --features schema -- -D warningscleancargo test --workspace --lib5959 tests pass, 0 failcargo test -p fallow-clifull integration suite greencargo test -p fallow-cli --features schema-emit --bin fallow-schema-emit5 active drift tests pass, 1 strict variant ignoredcargo fmt --all -- --checkclean,typos .clean,cargo doc --workspace --no-deps --document-private-itemsno warningscargo run -p fallow-cli --features schema-emit --bin fallow-schema-emitproduces a self-consistent schema (128 definitions, 119 refs, 0 dangling)cd editors/vscode && pnpm run check:codegenclean;pnpm run lintclean;pnpm run test6/6 pass;pnpm run buildrebuilds the committed bundle without furtherdist/diffpython3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"parses the new CI stepsRefs #338