Problem
docs/output-schema.json lists actions as a required field on every CloneGroup and CloneFamily (augmented via schema_emit.rs::augment_finding_definition), but the runtime emit paths for fallow dupes --format json and fallow --format json (bare combined) do NOT inject actions: [] on these findings. JSON Schema consumers that strict-validate output against the published schema get 234 validation errors on a standalone fallow dupes --format json of vite (one per clone_families[].groups[]) and 608 errors on the combined output (one per clone_groups[] plus the families).
Repro
cargo build --release --bin fallow
FALLOW_QUIET=1 ./target/release/fallow dupes --format json --root benchmarks/fixtures/real-world/vite \
| python3 -c "
import json, sys
from jsonschema import Draft7Validator
schema = json.load(open('docs/output-schema.json'))
inst = json.load(sys.stdin)
errs = list(Draft7Validator(schema).iter_errors(inst))
print('errors:', len(errs))
for e in errs[:3]: print(' -', list(e.absolute_path), e.validator, e.message[:80])
"
Output:
errors: 234
- ['clone_families', 0, 'groups', 0] required 'actions' is a required property
- ['clone_families', 1, 'groups', 0] required 'actions' is a required property
- ['clone_families', 2, 'groups', 0] required 'actions' is a required property
Background
The schema-derive pass (#338) introduced augment_finding_definition in crates/cli/src/bin/schema_emit.rs to unconditionally push actions into each finding type's required array, with the rationale (from the schema-emit doc comment) "The runtime always emits actions: [...] (possibly empty) on every finding, so requiring the field on the wire is honest." This contract holds for dead-code findings (UnusedFile, UnusedExport, etc.) where crates/cli/src/report/json.rs::build_actions injects actions on every entry, but NOT for CloneGroup/CloneFamily where inject_dupes_actions only runs under certain code paths (e.g., not in the bare combined path).
Fix options
- Wire side (recommended, matches the augmentation contract): make the dupes JSON build paths (
build_duplication_json, the combined.dupes sub-builder, the audit duplication block) always inject actions: [] on every CloneGroup and CloneFamily so the wire matches the schema.
- Schema side: drop
actions from CloneGroup/CloneFamily required in finding_augmentation so the schema documents the field as optional. This contradicts the implement-skill rule about "always emit" but is correct if the runtime contract is genuinely conditional.
Discovered during /fallow-review of #391 (Refs #384 item 6). Pre-existing on origin/main; not introduced by #391.
Problem
docs/output-schema.jsonlistsactionsas a required field on everyCloneGroupandCloneFamily(augmented viaschema_emit.rs::augment_finding_definition), but the runtime emit paths forfallow dupes --format jsonandfallow --format json(bare combined) do NOT injectactions: []on these findings. JSON Schema consumers that strict-validate output against the published schema get 234 validation errors on a standalonefallow dupes --format jsonofvite(one perclone_families[].groups[]) and 608 errors on the combined output (one perclone_groups[]plus the families).Repro
Output:
Background
The schema-derive pass (#338) introduced
augment_finding_definitionincrates/cli/src/bin/schema_emit.rsto unconditionally pushactionsinto each finding type'srequiredarray, with the rationale (from the schema-emit doc comment) "The runtime always emitsactions: [...](possibly empty) on every finding, so requiring the field on the wire is honest." This contract holds for dead-code findings (UnusedFile,UnusedExport, etc.) wherecrates/cli/src/report/json.rs::build_actionsinjectsactionson every entry, but NOT forCloneGroup/CloneFamilywhereinject_dupes_actionsonly runs under certain code paths (e.g., not in the bare combined path).Fix options
build_duplication_json, the combined.dupes sub-builder, the audit duplication block) always injectactions: []on everyCloneGroupandCloneFamilyso the wire matches the schema.actionsfromCloneGroup/CloneFamilyrequiredinfinding_augmentationso the schema documents the field as optional. This contradicts the implement-skill rule about "always emit" but is correct if the runtime contract is genuinely conditional.Discovered during /fallow-review of #391 (Refs #384 item 6). Pre-existing on
origin/main; not introduced by #391.