fix(schema): make inherited type composition scope- and topology-exact [roadmap:deterministic-substrate] - #495
Merged
Conversation
…t [roadmap:deterministic-substrate] Implements decisions/roadmaps/deterministic-substrate.md (Tranche C). - A type override's rationale resolves in the declaring corpus: a root-owned rationale outside the command's directory or --top-level scope is looked up over the root's working tree, materialised parents excluded, instead of failing as unresolved. - Rationale identifiers casefold, as ADR-137 artifact overrides do. - Element identity compares map-like fields as maps, so JSON key order no longer manufactures corpus-federation-artifact-type-conflict; list order stays significant. - The composed-registry memo key frames the root, each source's layer, and its canonical parent list, so a changed graph under byte-identical configs recomposes and raises what a fresh process would. - decided new / migrate in a version-2 repository union the composed closure with a plain repository walk, so identifiers in sibling top-level directories and replaced parent artifacts stay issued. - artifact_spec_bundles reports a null source for a local bundle whose config declares no corpus.source, not the `prefer: local` keyword.
… [roadmap:deterministic-substrate] Implements decisions/roadmaps/deterministic-substrate.md (Tranche C). Covers version-1 and version-2 rationale resolution outside the command scope with a lowercased identifier, a parent Decision refused as a local rationale, decided new in a sibling top-level directory, and the null source label for an unnamed local bundle. Unit tests cover key-order identity, topology-aware memoisation, and diamond re-collision.
…lision [roadmap:deterministic-substrate] Implements decisions/roadmaps/deterministic-substrate.md (Tranche C). States that type-override rationales resolve case-insensitively across the declaring corpus, that element identity ignores JSON key order, that a diamond re-opens a collision a middle node resolved, that the memo key covers topology, and that an unnamed local bundle reports a null source.
…e [roadmap:deterministic-substrate] Implements decisions/roadmaps/deterministic-substrate.md (Tranche C). decided new in a version-2 repository counts every identifier in the repository as issued, not only the composed closure of the target's top-level directory.
tcballard
force-pushed
the
claude/federated-spec-composition
branch
from
September 22, 2026 21:43
c593472 to
f24fd3f
Compare
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
This is PR E in the post-merge review series. It fixes the ADR-150 composition-correctness findings:
decided validate decisions/runbooks,stats/doctoron a subdirectory,validate --top-level, and v1 subdirectory scopes all failed withcorpus-federation-invalid-override … does not resolve to a local artifact. Now a root-owned rationale that is not in scope is resolved over a walk of the root's working tree, with every materialised parent excluded. Inherited sources are already captured whole, so they are unchanged.(source, config bytes). Parent edges live in the manifests, so a long-running process that saw the graph change under byte-identical configs kept serving the old registry and skipped findings a fresh process raises. The key now frames the root, plus each source's layer and canonical parent list.ArtifactSpecequality compared order-preservingVecs, contradicting the ADR-150 amendment ("key order and whitespace cannot manufacture a conflict"). Element identity now compares map-like fields (metadata,descriptions,guidance,synonyms,starter_bodies) as maps. List order stays significant because it is rendered order.composition.rsvalidate_override).decided new/migratecollision set in v2 was narrower than released (low). Since fix(new): compose the collision scope of a version-2 federation from its corpus directory [roadmap:deterministic-substrate] #489 a v2 repository counted only the composed closure of one top-level directory. It now also unions a plain repository walk, so identifiers in sibling top-level directories and in replaced parent artifacts stay issued.corpus.sourcewas reported inartifact_spec_bundlesas"source": "local", the same literal as theprefer: localkeyword. It is nownull. The field is still Unreleased, so this is not a contract break.A diamond that re-opens a collision is documented, not changed (root → A → G and root → B → G, where A resolves G's
runbook). A's resolution governs A's view only. The root reaches G's declaration again through B and must record its own override, which may prefer any source in its inherited view. This matches ADR-147's explicit diamond convergence for artifact overrides: carrying A's choice past B's path would let one parent silently decide for a sibling that never opted in. A unit test locks it in.Roadmap / ADR Trace
decisions/roadmaps/deterministic-substrate.md(Tranche C)artifact_spec_bundleschange lands before releaseScope
rust/rac-engine/src/spec_composition.rs:composition_key,root_tree_items,same_content, and casefolded rationale matching with a lazy root-tree fallback inverify_override_rationales; unit tests.rust/rac-engine/src/federated_corpus.rs,graph_federated_corpus.rs: both call sites pass the root source and the root-tree walk.rust/rac-engine/src/scaffold.rs: the v2 collision set unions a plain repository walk.rust/rac-engine/src/spec.rs,output.rs,doctor.rs:SourceSpecBundle.sourceisOption<String>.rust/decided/tests/spec_federation.rs,spec_bundle.rs: CLI coverage.docs/validation.md,docs/cli.md(collision scan),decisions/designs/third-party-artifact-extensibility.md,CHANGELOG.md.Product / Architecture Decisions
User-Facing Contract
--top-level, no longer fail a valid type override.artifact_spec_bundles[].sourceisnullfor an unnamed local bundle.mainon the same tree.Verification
cargo fmt --check,cargo clippy --workspace --release --no-deps -- -D warnings(the CI invocation),cargo test --workspace --release: pass.key_order_alone_is_not_a_conflict_but_list_order_isfails under plain==.a_topology_change_under_identical_configs_recomposesfails under the old config-only key.a_type_override_rationale_resolves_across_the_whole_local_corpusfails without the fallback and without casefolding.a_version_one_type_override_rationale_resolves_outside_the_command_scopefails without the fallback.validate,relationships --validate,review(no P1/P2),export --agent-rules --check,watchkeeper --base origin/main: all exit 0.Review Path
spec_composition.rs:verify_override_rationalesandroot_tree_items, thencomposition_key, thensame_content.federated_corpus.rs/graph_federated_corpus.rs.scaffold.rsissued_ids.Notes For Reviewer
decided-mcpcache path the root-tree fallback reads the live working tree, which the generation key does not cover. A rationale Decision outside the served root that is retired while the server runs is noticed only on the next recomposition. The served root normally holds the rationale (in scope, fully keyed), so the fallback is a CLI-scope concern in practice.&'staticand are leaked once per distinct composition key. That is bounded by the distinct configurations and topologies a process sees, at a few KB each. Making them freeable is anArcrefactor across every&'static ArtifactSpecholder, out of scope here.validate decisions/policieson thespec-federationfixture still fails withcorpus-federation-invalid-node, becausedata-retention.mdlinks to an ADR outside that scope. That is a pre-existing v2 relationship-scope property unrelated to overrides, so the new test scopes to a relationship-freedecisions/runbooks.by_typeorder, synonyms in validation, doctor orphan advice) are not in this PR. The corpus/doc corrections are docs(schema): reconcile ADR-150, ADR-083, the design, and docs with the engine [roadmap:deterministic-substrate] #496.Implementation Process
Implemented with AI assistance under the roadmap contract; final scope, review, and acceptance decisions were made by the maintainer.