From 14dfaad3d9a3df0be885ecb6d6865effaffc53f2 Mon Sep 17 00:00:00 2001 From: Tom Ballard Date: Sun, 30 Aug 2026 09:53:13 +0100 Subject: [PATCH] feat(federation): ship v0.29.0 federation Signed-off-by: Tom Ballard --- .cursor/rules | 12 +- .github/copilot-instructions.md | 12 +- AGENTS.md | 12 +- CHANGELOG.md | 54 +- CLAUDE.md | 12 +- README.md | 16 + ...single-source-of-truth-git-not-database.md | 9 +- .../decisions/adr-089-corpus-federation.md | 9 +- ...adr-133-single-direct-parent-federation.md | 8 +- ...4-offline-pinned-parent-materialisation.md | 9 +- .../adr-135-corpus-source-global-identity.md | 11 +- ...r-136-qualified-cross-corpus-resolution.md | 8 +- ...37-decision-backed-federation-overrides.md | 9 +- ...ified-source-aware-federated-read-model.md | 11 +- ...-139-source-neutral-federated-retrieval.md | 2 +- ...r-141-bounded-mcp-federation-provenance.md | 9 +- .../adr-142-inherited-export-composition.md | 6 + ...adr-143-versioned-federated-generations.md | 9 +- ...bounded-acyclic-corpus-federation-graph.md | 184 ++ ...ned-multiple-parent-federation-manifest.md | 209 ++ ...l-source-qualified-federated-resolution.md | 171 + ...cision-backed-federated-override-chains.md | 189 ++ ...sure-wide-federated-serving-generations.md | 253 ++ ...-git-repository-truth-is-forge-agnostic.md | 125 + .../corpus-federation-graph-composition.md | 590 ++++ .../designs/corpus-federation-mechanism.md | 8 +- .../requirements/corpus-source-identity.md | 29 +- .../federated-resolution-provenance.md | 176 +- .../requirements/parent-corpus-inheritance.md | 152 +- decisions/roadmaps/corpus-federation.md | 421 ++- decisions/roadmaps/corpus-sync.md | 7 +- docs/cli.md | 141 +- docs/examples.md | 6 + docs/export-contracts.md | 55 +- docs/federation.md | 150 + docs/quickstart.md | 8 +- docs/shared-server.md | 12 +- examples/federation/README.md | 36 + examples/federation/app/.decided/config.yaml | 3 + examples/federation/app/.decided/corpus.md | 28 + .../app/decisions/application-override.md | 26 + .../app/decisions/application-policy.md | 26 + .../app/vendor/platform/.decided/config.yaml | 3 + .../app/vendor/platform/.decided/corpus.md | 13 + .../platform/decisions/platform-policy.md | 26 + .../vendor/shared/.decided/config.yaml | 3 + .../vendor/shared/decisions/shared-policy.md | 26 + .../app/vendor/security/.decided/config.yaml | 3 + .../app/vendor/security/.decided/corpus.md | 13 + .../security/decisions/security-policy.md | 26 + .../vendor/shared/.decided/config.yaml | 3 + .../vendor/shared/decisions/shared-policy.md | 26 + mkdocs.yml | 1 + rac-localview/VIEWER_CONTRACT.md | 29 +- rac-localview/src/viewer/App.tsx | 9 +- rac-localview/src/viewer/DetailView.tsx | 18 +- rac-localview/src/viewer/ListView.tsx | 6 +- rac-localview/src/viewer/data.ts | 89 +- rac-localview/src/viewer/graph.ts | 21 +- rac-localview/src/viewer/types.ts | 28 +- rac-localview/test/App.test.tsx | 16 +- rac-localview/test/federation.test.ts | 61 + rac-localview/test/fixtures.ts | 155 + rust/Cargo.lock | 81 +- rust/Cargo.toml | 2 +- rust/decided-mcp/Cargo.toml | 5 +- rust/decided-mcp/src/audit.rs | 122 +- rust/decided-mcp/src/graph.rs | 499 ++- rust/decided-mcp/src/main.rs | 650 +++- rust/decided-mcp/src/sidecar.rs | 2 +- rust/decided-mcp/src/tools.rs | 654 +++- rust/decided-mcp/tests/federation.rs | 622 ++++ rust/decided-mcp/tests/federation_graph.rs | 500 +++ rust/decided-mcp/tests/http_transport.rs | 121 + rust/decided/Cargo.toml | 6 +- rust/decided/tests/cli.rs | 259 +- rust/decided/tests/eval_graph.rs | 105 + rust/decided/tests/federation.rs | 916 ++++++ rust/decided/tests/federation_graph.rs | 914 ++++++ .../tests/federation_graph_support/mod.rs | 285 ++ .../decided/tests/federation_observability.rs | 441 +++ rust/decided/tests/federation_support/mod.rs | 464 +++ .../tests/federation_v2_runtime_guards.rs | 286 ++ .../tests/federation_v2_write_boundary.rs | 125 + rust/fixtures/eval/README.md | 37 +- .../eval/federation/.decided/config.yaml | 3 + .../eval/federation/.decided/corpus.md | 30 + .../federation/audit/.decided/config.yaml | 3 + .../audit/decisions/quorum-audit-archive.md | 26 + rust/fixtures/eval/federation/baseline.json | 23 + .../federation/child/.decided/config.yaml | 3 + .../eval/federation/child/.decided/corpus.md | 19 + .../child/decisions/local-quantum-notes.md | 26 + .../vendor/standards/.decided/config.yaml | 3 + .../decisions/compaction-decoy-01.md | 27 + .../decisions/compaction-decoy-02.md | 27 + .../decisions/compaction-decoy-03.md | 27 + .../decisions/compaction-decoy-04.md | 27 + .../decisions/compaction-decoy-05.md | 27 + .../decisions/compaction-decoy-06.md | 27 + .../decisions/ledger-reference-hub.md | 27 + .../quantum-ledger-compaction-anchor.md | 26 + .../decisions/service-standard-001.md | 30 + .../decisions/service-standard-002.md | 30 + .../decisions/service-standard-003.md | 30 + .../decisions/service-standard-004.md | 30 + .../decisions/service-standard-005.md | 30 + .../decisions/service-standard-006.md | 30 + .../decisions/service-standard-007.md | 30 + .../decisions/service-standard-008.md | 30 + .../decisions/service-standard-009.md | 30 + .../decisions/service-standard-010.md | 30 + .../decisions/service-standard-011.md | 30 + .../decisions/service-standard-012.md | 30 + .../decisions/service-standard-013.md | 30 + .../decisions/service-standard-014.md | 30 + .../decisions/service-standard-015.md | 30 + .../decisions/service-standard-016.md | 30 + .../decisions/service-standard-017.md | 30 + .../decisions/service-standard-018.md | 30 + .../decisions/service-standard-019.md | 30 + .../decisions/service-standard-020.md | 30 + .../decisions/service-standard-021.md | 30 + .../decisions/service-standard-022.md | 30 + .../decisions/service-standard-023.md | 30 + .../decisions/service-standard-024.md | 30 + .../decisions/service-standard-025.md | 30 + .../decisions/service-standard-026.md | 30 + .../decisions/service-standard-027.md | 30 + .../decisions/service-standard-028.md | 30 + .../decisions/service-standard-029.md | 30 + .../decisions/service-standard-030.md | 30 + .../decisions/service-standard-031.md | 30 + .../decisions/service-standard-032.md | 30 + .../fixtures/eval/federation/eval-config.json | 14 + .../graph-decisions/root-quantum-notes.md | 26 + .../eval/federation/graph-track/baseline.json | 31 + .../federation/graph-track/eval-config.json | 15 + .../eval/federation/graph-track/queries.json | 37 + .../policy-tree/.decided/config.yaml | 3 + .../federation/policy-tree/.decided/corpus.md | 20 + .../decisions/transitive-policy-index.md | 30 + .../vendor/deep/.decided/config.yaml | 3 + .../cerulean-quorum-reconciliation.md | 28 + rust/fixtures/eval/federation/queries.json | 21 + rust/rac-engine/Cargo.toml | 1 + .../portal/asdecided-portal-legacy-shell.html | 74 + .../assets/portal/asdecided-portal-shell.html | 18 +- .../schemas/export-documents-v1.schema.json | 128 + .../schemas/export-graph-v1.schema.json | 163 + .../schemas/export-viewer-v1.schema.json | 163 + rust/rac-engine/src/agent_rules.rs | 10 + rust/rac-engine/src/budget.rs | 119 +- rust/rac-engine/src/cli.rs | 178 +- rust/rac-engine/src/commands.rs | 800 ++++- rust/rac-engine/src/composition.rs | 357 +- rust/rac-engine/src/coverage.rs | 74 +- rust/rac-engine/src/derived.rs | 76 + rust/rac-engine/src/derived_cache.rs | 618 +++- rust/rac-engine/src/doctor.rs | 186 +- rust/rac-engine/src/eval.rs | 87 +- rust/rac-engine/src/export.rs | 721 ++++- rust/rac-engine/src/federated_corpus.rs | 437 ++- rust/rac-engine/src/federation.rs | 2882 ++++++++++++++++- rust/rac-engine/src/federation_generation.rs | 457 +++ .../src/federation_observability.rs | 746 +++++ rust/rac-engine/src/gate.rs | 4 +- rust/rac-engine/src/graph_composition.rs | 1685 ++++++++++ rust/rac-engine/src/graph_federated_corpus.rs | 985 ++++++ rust/rac-engine/src/herald.rs | 61 +- rust/rac-engine/src/hook.rs | 21 +- rust/rac-engine/src/index.rs | 30 +- rust/rac-engine/src/index_format.rs | 9 + rust/rac-engine/src/index_store.rs | 732 ++++- rust/rac-engine/src/inspect.rs | 30 +- rust/rac-engine/src/lib.rs | 4 + rust/rac-engine/src/output.rs | 568 +++- rust/rac-engine/src/portal.rs | 60 +- rust/rac-engine/src/portfolio.rs | 95 +- rust/rac-engine/src/relationships.rs | 61 + rust/rac-engine/src/resolve.rs | 6 +- rust/rac-engine/src/retrieve.rs | 109 +- rust/rac-engine/src/review.rs | 64 +- rust/rac-engine/src/sentry.rs | 129 +- rust/rac-engine/src/skill.rs | 38 +- rust/rac-engine/src/stats.rs | 40 +- rust/rac-engine/tests/composition.rs | 67 + .../tests/composition_graph_facade.rs | 251 ++ rust/rac-engine/tests/federated_cache.rs | 112 +- .../tests/federated_export_emission.rs | 1058 ++++++ .../tests/federation_graph_cache.rs | 414 +++ .../tests/federation_graph_loader.rs | 431 +++ rust/rac-engine/tests/federation_loader.rs | 270 +- rust/rac-engine/tests/graph_composition.rs | 629 ++++ .../tests/graph_federated_corpus.rs | 341 ++ rust/rac-engine/tests/okf_v02.rs | 9 + .../tests/source_aware_substrate.rs | 36 + rust/tools/export_schema_contracts.py | 306 +- server.json | 4 +- 199 files changed, 28330 insertions(+), 1057 deletions(-) create mode 100644 decisions/decisions/adr-144-bounded-acyclic-corpus-federation-graph.md create mode 100644 decisions/decisions/adr-145-versioned-multiple-parent-federation-manifest.md create mode 100644 decisions/decisions/adr-146-global-source-qualified-federated-resolution.md create mode 100644 decisions/decisions/adr-147-decision-backed-federated-override-chains.md create mode 100644 decisions/decisions/adr-148-closure-wide-federated-serving-generations.md create mode 100644 decisions/decisions/adr-149-git-repository-truth-is-forge-agnostic.md create mode 100644 decisions/designs/corpus-federation-graph-composition.md create mode 100644 docs/federation.md create mode 100644 examples/federation/README.md create mode 100644 examples/federation/app/.decided/config.yaml create mode 100644 examples/federation/app/.decided/corpus.md create mode 100644 examples/federation/app/decisions/application-override.md create mode 100644 examples/federation/app/decisions/application-policy.md create mode 100644 examples/federation/app/vendor/platform/.decided/config.yaml create mode 100644 examples/federation/app/vendor/platform/.decided/corpus.md create mode 100644 examples/federation/app/vendor/platform/decisions/platform-policy.md create mode 100644 examples/federation/app/vendor/platform/vendor/shared/.decided/config.yaml create mode 100644 examples/federation/app/vendor/platform/vendor/shared/decisions/shared-policy.md create mode 100644 examples/federation/app/vendor/security/.decided/config.yaml create mode 100644 examples/federation/app/vendor/security/.decided/corpus.md create mode 100644 examples/federation/app/vendor/security/decisions/security-policy.md create mode 100644 examples/federation/app/vendor/security/vendor/shared/.decided/config.yaml create mode 100644 examples/federation/app/vendor/security/vendor/shared/decisions/shared-policy.md create mode 100644 rac-localview/test/federation.test.ts create mode 100644 rust/decided-mcp/tests/federation.rs create mode 100644 rust/decided-mcp/tests/federation_graph.rs create mode 100644 rust/decided/tests/eval_graph.rs create mode 100644 rust/decided/tests/federation.rs create mode 100644 rust/decided/tests/federation_graph.rs create mode 100644 rust/decided/tests/federation_graph_support/mod.rs create mode 100644 rust/decided/tests/federation_observability.rs create mode 100644 rust/decided/tests/federation_support/mod.rs create mode 100644 rust/decided/tests/federation_v2_runtime_guards.rs create mode 100644 rust/decided/tests/federation_v2_write_boundary.rs create mode 100644 rust/fixtures/eval/federation/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/.decided/corpus.md create mode 100644 rust/fixtures/eval/federation/audit/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/audit/decisions/quorum-audit-archive.md create mode 100644 rust/fixtures/eval/federation/baseline.json create mode 100644 rust/fixtures/eval/federation/child/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/child/.decided/corpus.md create mode 100644 rust/fixtures/eval/federation/child/decisions/local-quantum-notes.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-01.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-02.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-03.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-04.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-05.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/compaction-decoy-06.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/ledger-reference-hub.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/quantum-ledger-compaction-anchor.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-001.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-002.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-003.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-004.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-005.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-006.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-007.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-008.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-009.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-010.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-011.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-012.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-013.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-014.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-015.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-016.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-017.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-018.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-019.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-020.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-021.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-022.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-023.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-024.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-025.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-026.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-027.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-028.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-029.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-030.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-031.md create mode 100644 rust/fixtures/eval/federation/child/vendor/standards/decisions/service-standard-032.md create mode 100644 rust/fixtures/eval/federation/eval-config.json create mode 100644 rust/fixtures/eval/federation/graph-decisions/root-quantum-notes.md create mode 100644 rust/fixtures/eval/federation/graph-track/baseline.json create mode 100644 rust/fixtures/eval/federation/graph-track/eval-config.json create mode 100644 rust/fixtures/eval/federation/graph-track/queries.json create mode 100644 rust/fixtures/eval/federation/policy-tree/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/policy-tree/.decided/corpus.md create mode 100644 rust/fixtures/eval/federation/policy-tree/decisions/transitive-policy-index.md create mode 100644 rust/fixtures/eval/federation/policy-tree/vendor/deep/.decided/config.yaml create mode 100644 rust/fixtures/eval/federation/policy-tree/vendor/deep/decisions/cerulean-quorum-reconciliation.md create mode 100644 rust/fixtures/eval/federation/queries.json create mode 100644 rust/rac-engine/assets/portal/asdecided-portal-legacy-shell.html create mode 100644 rust/rac-engine/src/federation_generation.rs create mode 100644 rust/rac-engine/src/federation_observability.rs create mode 100644 rust/rac-engine/src/graph_composition.rs create mode 100644 rust/rac-engine/src/graph_federated_corpus.rs create mode 100644 rust/rac-engine/tests/composition_graph_facade.rs create mode 100644 rust/rac-engine/tests/federated_export_emission.rs create mode 100644 rust/rac-engine/tests/federation_graph_cache.rs create mode 100644 rust/rac-engine/tests/federation_graph_loader.rs create mode 100644 rust/rac-engine/tests/graph_composition.rs create mode 100644 rust/rac-engine/tests/graph_federated_corpus.rs diff --git a/.cursor/rules b/.cursor/rules index f5cf4481..a242c3d2 100644 --- a/.cursor/rules +++ b/.cursor/rules @@ -1,4 +1,4 @@ - + ## Settled decisions (AsDecided) @@ -65,6 +65,7 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KVK19NPWFYC9** — ADR-074: The Graph Export Surfaces Typed Relationship Edges _(Technical)_ - **RAC-KVNM01QPBPXB** — ADR-075: The Pre-Merge Check Tier Is a Required Merge Gate on `main` _(Process)_ - **RAC-KVSQ2A0BB9XF** — ADR-079: Note-Tool Exports Are Ingested by Normalisation, Not markitdown _(Architecture)_ +- **RAC-KVSTYDARXKYW** — ADR-080: The Single Source of Truth Is Git, Not a Database _(Architecture)_ - **RAC-KVTS86ZGVJV7** — ADR-077: The Two-Gate Capture Write Model _(Architecture)_ - **RAC-KW2YW6XK593X** — ADR-084: Read-Access Audit Recorder _(Product)_ - **RAC-KW47GFBHK31W** — ADR-086: Air-Gap Posture and Enterprise Telemetry Hard-Lock _(Product)_ @@ -98,17 +99,20 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KXGVR299XY5E** — ADR-116: The Native Rust Engine Is a Sanctioned Second Implementation Under Lockstep Guards _(Architecture)_ - **RAC-KYVTHFQD44BP** — ADR-124: Publish the Native MCP Server Through OCI and the Official Registry _(Architecture)_ - **RAC-KYYC7HBFMRBA** — ADR-126: Package the Native MCP Server for Docker's MCP Catalog _(Architecture)_ -- **RAC-KZKMJ8Q49GHV** — ADR-133: Start Corpus Federation With One Direct Parent _(Architecture)_ +- **RAC-KZ0F0RG3N5XT** — ADR-149: Git Repository Truth Is Forge-Agnostic _(Architecture)_ - **RAC-KZKMJ8WSMFA1** — ADR-134: Declare and Verify an Offline Materialised Parent _(Architecture)_ - **RAC-KZKMJ92ABVJG** — ADR-135: Use `corpus.source` as the Global Corpus Identity _(Architecture)_ -- **RAC-KZKMJ97Z2PBE** — ADR-136: Resolve Cross-Corpus References Without Implicit Precedence _(Architecture)_ - **RAC-KZKMJ9DGR69Z** — ADR-137: Require Decision-Backed Explicit Federation Overrides _(Architecture)_ - **RAC-KZKMJ9K3AFB2** — ADR-138: Build Federation Through One Source-Aware Read Model _(Architecture)_ - **RAC-KZKMJ9RP0KNV** — ADR-139: Rank Federated Retrieval Without Source Preference _(Technical)_ - **RAC-KZKMJ9YA8BRG** — ADR-140: Apply Inherited Decisions to Child Code _(Product)_ - **RAC-KZKMJA3YK5Y1** — ADR-141: Add Bounded Federation Provenance to the Existing MCP Surface _(Product)_ - **RAC-KZKMJA9JVF6J** — ADR-142: Export the Inherited Layer by Default _(Product)_ -- **RAC-KZKMJAF599TB** — ADR-143: Version Federated Generations, Cache State, and Freshness _(Technical)_ +- **RAC-KZN54DB1VNPB** — ADR-144: Compose Federation as a Bounded Acyclic Source Graph _(Architecture)_ +- **RAC-KZN54DB2M7FZ** — ADR-145: Declare Multiple Offline Parents Through a Versioned Federation Manifest _(Architecture)_ +- **RAC-KZN54DB3V0ZC** — ADR-146: Resolve Federated Artifacts by Global Source Identity Without Precedence _(Architecture)_ +- **RAC-KZN54DB4QY0R** — ADR-147: Permit Decision-Backed Override Chains Across the Federation Graph _(Architecture)_ +- **RAC-KZN54DB55X9R** — ADR-148: Key Serving State to the Entire Federated Closure _(Technical)_ - **RAC-MCP20260728A** — ADR-121: Dual-Era MCP Protocol Compatibility _(Architecture)_ - **RAC-P55FRE5HNE55** — ADR-118: Native Event Freshness Acceleration - **RAC-P61BA5EDE7A0** — ADR-119: Base-Plus-Delta Serving Generations diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f5cf4481..a242c3d2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,4 @@ - + ## Settled decisions (AsDecided) @@ -65,6 +65,7 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KVK19NPWFYC9** — ADR-074: The Graph Export Surfaces Typed Relationship Edges _(Technical)_ - **RAC-KVNM01QPBPXB** — ADR-075: The Pre-Merge Check Tier Is a Required Merge Gate on `main` _(Process)_ - **RAC-KVSQ2A0BB9XF** — ADR-079: Note-Tool Exports Are Ingested by Normalisation, Not markitdown _(Architecture)_ +- **RAC-KVSTYDARXKYW** — ADR-080: The Single Source of Truth Is Git, Not a Database _(Architecture)_ - **RAC-KVTS86ZGVJV7** — ADR-077: The Two-Gate Capture Write Model _(Architecture)_ - **RAC-KW2YW6XK593X** — ADR-084: Read-Access Audit Recorder _(Product)_ - **RAC-KW47GFBHK31W** — ADR-086: Air-Gap Posture and Enterprise Telemetry Hard-Lock _(Product)_ @@ -98,17 +99,20 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KXGVR299XY5E** — ADR-116: The Native Rust Engine Is a Sanctioned Second Implementation Under Lockstep Guards _(Architecture)_ - **RAC-KYVTHFQD44BP** — ADR-124: Publish the Native MCP Server Through OCI and the Official Registry _(Architecture)_ - **RAC-KYYC7HBFMRBA** — ADR-126: Package the Native MCP Server for Docker's MCP Catalog _(Architecture)_ -- **RAC-KZKMJ8Q49GHV** — ADR-133: Start Corpus Federation With One Direct Parent _(Architecture)_ +- **RAC-KZ0F0RG3N5XT** — ADR-149: Git Repository Truth Is Forge-Agnostic _(Architecture)_ - **RAC-KZKMJ8WSMFA1** — ADR-134: Declare and Verify an Offline Materialised Parent _(Architecture)_ - **RAC-KZKMJ92ABVJG** — ADR-135: Use `corpus.source` as the Global Corpus Identity _(Architecture)_ -- **RAC-KZKMJ97Z2PBE** — ADR-136: Resolve Cross-Corpus References Without Implicit Precedence _(Architecture)_ - **RAC-KZKMJ9DGR69Z** — ADR-137: Require Decision-Backed Explicit Federation Overrides _(Architecture)_ - **RAC-KZKMJ9K3AFB2** — ADR-138: Build Federation Through One Source-Aware Read Model _(Architecture)_ - **RAC-KZKMJ9RP0KNV** — ADR-139: Rank Federated Retrieval Without Source Preference _(Technical)_ - **RAC-KZKMJ9YA8BRG** — ADR-140: Apply Inherited Decisions to Child Code _(Product)_ - **RAC-KZKMJA3YK5Y1** — ADR-141: Add Bounded Federation Provenance to the Existing MCP Surface _(Product)_ - **RAC-KZKMJA9JVF6J** — ADR-142: Export the Inherited Layer by Default _(Product)_ -- **RAC-KZKMJAF599TB** — ADR-143: Version Federated Generations, Cache State, and Freshness _(Technical)_ +- **RAC-KZN54DB1VNPB** — ADR-144: Compose Federation as a Bounded Acyclic Source Graph _(Architecture)_ +- **RAC-KZN54DB2M7FZ** — ADR-145: Declare Multiple Offline Parents Through a Versioned Federation Manifest _(Architecture)_ +- **RAC-KZN54DB3V0ZC** — ADR-146: Resolve Federated Artifacts by Global Source Identity Without Precedence _(Architecture)_ +- **RAC-KZN54DB4QY0R** — ADR-147: Permit Decision-Backed Override Chains Across the Federation Graph _(Architecture)_ +- **RAC-KZN54DB55X9R** — ADR-148: Key Serving State to the Entire Federated Closure _(Technical)_ - **RAC-MCP20260728A** — ADR-121: Dual-Era MCP Protocol Compatibility _(Architecture)_ - **RAC-P55FRE5HNE55** — ADR-118: Native Event Freshness Acceleration - **RAC-P61BA5EDE7A0** — ADR-119: Base-Plus-Delta Serving Generations diff --git a/AGENTS.md b/AGENTS.md index f5cf4481..a242c3d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ - + ## Settled decisions (AsDecided) @@ -65,6 +65,7 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KVK19NPWFYC9** — ADR-074: The Graph Export Surfaces Typed Relationship Edges _(Technical)_ - **RAC-KVNM01QPBPXB** — ADR-075: The Pre-Merge Check Tier Is a Required Merge Gate on `main` _(Process)_ - **RAC-KVSQ2A0BB9XF** — ADR-079: Note-Tool Exports Are Ingested by Normalisation, Not markitdown _(Architecture)_ +- **RAC-KVSTYDARXKYW** — ADR-080: The Single Source of Truth Is Git, Not a Database _(Architecture)_ - **RAC-KVTS86ZGVJV7** — ADR-077: The Two-Gate Capture Write Model _(Architecture)_ - **RAC-KW2YW6XK593X** — ADR-084: Read-Access Audit Recorder _(Product)_ - **RAC-KW47GFBHK31W** — ADR-086: Air-Gap Posture and Enterprise Telemetry Hard-Lock _(Product)_ @@ -98,17 +99,20 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KXGVR299XY5E** — ADR-116: The Native Rust Engine Is a Sanctioned Second Implementation Under Lockstep Guards _(Architecture)_ - **RAC-KYVTHFQD44BP** — ADR-124: Publish the Native MCP Server Through OCI and the Official Registry _(Architecture)_ - **RAC-KYYC7HBFMRBA** — ADR-126: Package the Native MCP Server for Docker's MCP Catalog _(Architecture)_ -- **RAC-KZKMJ8Q49GHV** — ADR-133: Start Corpus Federation With One Direct Parent _(Architecture)_ +- **RAC-KZ0F0RG3N5XT** — ADR-149: Git Repository Truth Is Forge-Agnostic _(Architecture)_ - **RAC-KZKMJ8WSMFA1** — ADR-134: Declare and Verify an Offline Materialised Parent _(Architecture)_ - **RAC-KZKMJ92ABVJG** — ADR-135: Use `corpus.source` as the Global Corpus Identity _(Architecture)_ -- **RAC-KZKMJ97Z2PBE** — ADR-136: Resolve Cross-Corpus References Without Implicit Precedence _(Architecture)_ - **RAC-KZKMJ9DGR69Z** — ADR-137: Require Decision-Backed Explicit Federation Overrides _(Architecture)_ - **RAC-KZKMJ9K3AFB2** — ADR-138: Build Federation Through One Source-Aware Read Model _(Architecture)_ - **RAC-KZKMJ9RP0KNV** — ADR-139: Rank Federated Retrieval Without Source Preference _(Technical)_ - **RAC-KZKMJ9YA8BRG** — ADR-140: Apply Inherited Decisions to Child Code _(Product)_ - **RAC-KZKMJA3YK5Y1** — ADR-141: Add Bounded Federation Provenance to the Existing MCP Surface _(Product)_ - **RAC-KZKMJA9JVF6J** — ADR-142: Export the Inherited Layer by Default _(Product)_ -- **RAC-KZKMJAF599TB** — ADR-143: Version Federated Generations, Cache State, and Freshness _(Technical)_ +- **RAC-KZN54DB1VNPB** — ADR-144: Compose Federation as a Bounded Acyclic Source Graph _(Architecture)_ +- **RAC-KZN54DB2M7FZ** — ADR-145: Declare Multiple Offline Parents Through a Versioned Federation Manifest _(Architecture)_ +- **RAC-KZN54DB3V0ZC** — ADR-146: Resolve Federated Artifacts by Global Source Identity Without Precedence _(Architecture)_ +- **RAC-KZN54DB4QY0R** — ADR-147: Permit Decision-Backed Override Chains Across the Federation Graph _(Architecture)_ +- **RAC-KZN54DB55X9R** — ADR-148: Key Serving State to the Entire Federated Closure _(Technical)_ - **RAC-MCP20260728A** — ADR-121: Dual-Era MCP Protocol Compatibility _(Architecture)_ - **RAC-P55FRE5HNE55** — ADR-118: Native Event Freshness Acceleration - **RAC-P61BA5EDE7A0** — ADR-119: Base-Plus-Delta Serving Generations diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b935e2a..a7cb8866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,56 @@ details, release history over commit history. ## Unreleased +## v0.29.0 — 2026-08-30 + +### Graph-complete corpus federation + +- Added manifest version 2 with one to 32 direct parents, bounded recursive + DAG verification, topology-binding `sha256-v2` pins, same-pin diamond + deduplication, deterministic cycle/divergent-pin failures, and complete + physical-route verification. Version 1 and repositories without a manifest + retain their released behavior. +- Made `corpus.source` the stable global namespace for artifacts and paths. + Source-qualified IDs, source-local aliases, legal equal IDs across sources, + and deterministic bare-reference ambiguity now share one contextual resolver. +- Added explicit Decision-backed override chains and diamond reconvergence. + Catalog history retains every original, replacement, rationale, mapping owner, + and provenance state while only the unique terminal participates in live + retrieval, relationships, path routing, and code enforcement. +- Unified validation, search, retrieval, `decisions-for`, Gate, Sentry, all six + MCP tools, cache generations, audit identity, and viewer/documents/graph + exports over the same immutable verified closure. Inherited Decisions at any + depth can govern root code; every inherited physical route remains read-only. + +### Federation operators can see why + +- Added `decided corpus status [directory] [--json]`. It verifies the full + closure before reporting logical sources, exact pins, canonical and physical + routes, edges, materialisation paths, graph depth, artifact projections, + overrides, and read-only boundaries. A stale or tampered route fails before + partial output. +- Added `decided corpus explain [directory] [--from source] + [--json]`. It shows contextual visibility and aliases, historical candidates, + the selected source-owned record, effective terminal, and complete ordered + override provenance; ambiguous and missing results remain machine-readable. +- Added a runnable four-source federation example with two direct parents, a + shared diamond ancestor, identical independently verified routes, and an + explicit root override. + +### Git-native, not forge-dependent + +- Made the architectural boundary explicit: corpus truth is reviewed Markdown + in Git, not GitHub, Cursor Origin, a branch named `main`, or a hosted database. + Federation and its stable reports work from a plain materialised tree without + `.git`, networking, a remote, or a forge API; optional review/check adapters + remain outside core semantics. - Added versioned Draft 2020-12 schemas for the viewer, documents, and graph export contracts. `decided export --schema ` emits - the packaged schema bytes offline, and CI now checks fixture and live-corpus - exports for contract and field-set drift. -- Added optional `corpus.source` configuration as the stable corpus provenance - identity shared by viewer, documents, and graph exports. Existing - repositories fall back to their lower-case `repository_key`; uninitialised - corpora retain the released directory-basename source value. + packaged schema bytes offline, and CI checks fixture and live-corpus exports + for contract and field-set drift. +- Added opt-in `decided init --parent-corpus` guidance and explicit + `decided corpus digest --version 2` pin calculation without materialising, + fetching, editing, or repinning a parent automatically. ## v0.28.0 — 2026-08-09 diff --git a/CLAUDE.md b/CLAUDE.md index b7789ea2..a9990262 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ to the corpus artifact and they load through the imports below. - Historical roadmaps: `decisions/roadmaps/archive/` - Decisions (ADRs): `decisions/decisions/` - + ## Settled decisions (AsDecided) @@ -90,6 +90,7 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KVK19NPWFYC9** — ADR-074: The Graph Export Surfaces Typed Relationship Edges _(Technical)_ - **RAC-KVNM01QPBPXB** — ADR-075: The Pre-Merge Check Tier Is a Required Merge Gate on `main` _(Process)_ - **RAC-KVSQ2A0BB9XF** — ADR-079: Note-Tool Exports Are Ingested by Normalisation, Not markitdown _(Architecture)_ +- **RAC-KVSTYDARXKYW** — ADR-080: The Single Source of Truth Is Git, Not a Database _(Architecture)_ - **RAC-KVTS86ZGVJV7** — ADR-077: The Two-Gate Capture Write Model _(Architecture)_ - **RAC-KW2YW6XK593X** — ADR-084: Read-Access Audit Recorder _(Product)_ - **RAC-KW47GFBHK31W** — ADR-086: Air-Gap Posture and Enterprise Telemetry Hard-Lock _(Product)_ @@ -123,17 +124,20 @@ These decisions are already accepted. Do not re-open or contradict them; ask the - **RAC-KXGVR299XY5E** — ADR-116: The Native Rust Engine Is a Sanctioned Second Implementation Under Lockstep Guards _(Architecture)_ - **RAC-KYVTHFQD44BP** — ADR-124: Publish the Native MCP Server Through OCI and the Official Registry _(Architecture)_ - **RAC-KYYC7HBFMRBA** — ADR-126: Package the Native MCP Server for Docker's MCP Catalog _(Architecture)_ -- **RAC-KZKMJ8Q49GHV** — ADR-133: Start Corpus Federation With One Direct Parent _(Architecture)_ +- **RAC-KZ0F0RG3N5XT** — ADR-149: Git Repository Truth Is Forge-Agnostic _(Architecture)_ - **RAC-KZKMJ8WSMFA1** — ADR-134: Declare and Verify an Offline Materialised Parent _(Architecture)_ - **RAC-KZKMJ92ABVJG** — ADR-135: Use `corpus.source` as the Global Corpus Identity _(Architecture)_ -- **RAC-KZKMJ97Z2PBE** — ADR-136: Resolve Cross-Corpus References Without Implicit Precedence _(Architecture)_ - **RAC-KZKMJ9DGR69Z** — ADR-137: Require Decision-Backed Explicit Federation Overrides _(Architecture)_ - **RAC-KZKMJ9K3AFB2** — ADR-138: Build Federation Through One Source-Aware Read Model _(Architecture)_ - **RAC-KZKMJ9RP0KNV** — ADR-139: Rank Federated Retrieval Without Source Preference _(Technical)_ - **RAC-KZKMJ9YA8BRG** — ADR-140: Apply Inherited Decisions to Child Code _(Product)_ - **RAC-KZKMJA3YK5Y1** — ADR-141: Add Bounded Federation Provenance to the Existing MCP Surface _(Product)_ - **RAC-KZKMJA9JVF6J** — ADR-142: Export the Inherited Layer by Default _(Product)_ -- **RAC-KZKMJAF599TB** — ADR-143: Version Federated Generations, Cache State, and Freshness _(Technical)_ +- **RAC-KZN54DB1VNPB** — ADR-144: Compose Federation as a Bounded Acyclic Source Graph _(Architecture)_ +- **RAC-KZN54DB2M7FZ** — ADR-145: Declare Multiple Offline Parents Through a Versioned Federation Manifest _(Architecture)_ +- **RAC-KZN54DB3V0ZC** — ADR-146: Resolve Federated Artifacts by Global Source Identity Without Precedence _(Architecture)_ +- **RAC-KZN54DB4QY0R** — ADR-147: Permit Decision-Backed Override Chains Across the Federation Graph _(Architecture)_ +- **RAC-KZN54DB55X9R** — ADR-148: Key Serving State to the Entire Federated Closure _(Technical)_ - **RAC-MCP20260728A** — ADR-121: Dual-Era MCP Protocol Compatibility _(Architecture)_ - **RAC-P55FRE5HNE55** — ADR-118: Native Event Freshness Acceleration - **RAC-P61BA5EDE7A0** — ADR-119: Base-Plus-Delta Serving Generations diff --git a/README.md b/README.md index 73f29655..a706d404 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,22 @@ decisions/ Existing artifact IDs such as `RAC-ABC123DEF456` are durable identities and do not change with the product name. +## Federate corpora + +Manifest version 2 composes several pinned, materialised parent corpora into +one source-aware DAG while preserving every source, diamond route, override, +and read-only boundary: + +```sh +decided corpus status decisions/ +decided corpus explain organization/standards::ADR-01K000000001 decisions/ +``` + +Federation is fully offline and forge-neutral: the engine consumes verified +working-tree bytes, not GitHub APIs or a hosted index. See the +[federation guide](docs/federation.md) and +[runnable multi-parent example](examples/federation/). + ## Migrate an existing repository Migration is explicit and never runs during an ordinary command: diff --git a/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md b/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md index 20e50ab8..4e7c65f8 100644 --- a/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md +++ b/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md @@ -3,7 +3,7 @@ schema_version: 1 id: RAC-KVSTYDARXKYW type: decision --- -# ADR-080: The Single Source of Truth Is Git `main`, Not a Database +# ADR-080: The Single Source of Truth Is Git, Not a Database ## Context @@ -67,7 +67,11 @@ truth, and it is the one the team already reviews and versions. ## Status -Proposed +Accepted + +ADR-149 preserves this decision's no-database, Git-backed source-of-truth +contract while amending its hard-coded Git-host `main` branch language with a +forge-, remote-, and branch-name-neutral repository contract. ## Category @@ -96,6 +100,7 @@ Architecture - adr-065 - adr-066 - adr-073 +- adr-149 ## Related Roadmaps diff --git a/decisions/decisions/adr-089-corpus-federation.md b/decisions/decisions/adr-089-corpus-federation.md index 5638905d..bddfa47c 100644 --- a/decisions/decisions/adr-089-corpus-federation.md +++ b/decisions/decisions/adr-089-corpus-federation.md @@ -21,7 +21,8 @@ rules that should apply across an organisation. Federation is the single genuine semantic change among the enterprise asks, and the deepest. Today the corpus is single-tree: one canonical root per repository -(ADR-018) with git `main` as the only source of truth (ADR-080); cross-repository +(ADR-018) with its reviewed Git state as the only source of truth (ADR-080 as +amended by ADR-149); cross-repository references do not resolve; relationships are local and Git-native (ADR-016, ADR-055). A parent-corpus mechanism touches resolution, validation, and export at once. @@ -46,8 +47,8 @@ Non-negotiable constraints on any future federation design: - **Deterministic and offline** (ADR-002): parent resolution reads materialised bytes — a pinned submodule, a vendored bundle, or a path — never a live network fetch inside the validate path. -- **Single canonical state preserved per repo** (ADR-018, ADR-080): a parent is - an inherited, read-only layer; the child's `main` remains its own truth; +- **Single canonical state preserved per repo** (ADR-018, ADR-080, ADR-149): a parent is + an inherited, read-only layer; the child's reviewed Git state remains its own truth; overrides are explicit, not implicit precedence. - **Git-native and human-readable** (ADR-016, ADR-055): inheritance is declared in Markdown (a `## inherits` section) plus a pinned source reference; no @@ -115,7 +116,7 @@ last — built with the design partner once the additive items have landed. ## Relationship to Other Decisions -- ADR-018, ADR-080: federation must preserve the single canonical state per repo; +- ADR-018, ADR-080, ADR-149: federation must preserve the single canonical state per repo; a parent is read-only and inherited. - ADR-016, ADR-055: inheritance stays Git-native and human-readable; no database. - ADR-002: parent resolution is offline over materialised bytes. diff --git a/decisions/decisions/adr-133-single-direct-parent-federation.md b/decisions/decisions/adr-133-single-direct-parent-federation.md index 4e2856c5..af0bb77e 100644 --- a/decisions/decisions/adr-133-single-direct-parent-federation.md +++ b/decisions/decisions/adr-133-single-direct-parent-federation.md @@ -47,7 +47,11 @@ migration and new decisions about ordering, collisions, cycles, and overrides. ## Status -Accepted +Superseded + +Superseded by ADR-144 for manifest-version-2 graph composition. This ADR's +one-direct-leaf-parent behavior remains the exact compatibility contract for a +version-1 root under ADR-145. ## Category @@ -77,10 +81,12 @@ a separately ratified extension. - adr-080 - adr-085 - adr-089 +- adr-144 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-134-offline-pinned-parent-materialisation.md b/decisions/decisions/adr-134-offline-pinned-parent-materialisation.md index be565ca1..a28a56fb 100644 --- a/decisions/decisions/adr-134-offline-pinned-parent-materialisation.md +++ b/decisions/decisions/adr-134-offline-pinned-parent-materialisation.md @@ -7,6 +7,12 @@ type: decision ## Context +> **Amended by ADR-145.** Version 1 retains this ADR's exact single-parent +> carrier and digest. Manifest version 2 permits a strict parent sequence and +> uses a topology-binding digest that authenticates exact nested-manifest +> presence and bytes. Materialised-only, offline, containment, exact-byte, +> source-verification, and pre-overlay failure rules remain authoritative. + ADR-089 requires federation to remain offline, deterministic, Git-native, and human-readable. Those constraints still leave materially different mechanisms: a configuration key, a live repository URL, an adjacent checkout, a Git commit @@ -95,11 +101,12 @@ operational manifest keeps the mechanism visible without making it an artifact. - adr-065 - adr-080 - adr-089 -- adr-133 +- adr-145 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-135-corpus-source-global-identity.md b/decisions/decisions/adr-135-corpus-source-global-identity.md index 81cdcacc..89fc7794 100644 --- a/decisions/decisions/adr-135-corpus-source-global-identity.md +++ b/decisions/decisions/adr-135-corpus-source-global-identity.md @@ -7,6 +7,13 @@ type: decision ## Context +> **Clarified by ADR-144 and ADR-146.** Every logical graph node has one +> explicit source distinct from every other logical node. Several physical +> routes may reuse a source only when they verify the same canonical version-2 +> node digest and deduplicate to that one node. Version 2 permits the same +> canonical artifact id in distinct source namespaces and treats unqualified +> use as ambiguous unless explicit overrides converge every candidate. + Federation needs an identity that survives checkout moves and distinguishes an artifact's corpus from the namespace used to mint its opaque ID. Existing `repository_key` values are short ID-generation prefixes and can be shared or @@ -78,12 +85,14 @@ new pinned state of the same corpus. - adr-026 - adr-074 - adr-089 -- adr-133 - adr-134 +- adr-144 +- adr-146 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-136-qualified-cross-corpus-resolution.md b/decisions/decisions/adr-136-qualified-cross-corpus-resolution.md index f520b464..b4e0d017 100644 --- a/decisions/decisions/adr-136-qualified-cross-corpus-resolution.md +++ b/decisions/decisions/adr-136-qualified-cross-corpus-resolution.md @@ -51,7 +51,11 @@ unqualified surfaces. ## Status -Accepted +Superseded + +Superseded by ADR-146 for manifest-version-2 graph resolution. This ADR's +alias-qualified references, canonical-collision finding, and direct-override +behavior remain the exact compatibility contract for a version-1 root. ## Category @@ -87,10 +91,12 @@ unambiguously source-aware. - adr-078 - adr-089 - adr-135 +- adr-146 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-137-decision-backed-federation-overrides.md b/decisions/decisions/adr-137-decision-backed-federation-overrides.md index 5566eca2..b102a0a8 100644 --- a/decisions/decisions/adr-137-decision-backed-federation-overrides.md +++ b/decisions/decisions/adr-137-decision-backed-federation-overrides.md @@ -7,6 +7,11 @@ type: decision ## Context +> **Amended by ADR-147.** Version 1 retains this ADR's direct mapping and chain +> rejection. Version 2 permits explicit ancestry-level override chains and +> diamond convergence while retaining every same-type, local-replacement, live +> local-Decision, no-precedence, and historical-provenance safeguard here. + ADR-136 rejects implicit child-first and parent-first precedence. A child still needs a controlled way to depart from an inherited standard without editing or silently hiding the parent corpus. A bare replacement mapping would identify @@ -87,11 +92,13 @@ its override state for auditability. - adr-080 - adr-089 - adr-134 -- adr-136 +- adr-146 +- adr-147 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-138-unified-source-aware-federated-read-model.md b/decisions/decisions/adr-138-unified-source-aware-federated-read-model.md index c27cc3be..4a19f4cf 100644 --- a/decisions/decisions/adr-138-unified-source-aware-federated-read-model.md +++ b/decisions/decisions/adr-138-unified-source-aware-federated-read-model.md @@ -7,6 +7,11 @@ type: decision ## Context +> **Amended by ADR-144 and ADR-148.** Manifest version 2 generalises the one +> writable child plus read-only parent into one writable root plus a verified +> read-only unique-source closure. One immutable closure generation still feeds +> every consumer; no surface may construct an independent overlay. + The released Rust engine assumes one corpus across validation rows, resolver entries, relationship endpoints, resolved artifacts, derived and persistent indexes, freshness tracking, MCP `GraphView`, routing, and Sentry enforcement. @@ -89,15 +94,17 @@ lost, and a parent subtree could be discovered twice. - adr-105 - adr-119 - adr-121 -- adr-133 - adr-134 - adr-135 -- adr-136 - adr-137 +- adr-144 +- adr-146 +- adr-148 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-139-source-neutral-federated-retrieval.md b/decisions/decisions/adr-139-source-neutral-federated-retrieval.md index 2d34c5b1..9197a618 100644 --- a/decisions/decisions/adr-139-source-neutral-federated-retrieval.md +++ b/decisions/decisions/adr-139-source-neutral-federated-retrieval.md @@ -79,8 +79,8 @@ comparable, and interleaving introduces an arbitrary quota policy. - adr-103 - adr-128 - adr-135 -- adr-136 - adr-138 +- adr-146 ## Related Designs diff --git a/decisions/decisions/adr-141-bounded-mcp-federation-provenance.md b/decisions/decisions/adr-141-bounded-mcp-federation-provenance.md index 681ce317..707226fc 100644 --- a/decisions/decisions/adr-141-bounded-mcp-federation-provenance.md +++ b/decisions/decisions/adr-141-bounded-mcp-federation-provenance.md @@ -7,6 +7,11 @@ type: decision ## Context +> **Amended by ADR-146 and ADR-147.** Version-2 ID arguments also accept global +> source qualification. Version-2 response provenance carries the complete +> ordered override chain atomically or returns ADR-128's hard budget error; +> audit records remain the smaller fixed identity and never copy mappings. + MCP clients need to distinguish local and inherited results and identify the verified parent pin. Adding federation-specific tools would expand the standing surface and force agents to choose between local and federated retrieval. @@ -90,14 +95,16 @@ Rejected. Federation does not get a context-flooding exception to ADR-128. - adr-127 - adr-128 - adr-135 -- adr-136 - adr-138 - adr-139 - adr-140 +- adr-146 +- adr-147 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-142-inherited-export-composition.md b/decisions/decisions/adr-142-inherited-export-composition.md index c9cfc2b2..df8d405d 100644 --- a/decisions/decisions/adr-142-inherited-export-composition.md +++ b/decisions/decisions/adr-142-inherited-export-composition.md @@ -7,6 +7,10 @@ type: decision ## Context +> **Amended by ADR-147.** Version-2 composed exports retain the complete +> deterministically ordered override chain, including the additive `lineage` +> role. Version-1 direct-mapping provenance remains exact. + Downstream viewers, document stores, and graph consumers need the same corpus an agent and gate used. Exporting only the child by default would make a successful federated validation produce an incomplete external record. Flattening parent @@ -87,10 +91,12 @@ audit and historical reconstruction. - adr-135 - adr-137 - adr-138 +- adr-147 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-143-versioned-federated-generations.md b/decisions/decisions/adr-143-versioned-federated-generations.md index 6150d733..d108596d 100644 --- a/decisions/decisions/adr-143-versioned-federated-generations.md +++ b/decisions/decisions/adr-143-versioned-federated-generations.md @@ -49,7 +49,12 @@ prevent stale inherited governance from being served as current. ## Status -Accepted +Superseded + +Superseded by ADR-148 for manifest-version-2 closure generations. This ADR's +single-parent generation, verified reuse, fail-closed freshness, store-layout, +recency, and no-manifest behavior remain the exact version-1 compatibility +contract. ## Category @@ -88,10 +93,12 @@ parent corpus and would misstate provenance. - adr-119 - adr-135 - adr-138 +- adr-148 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Requirements diff --git a/decisions/decisions/adr-144-bounded-acyclic-corpus-federation-graph.md b/decisions/decisions/adr-144-bounded-acyclic-corpus-federation-graph.md new file mode 100644 index 00000000..a9ef1f6d --- /dev/null +++ b/decisions/decisions/adr-144-bounded-acyclic-corpus-federation-graph.md @@ -0,0 +1,184 @@ +--- +schema_version: 1 +id: RAC-KZN54DB1VNPB +type: decision +--- +# ADR-144: Compose Federation as a Bounded Acyclic Source Graph + +## Context + +ADR-133 deliberately bounded the first corpus-federation implementation to one +direct parent and rejected transitivity. That boundary let the source-aware +identity, read-model, retrieval, enforcement, cache, MCP, and export substrate +be built without pretending that directory concatenation was a graph model. + +The intended v0.29 capability is broader. A repository may need independent +security, engineering, regulatory, and product corpora, and those corpora may +share common ancestors. Requiring teams to manufacture one synthetic parent +would hide provenance and move composition policy outside AsDecided. Merely +changing the singular parent field into a list would be worse: it would leave +cycles, diamonds, divergent pins, traversal order, resource exhaustion, and +the write boundary undefined. + +## Decision + +Manifest version 2 composes a rooted, bounded, directed acyclic graph of corpus +sources. + +- The invocation repository is the graph root and the only writable node. + Every other node is inherited and read-only relative to that root, including + a node that is local when inspected in its own repository. +- Every node may declare between one and 32 direct parents in its own version-2 + manifest. The engine recursively verifies those already-materialised parents + before constructing any effective corpus. +- Logical node identity is the explicit `corpus.source`. Every physical edge is + verified independently. A source reached through several branches is one + logical node only when every route verifies the same canonical version-2 node + digest. The engine retains every verified physical root as read-only. +- Reaching the same source with a different canonical node digest is a + deterministic divergent-pin error. Reaching a source already on the active + ancestry stack is a cycle error, even when its pin matches. Cycle diagnosis + takes precedence over divergent-pin diagnosis for an active-stack revisit. +- Two declarations of the same source in one manifest are an error even when + their pins match. Diamond deduplication applies across independently verified + ancestry branches, not to redundant sibling declarations. +- Manifest order has no semantic meaning and grants no precedence. Discovery, + validation, findings, catalog construction, and output use fixed bytewise + source-aware ordering. Permuting the root manifest's direct-parent list + therefore produces byte-identical public output. Permuting an inherited + node's list requires repinning because that node's exact manifest bytes are + authenticated; after bottom-up repinning it produces the same effective + identities, relationships, ranking, and findings, while pin and generation + provenance change by design. +- Nothing is partially overlaid. A cycle, divergent pin, invalid edge, invalid + node, or exceeded bound blocks the complete root composition. + +Version 2 has fixed, versioned safety ceilings. Exactly-at-limit succeeds and +limit-plus-one fails with `corpus-federation-limit-exceeded`, naming the +dimension, limit, and observed value. Limits are not machine-adaptive and the +engine never truncates a closure to fit: + +- 1 MiB for each manifest and governing config; +- 64 bytes for an edge alias and 255 bytes for a source identity; +- 4,096 bytes, 64 components, and 255 bytes per component for each `root` or + `corpus` path; +- 32 YAML levels and 16,384 YAML nodes per manifest or governing config; +- 32 direct-parent declarations per manifest; +- 16 inheritance edges of maximum depth; +- 256 unique inherited source nodes, excluding the root; +- 1,024 declared inheritance edges and 4,096 override declarations; +- 50,000 unique inherited Markdown files, with 16 MiB per file; +- 256 MiB of unique inherited captured bytes; +- 512 MiB of physical verification work across distinct canonical roots; and +- 200,000 visited filesystem entries. + +Counts for logical nodes, files, and logical bytes occur after verified diamond +deduplication. Every declared edge counts, and every distinct physical route is +verified and charged to the physical-work budget. Implementations store unique +nodes and edges rather than enumerating every possible diamond route. + +The 256 MiB logical-byte count is the sum of each unique inherited node's exact +config bytes, present manifest bytes, and owned Markdown bytes; root bytes are +excluded and override declarations are already counted inside manifest bytes. +The 512 MiB physical-work count charges those same bytes once for each distinct +canonical `(materialisation root, corpus root)` capture, including a route that +later deduplicates or fails its pin. An identical canonical route is captured +and charged once. Edge and override declarations are counted before logical +node deduplication across distinct physical manifests; the root manifest is +counted once. + +A visited entry is every directory entry examined below an inherited +materialisation or corpus root during config/manifest discovery, exclusion +discovery, and corpus walking, including ignored, excluded, rejected, and +eventually deduplicated entries. The root-local walk is not charged. Every +counter stops as soon as it reaches `limit + 1` and reports that saturated +value as `observed`; it does not finish an unbounded scan merely to report a +larger total. + +This decision supersedes ADR-133 in full. ADR-133 correctly bounded the first +implementation; it no longer governs a version-2 graph composition. ADR-138's +one writable child plus read-only parent model becomes one writable root plus a +read-only unique-source closure. ADR-139's source-neutral ranking and ADR-140's +root-code enforcement apply unchanged across that closure. + +This decision also clarifies ADR-135's distinct-source rule. Each logical graph +node has one explicit source distinct from every other logical node. Several +verified physical routes with the same source and canonical node digest are one +node, not several nodes allowed to share an identity; the same source with a +different digest remains an error. + +## Consequences + +Teams can compose independent policy corpora without manufacturing a synthetic +repository or accepting load-order semantics. Shared ancestors are represented +once in the logical catalog while every physical pin is still checked. Cycles, +split pins, and pathological inputs fail in a bounded and reproducible way. + +The engine must replace singular parent and read-only-root concepts with a +verified closure, source-contextual visibility, and a set of materialisation +roots. Verification becomes more expensive because every physical route is +checked before deduplication. That cost is deliberate: deduplication is not a +permission to trust an unchecked copy. + +## Status + +Accepted + +## Category + +Architecture + +## Supersedes + +- adr-133 + +## Alternatives Considered + +### Support several direct parents but continue rejecting transitivity + +Rejected. It still needs deterministic collision, ordering, and pin rules while +forcing every shared hierarchy to be flattened outside AsDecided. + +### Treat manifest order as parent precedence + +Rejected. Reordering reviewed YAML would silently change governance, make +diamonds path-dependent, and contradict the existing ban on implicit source +preference. + +### Deduplicate on source without comparing pins + +Rejected. Two branches could silently disagree about the bytes represented by +one global identity. + +### Leave limits to available machine memory + +Rejected. The same corpus could validate on one machine and exhaust another, +and a server could allocate unbounded state before producing a finding. + +## Related Decisions + +- adr-002 +- adr-018 +- adr-065 +- adr-080 +- adr-089 +- adr-103 +- adr-135 +- adr-138 +- adr-139 +- adr-140 + +## Related Designs + +- corpus-federation-mechanism +- corpus-federation-graph-composition + +## Related Requirements + +- parent-corpus-inheritance +- federated-resolution-provenance +- corpus-source-identity + +## Related Roadmaps + +- corpus-federation diff --git a/decisions/decisions/adr-145-versioned-multiple-parent-federation-manifest.md b/decisions/decisions/adr-145-versioned-multiple-parent-federation-manifest.md new file mode 100644 index 00000000..19fdef0e --- /dev/null +++ b/decisions/decisions/adr-145-versioned-multiple-parent-federation-manifest.md @@ -0,0 +1,209 @@ +--- +schema_version: 1 +id: RAC-KZN54DB2M7FZ +type: decision +--- +# ADR-145: Declare Multiple Offline Parents Through a Versioned Federation Manifest + +## Context + +ADR-134 fixes the secure first-increment carrier: one parent mapping in +`.decided/corpus.md`, local materialised bytes only, strict containment, source +verification, and a version-1 digest over config and artifact bytes. Its digest +intentionally excludes the operational manifest because transitive inheritance +is rejected. Reusing that pin for a graph would allow a parent's outgoing +topology and nested pins to change without changing the digest recorded by its +child. + +Multiple and transitive parents therefore need an explicit new manifest and +digest mode. Existing version-1 repositories must not be reinterpreted. + +## Decision + +The fixed manifest remains `.decided/corpus.md`, with the exact lowercase +headings `## inherits` and `## overrides`. Manifest version 2 declares an +unordered `parents` sequence in the single fenced YAML mapping under +`## inherits`: + +```yaml +version: 2 +parents: + - alias: standards + source: acme/standards + root: vendor/standards + corpus: decisions + digest: sha256-v2:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + - alias: security + source: acme/security + root: vendor/security + corpus: decisions + digest: sha256-v2:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 +``` + +The sequence contains between one and 32 records. Each record has exactly +`alias`, `source`, `root`, `corpus`, and `digest`; unknown fields, duplicate +YAML keys, merge keys, duplicate aliases, and duplicate direct sources are +errors. Alias syntax is +`^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$`, with the version-2 length bound from +ADR-144. A version-2 path is POSIX-relative UTF-8 and rejects absolute forms, +backslashes, drive or UNC prefixes, empty segments, `.`, and `..`. + +The `version` in `## inherits` selects the complete manifest's semantic mode. +The `## overrides` section is optional; when present, its single fenced mapping +must carry the same `version`. Mixed section versions and `## overrides` +without a valid `## inherits` section are errors. Version 2 restricts both +operational mappings and every governing config parsed during graph capture to +YAML mappings, sequences, and scalar values with at most 32 levels and 16,384 +nodes. Anchors, aliases, custom tags, and merge keys are rejected before +semantic construction. Each mapping, sequence, mapping key scalar, and value +scalar counts as one node in the event stream before construction. These +restrictions do not alter no-manifest or standalone version-1 parsing. + +Each edge is relative to the repository root of the corpus that declares it. +Its materialisation root must be a strict canonical descendant of that root. +The materialisation and corpus roots must be real contained directories; the +governing config, present manifest, and discovered Markdown artifacts must be +real contained regular files. Symlinks and Windows reparse points are rejected. +Each `root` or `corpus` value is at most 4,096 UTF-8 bytes, 64 components, and +255 UTF-8 bytes per component. Recursion preserves ultimate containment inside +the invocation repository. Sibling physical roots may not overlap; nesting is +valid only along the declared ancestry edge or when two independently verified +routes are the same deduplicated source and pin. + +An inherited regular file with more than one hard link is rejected. Capture +must also reject bind-mount, mount-point, volume, junction, or reparse-boundary +crossing between a declaring repository root and any inherited root or file. +When the platform cannot supply stable file identity, link-count, volume or +mount identity, and reparse metadata, version-2 verification fails with +`corpus-federation-unsupported-filesystem` rather than weakening the read-only +boundary. + +AsDecided performs no clone, fetch, pull, refresh, registry lookup, or other +network operation. Updating bytes and pins remains an explicit Git operation +outside the engine. + +Version-2 edge pins use a fixed SHA-256 byte contract: + +```text +"asdecided-corpus-digest-v2\0" +frame(0x01, source UTF-8) +frame(0x02, exact governing .decided/config.yaml bytes) +frame(0x03, one byte: 0x00 when corpus.md is absent, 0x01 when present) +frame(0x04, exact .decided/corpus.md bytes) # only when present +for each owned Markdown file in relative-path byte order: + frame(0x05, corpus-relative POSIX UTF-8 path) + frame(0x06, exact file bytes) +``` + +`frame` is the one-byte tag, unsigned 64-bit big-endian payload length, and +exact payload bytes. The owned snapshot excludes `.decided/` and every direct +parent materialisation subtree declared by the captured manifest. Checkout +path, timestamp, filesystem order, newline form, Unicode form, YAML meaning, +and Markdown meaning never enter the digest. + +The manifest-presence record prevents adding or removing topology without a +repin. Because exact nested manifest bytes contain descendant pins, direct pins +form a Merkle-like commitment to the closure without hashing descendants into +the same node recursively. + +Verification reads and bounds the target config and manifest, parses only the +strict declarations needed to establish local-walk exclusions, captures the +owned bytes, and verifies the incoming pin before following any child path +named by that manifest. Unverified manifest bytes never direct recursive +filesystem traversal. Every distinct canonical physical capture follows and +verifies its own child edges even when its source and node digest later +deduplicate; only the same canonical capture may reuse a completed route result. +Digesting, parsing, composition, and serving consume one captured byte snapshot +rather than reopening mutable files. + +The operator surface is +`decided corpus digest --version 2 --root --corpus ` +and prints `sha256-v2:` plus 64 lowercase hexadecimal characters. The current +command without `--version 2`, the `sha256:` prefix, the version-1 domain and +framing, its known vector, its stable findings, and valid version-1 output +remain byte-for-byte unchanged. + +A version-1 root retains exactly one direct leaf parent, version-1 collision +and override rules, and its current transitivity failure. A version-2 edge may +target a corpus whose own manifest is absent, version 1, or version 2. A nested +version-1 node applies its existing one-parent/leaf-only rules in its own +context: its authored references, collision checks, alias qualification, and +override validation use the exact version-1 rules. Its resulting effective +projection is then one input to the enclosing version-2 graph. A valid nested +version-1 override is normalized to its resolved source-aware target, local +replacement, and local rationale keys, so a version-2 ancestor may explicitly +chain from that replacement without reinterpreting the version-1 node. During +a version-2 root composition, every captured node also receives a canonical +version-2 node digest for graph deduplication and provenance; a legacy +version-1 edge pin is still checked and retained as edge provenance. + +This decision explicitly amends ADR-134 only where ADR-134 fixes one mapping +and a version-1 digest that omits topology. ADR-134's materialised-only, +offline, containment, exact-byte, source-verification, and pre-overlay failure +rules remain authoritative. + +## Consequences + +A direct pin now commits to the target's local content and its declared +outgoing topology, so bottom-up repinning is explicit and reviewable. V1 users +receive no semantic migration; graph behavior is entered only by authoring a +v2 manifest and v2 pins. + +Operators must calculate new pins when converting to v2 or changing any nested +manifest byte, including comments and whitespace. This is intentionally +stricter than semantic YAML hashing and keeps verification portable. + +## Status + +Accepted + +## Category + +Architecture + +## Alternatives Considered + +### Put several mappings in a version-1 YAML list + +Rejected. Older engines already reject that shape, and silently assigning new +meaning to it would break the recorded version-1 failure contract. + +### Keep digest v1 and verify nested manifests separately + +Rejected. The child pin would not authenticate which descendant sources and +pins its parent declared. + +### Hash the fully expanded closure into every node + +Rejected. It creates redundant work and awkward self-reference. Exact manifest +bytes already commit to the recursively verified descendant pins. + +### Fetch missing parents from `source` + +Rejected. `source` is stable identity, not a locator, and federation remains an +offline function of reviewed materialised bytes. + +## Related Decisions + +- adr-002 +- adr-018 +- adr-065 +- adr-080 +- adr-089 +- adr-134 +- adr-135 +- adr-144 + +## Related Designs + +- corpus-federation-mechanism +- corpus-federation-graph-composition + +## Related Requirements + +- parent-corpus-inheritance +- corpus-source-identity + +## Related Roadmaps + +- corpus-federation diff --git a/decisions/decisions/adr-146-global-source-qualified-federated-resolution.md b/decisions/decisions/adr-146-global-source-qualified-federated-resolution.md new file mode 100644 index 00000000..837480dd --- /dev/null +++ b/decisions/decisions/adr-146-global-source-qualified-federated-resolution.md @@ -0,0 +1,171 @@ +--- +schema_version: 1 +id: RAC-KZN54DB3V0ZC +type: decision +--- +# ADR-146: Resolve Federated Artifacts by Global Source Identity Without Precedence + +## Context + +ADR-136 uses a child-local parent alias for qualified references and rejects a +canonical-id collision across the two first-increment layers. A source graph +has no single parent alias table, and the same stable artifact id may +legitimately occur in independent source namespaces. Flattening every nested +alias into one global map would let an unrelated sibling change the meaning of +an immutable parent's relationship. Treating traversal order as precedence +would make that meaning depend on YAML order. + +ADR-135 already establishes `(corpus.source, canonical_id)` as stable global +identity. Graph resolution should expose that identity directly while retaining +short aliases as local authoring conveniences. + +## Decision + +Manifest version 2 uses `corpus.source::canonical-id` as the globally stable +qualified reference form: + +```text +acme/standards::STD-KWJ4VMKVSS65 +``` + +- The qualifier is an exact configured `corpus.source` visible from the + reference's source context. It works for the root and every inherited node. +- The right-hand side is a canonical artifact id only. Filename, title, legacy, + and other aliases remain invalid after `::`. +- Global qualification returns the retained record owned by that exact source. + It does not follow an override redirect and therefore remains a reliable + historical lookup. +- A direct edge alias remains accepted as `alias::canonical-id`, but only in the + lexical context of the corpus that declares that edge. Aliases do not leak + upward, become global identity, or appear as persisted/exported keys. Public + CLI and MCP lookup uses the root manifest's alias table. Alias qualification + selects the same source-owned historical record as global qualification and + never follows an override. +- Version-2 sources contain `/` and aliases do not, so the two qualifier forms + are syntactically distinct. Alias validation is shared by manifest parsing + and composition. + +Resolution is source-contextual within the one central graph model: + +1. A root-authored reference and a public root lookup see the root's effective + closure. +2. A reference authored by source A sees A and the closure A declares, not + unrelated sibling branches later added above A. +3. A's direct aliases are resolved with `(A source, alias)`. +4. A catalog relationship endpoint retains the authored token and a sorted, + non-empty `historical_candidates` set. A qualified or uniquely resolved + token has one candidate. An unqualified token whose several candidates all + converge has every original candidate in `(source, canonical_id)` order and + one separate `effective_terminal`. An ancestor may change only that terminal + in its effective graph; it never invents one preferred historical key. + +The engine must not construct independent per-source overlays to implement +this rule. The verified closure owns one source-aware catalog, visibility +relation, alias-edge table, and resolver; source context is an input to that +resolver. + +In version 2, equal canonical ids in distinct sources are valid catalog +records. An unqualified reference resolves only when its visible effective +candidates reduce to exactly one `ArtifactKey`, including when every candidate +explicitly redirects to the same terminal override. Otherwise that reference +is a deterministic sourced ambiguity. The mere presence of unused equal ids +does not invalidate composition. Duplicate canonical ids within one source +remain invalid. + +When a version-2 closure contains a version-1 node, references authored by that +node are first resolved and validated under its exact version-1 local/parent +rules. The enclosing version-2 model retains those resolved keys and may apply +only explicit ancestor redirects. Global source qualification and legal +cross-source equal ids apply to version-2 root/public lookup over the resulting +catalog; they do not retroactively make a version-1-local collision valid. + +No source, graph depth, local layer, parent layer, direct edge, or manifest +position receives implicit precedence. Deterministic tie ordering is +`(source, relative_path)` after the existing source-neutral ranking contract; +it never converts an identity ambiguity into a winner. + +Version-1 roots retain ADR-136's exact alias qualification, cross-layer +canonical-collision error, stable findings, and output bytes. Version 2 is the +only mode that enables global source qualification and legal cross-source equal +ids. + +This decision supersedes ADR-136. It preserves ADR-136's prohibition on +implicit precedence and its canonical-ID-only qualified suffix, but replaces +alias-only qualification with stable source qualification plus scoped alias +convenience, and replaces composition-time cross-source canonical collision +with deterministic unqualified ambiguity. It amends ADR-141 only where that +decision names `alias::canonical-id` as the MCP-qualified form: the six-tool +surface and bounded provenance rules remain unchanged, while version-2 ID +arguments also accept the global source-qualified form. + +## Consequences + +Every durable reference can name the same artifact independently of which path +or alias reached it. Adding an unrelated sibling cannot reinterpret a parent's +authored references, while root-authored references still see the complete +root policy closure. + +Global source names are longer than local aliases, and a user must qualify an +ambiguous bare id. That explicitness is preferable to silent source order. The +resolver and relationship store must retain source context and separate +historical endpoints from effective redirected endpoints. + +## Status + +Accepted + +## Category + +Architecture + +## Supersedes + +- adr-136 + +## Alternatives Considered + +### Export aliases as global qualifiers + +Rejected. Aliases are mutable and edge-local; the same spelling may correctly +name different sources in different manifests. + +### Keep cross-source canonical-id collisions as composition errors + +Rejected for v2. The source namespace already distinguishes the records, and +unqualified ambiguity is sufficient without preventing qualified use. + +### Let the nearest or root-local source win + +Rejected. It hides governance based on graph shape and contradicts the +source-neutral resolution contract. + +### Resolve all authored references against the final flat root closure + +Rejected. Adding sibling B could make an unchanged relationship inside A +ambiguous even though A cannot see or edit B in its own composition. + +## Related Decisions + +- adr-016 +- adr-026 +- adr-078 +- adr-089 +- adr-135 +- adr-139 +- adr-141 +- adr-144 +- adr-145 + +## Related Designs + +- corpus-federation-mechanism +- corpus-federation-graph-composition + +## Related Requirements + +- corpus-source-identity +- federated-resolution-provenance + +## Related Roadmaps + +- corpus-federation diff --git a/decisions/decisions/adr-147-decision-backed-federated-override-chains.md b/decisions/decisions/adr-147-decision-backed-federated-override-chains.md new file mode 100644 index 00000000..ab484ba9 --- /dev/null +++ b/decisions/decisions/adr-147-decision-backed-federated-override-chains.md @@ -0,0 +1,189 @@ +--- +schema_version: 1 +id: RAC-KZN54DB4QY0R +type: decision +--- +# ADR-147: Permit Decision-Backed Override Chains Across the Federation Graph + +## Context + +ADR-137 requires every override to name one inherited target, one same-type +local replacement, and one live local Decision rationale. It rejects chains +because the first increment has only a child and one parent. In a transitive +graph, an intermediate corpus may make a legitimate recorded exception and a +root corpus may later replace that exception with its own. A diamond may also +bring the same shared ancestor through branches with different explicit +exceptions. + +Flattening those mappings or choosing one branch by traversal order would lose +the decisions that explain the policy lineage. Rejecting every chain would make +transitive federation unusable for controlled local adaptation. + +## Decision + +Version 2 permits explicit, Decision-backed override chains across ancestry +levels. The exact `## overrides` section contains one fenced YAML mapping: + +```yaml +version: 2 +items: + - target: acme/standards::STD-KWJ4VMKVSS65 + with: APP-KWJ9ABCD1234 + rationale: APP-KWJ9D3C1S10N +``` + +Every entry keeps the first-increment safeguards: + +- `target` is a globally source-qualified canonical id in the declaring + corpus's inherited effective view. An alias, unqualified id, local artifact, + or unreachable sibling is invalid. +- `with` is one canonical artifact id local to the declaring corpus and of the + same type as the target. Parent-to-parent or inherited replacement remains + invalid. +- `rationale` is one live Decision local to the declaring corpus. An inherited, + ambiguous, or retired rationale remains invalid. +- A manifest may name a target at most once. Multiple targets may deliberately + converge on the same local replacement. Mapping order has no meaning. + +The version-2 override mapping is valid only in a manifest whose `## inherits` +mapping is also version 2. A present overrides mapping must match the inherits +version; mixed versions and overrides without inheritance are rejected under +ADR-145. + +Composition evaluates nodes bottom-up. A node unions the effective projections +of its direct parents, deduplicates identical keys, and then applies only the +overrides declared by that node. An exception declared in branch A therefore +does not silently suppress the same shared ancestor still inherited through +sibling B. + +The complete set of mappings forms a source-aware override graph. Chains such +as `A -> B -> C` are valid when each hop is declared by the corpus that owns its +local replacement. Same-manifest indirect chains, type changes, inherited +replacements, cycles, and order-dependent mappings are errors. Defensive cycle +detection remains mandatory even though the inherited-to-local rule normally +makes a cycle impossible. + +A shared historical key may acquire different effective branch targets. At a +join, that is `corpus-federation-override-divergence` and blocks the root +effective composition unless the joining corpus explicitly maps every live +branch target to one local same-type replacement. A fork is valid only when all +paths explicitly reconverge on one terminal `ArtifactKey`; traversal order +never selects a terminal. + +The catalog retains every original, intermediate, terminal, mapping-owner, and +rationale record. Global source-qualified lookup always retrieves the named +historical record. In the root effective view, nonterminal artifacts do not +contribute search rows, graph popularity, scope, routing, or enforcement; each +nonterminal bare canonical id redirects to the unique terminal. Historical +relationships retain the authored token and complete sorted historical +candidate set defined by ADR-146, while effective relationships carry the one +terminal or fail as ambiguous. + +For a returned or exported historical artifact, per-artifact provenance +contains every mapping edge on every compiled origin-to-terminal path that +contains that artifact key. For a returned or exported effective terminal, it +contains every mapping edge whose compiled terminal is that key. The union of +per-artifact provenance in a default catalog export therefore contains the +complete mapping table; there is no standalone state-bearing mapping row. Each +per-artifact set includes owner source, target, replacement, rationale, and +state for every hop in a deterministic total order. `state` is `overridden` +when the carrying artifact is that hop's target, `replacement` when it is that +hop's direct replacement, and `lineage` when the hop is included only to make +the longer path complete. An intermediate artifact may therefore have +replacement and overridden entries. The additive `lineage` value is +version-2-only; existing version-1 values and output remain exact. +Owners use the closure's parents-before-child, source-lexicographic Kahn rank. +Mapping edges then sort by `(owner rank, owner source, target source, target +canonical id, replacement source, replacement canonical id, rationale source, +rationale canonical id)`, all as UTF-8 byte order. Override-chain provenance is +atomic under ADR-128: optional content and list entries are reduced first, and +if the complete set still cannot fit the response fails with +`response_budget_exceeded`. ADR-127 audit records remain intentionally smaller +and never copy override mappings. + +Version-1 roots retain ADR-137's direct mapping, local replacement, chain +rejection, and exact output behavior. Within a version-2 closure, a valid nested +version-1 override remains valid under those rules and is normalized as one +source-aware graph hop. A version-2 ancestor may target its effective local +replacement and thereby extend the chain; the original version-1 record, edge +pin, mapping spelling, and rationale remain attributable. + +This decision amends ADR-137 only where it rejects chained mappings. Every hop +remains explicit, same-type, local-replacement, and backed by a live local +Decision. It also amends ADR-142 so composed exports retain complete ordered +chain provenance rather than only one direct mapping and add the `lineage` +role where a carrying artifact is not a hop's direct operand. It amends +ADR-141's MCP response provenance for version 2 to carry that same complete +ordered chain and to fail with ADR-128's hard budget error rather than emit a +partial chain; the six-tool surface remains unchanged. ADR-141's prohibition +on copying mappings into audit records remains unchanged. + +## Consequences + +An organisation can evolve policy through several corpus levels without +erasing the exceptions and Decisions that led to the root's effective rule. +Diamonds with conflicting policy cannot silently choose a branch; the joining +corpus must record a deliberate reconciliation. + +Override validation and provenance become graph operations rather than one +parent-to-child lookup. Catalog and effective relationship projections must be +separate, and tight MCP budgets may return an explicit budget error for a long +chain rather than an incomplete explanation. + +## Status + +Accepted + +## Category + +Architecture + +## Alternatives Considered + +### Continue rejecting all chains + +Rejected. A root could not explicitly replace an intermediate corpus's valid +exception without copying or deleting its history. + +### Let the closest descendant override win + +Rejected. Graph distance is implicit precedence and does not reconcile diamond +branches. + +### Permit an override to select another inherited artifact + +Rejected. That is parent-to-parent precedence. Each exception must remain an +owned local policy choice with a local Decision rationale. + +### Keep only the terminal mapping in provenance + +Rejected. It would remove the intermediate decisions needed to audit why the +effective policy changed. + +## Related Decisions + +- adr-016 +- adr-026 +- adr-065 +- adr-080 +- adr-127 +- adr-128 +- adr-137 +- adr-141 +- adr-142 +- adr-144 +- adr-146 + +## Related Designs + +- corpus-federation-mechanism +- corpus-federation-graph-composition + +## Related Requirements + +- federated-resolution-provenance +- parent-corpus-inheritance + +## Related Roadmaps + +- corpus-federation diff --git a/decisions/decisions/adr-148-closure-wide-federated-serving-generations.md b/decisions/decisions/adr-148-closure-wide-federated-serving-generations.md new file mode 100644 index 00000000..21492715 --- /dev/null +++ b/decisions/decisions/adr-148-closure-wide-federated-serving-generations.md @@ -0,0 +1,253 @@ +--- +schema_version: 1 +id: RAC-KZN54DB55X9R +type: decision +--- +# ADR-148: Key Serving State to the Entire Federated Closure + +## Context + +ADR-143 correctly requires source-aware store state, re-verification before +reuse, fail-closed freshness, and no borrowed child Git recency. Its generation +formula and watcher topology enumerate one child, one parent, two configs, two +roots, and one manifest. Repeating that singular structure for several parents +would miss transitive manifests, duplicate-route verification, graph edges, +override chains, and topology changes. + +A server must never keep exposing the last valid subset after one leaf changes +or becomes unverifiable. Cold, resident, persistent-store, cache-disabled, and +event-driven paths need one closure capture and one composition contract. + +## Decision + +Version-2 federation introduces an immutable `VerifiedFederation` closure that +owns the exact bytes and verified topology used by every derived model. It +contains: + +- the root source, governing config, exact manifest, and root-local snapshot; +- every unique node sorted by `(source, canonical version-2 node digest)`; +- every declared edge sorted by owner source, target source, digest, alias, + root, and corpus; +- every canonical physical materialisation and corpus root, including all + verified routes to a deduplicated diamond node; +- exact config, manifest-presence, manifest, and owned-artifact byte buffers; +- source visibility and edge-local alias tables; +- parsed override declarations and the compiled terminal/chain graph; and +- the fixed graph-contract version and ADR-144 limit values. + +Digest verification, parsing, node validation, relationship resolution, +composition, indexing, persistence, and serving consume those captured bytes. +They do not reopen inherited public paths. No-follow opens are used where +available; portable pre/open/post file-identity checks fail with a stable +snapshot-changed error when bytes or filesystem shape change during capture. + +The canonical logical generation is SHA-256 over the raw domain literal +`asdecided-federation-generation-v3\0` followed by frames. A frame is one tag +byte, an unsigned 64-bit big-endian payload length, and the exact payload. Its +canonical text form is `sha256-v3:` plus 64 lowercase hexadecimal characters. +Strings are exact UTF-8; stable paths are POSIX-relative UTF-8. Tables and +snapshots use the following closed byte contract: + +```text +frame(0x01, "corpus-federation-graph/v2") +frame(0x02, exact newline-delimited limit block defined below) +frame(0x03, one byte: 0x00 non-recursive or 0x01 recursive) +frame(0x04, root source) +frame(0x05, stable root-relative corpus path) +frame(0x06, exact root config bytes) +frame(0x07, one byte root-manifest presence) +frame(0x08, exact root manifest bytes) # only when present +for each root-owned file in relative-path byte order: + frame(0x09, relative path); frame(0x0a, exact bytes) +for each inherited node in (source, canonical v2 digest) order: + frame(0x10, source); frame(0x11, canonical sha256-v2 text) + frame(0x12, exact config); frame(0x13, one-byte manifest presence) + frame(0x14, exact manifest) # only when present + for each owned file in relative-path byte order: + frame(0x15, relative path); frame(0x16, exact bytes) + frame(0x17, empty) +for each edge in (owner source, target source, declared pin, alias, root, corpus): + frame(0x20, owner); frame(0x21, target); frame(0x22, declared pin text) + frame(0x23, alias); frame(0x24, root); frame(0x25, corpus) + frame(0x26, empty) +for each mapping edge in ADR-147's total order: + frame(0x30, owner source) + frame(0x31, target source); frame(0x32, target canonical id) + frame(0x33, replacement source); frame(0x34, replacement canonical id) + frame(0x35, rationale source); frame(0x36, rationale canonical id) + frame(0x37, empty) +for each terminal redirect in (target source, target canonical id) order: + frame(0x38, target source); frame(0x39, target canonical id) + frame(0x3a, terminal source); frame(0x3b, terminal canonical id) + frame(0x3c, empty) +frame(0x40, "artifact-spec-registry/v1") +frame(0x41, "relationship-description-registry/v1") +frame(0x42, "tokenizer-ranking-graph-floor/v1") +frame(0x43, "federation-derived/v3") +frame(0x44, "store/v3") +``` + +The exact 0x02 payload is ASCII, in the shown order, with a final newline: + +```text +manifest-bytes=1048576 +config-bytes=1048576 +alias-bytes=64 +source-bytes=255 +path-bytes=4096 +path-components=64 +path-component-bytes=255 +yaml-depth=32 +yaml-nodes=16384 +direct-parents=32 +depth=16 +unique-inherited-sources=256 +edges=1024 +overrides=4096 +inherited-files=50000 +file-bytes=16777216 +logical-bytes=268435456 +physical-bytes=536870912 +visited-entries=200000 +``` + +The mapping table is the normalized override graph; the terminal table commits +its compiled result. Aliases are already committed inside sorted edge rows. +Checkout paths, canonical materialisation spellings, event order, and +timestamps are excluded. The five literal subsystem fingerprints are the +closed answer-affecting constant set for the first graph implementation. A +change to artifact specifications, relationship descriptions, +tokenization/ranking including graph-floor or tie rules, derived schemas, or +store layout must bump its corresponding literal before state can be reused. + +Because exact manifests are inputs, reordering even the root parent list +changes the internal generation. It does not change public corpus output: all +semantic tables are independently sorted. Reordering an inherited manifest +also changes that node's pin and requires bottom-up repinning, so pin/generation +provenance changes even though the effective non-provenance answer remains the +same. + +Persistent federation state uses a new explicit `store/v3` layout and a new +derived-schema generation. V1 and V2 store segments are cache misses and are +never decoded as graph answers. Persisted keys and endpoints use stable +`ArtifactKey` and `ArtifactPath`; absolute locators remain runtime-only. + +Before every resident or store reuse, the engine verifies and captures the +complete closure, computes its generation, and validates that the stored model +contains exactly the expected sources, layers, inherited pins, root pin +absence, identities, redirect terminals, and relationship projections. A cache +option controls persistence, not federation verification or semantics. + +Freshness observes the root corpus/config/manifest and every node corpus root, +config, manifest path (including currently absent manifests), captured +artifact, and physical materialisation root. Create, content/identity/type +change, remove, and rename events on those inputs, plus watcher overflow or lost +coverage, trigger a full candidate recapture and recomposition in the first +graph implementation; source-blind delta mutation is not used across the +closure. If any edge, node, pin, bound, or override fails, the prior model +becomes inaccessible and the request fails closed. It is never served as stale +fallback. + +All consumers receive the same request-current closure generation: +validation, relationships, resolution, retrieval, scope routing, Gate, Sentry, +the six MCP tools, audit extraction, exports, and diagnostics do not assemble +independent parent lists. A configured graph has no cache-off, MCP, enforcement, +or export code path that silently degrades to root-local reads. + +Inherited records carry exactly stable source, inherited layer, and the verified +canonical pin for their owning node as fixed origin identity. Override +provenance carries the owner, target, replacement, rationale, and state fields +defined by ADR-147; ordinary artifact results need not enumerate ancestry +routes. Topology findings carry the canonical route contract from the graph +design. Recency is absent unless it can be derived from that source's own +materialisation history; root Git history is never borrowed. Override-chain +provenance remains atomic under response budgets, while audit remains bounded +to path, source, layer, and pin. + +No-manifest repositories retain the contemporaneous single-corpus path and +byte-identical output. A version-1 manifest retains its accepted observable +behavior and pin provenance; an internal store-layout miss is not an output +change. + +This decision supersedes ADR-143. It preserves versioned layouts, fail-closed +freshness, verified reuse, no-manifest parity, and the ban on borrowed root +recency, but replaces the two-root generation formula with a canonical +commitment to the complete verified closure. + +## Consequences + +A leaf change, topology edit, divergent pin, or newly invalid override cannot +leave one consumer or one cache tier serving a different corpus from another. +Two clones of the same graph share stable generation and output identities even +though their physical paths differ. + +Every request-current verification can be more expensive, and graph changes +initially force full recomposition rather than incremental deltas. Version-2 +federation also causes one deliberate store rebuild. These costs buy a clear +atomicity boundary; later acceleration must preserve this generation contract. + +## Status + +Accepted + +## Category + +Technical + +## Supersedes + +- adr-143 + +## Alternatives Considered + +### Hash only the root manifest and direct pins + +Rejected. It omits captured descendant state and cannot validate that a stored +model represents the same closure. + +### Keep one cache generation per parent and merge on read + +Rejected. Consumers could combine generations captured at different times and +recreate independent overlay semantics. + +### Keep serving the last valid graph after a leaf fails verification + +Rejected. The answer would claim current governance from bytes that no longer +match the reviewed pins. + +### Incrementally patch any changed graph node from the first release + +Rejected. Full closure recomposition is the safe initial boundary. A later +acceleration may be accepted only with cold/warm/delta byte parity. + +## Related Decisions + +- adr-080 +- adr-103 +- adr-104 +- adr-105 +- adr-112 +- adr-118 +- adr-119 +- adr-128 +- adr-135 +- adr-138 +- adr-141 +- adr-144 +- adr-145 +- adr-147 + +## Related Designs + +- corpus-federation-mechanism +- corpus-federation-graph-composition + +## Related Requirements + +- parent-corpus-inheritance +- federated-resolution-provenance +- corpus-source-identity + +## Related Roadmaps + +- corpus-federation diff --git a/decisions/decisions/adr-149-git-repository-truth-is-forge-agnostic.md b/decisions/decisions/adr-149-git-repository-truth-is-forge-agnostic.md new file mode 100644 index 00000000..d91394ef --- /dev/null +++ b/decisions/decisions/adr-149-git-repository-truth-is-forge-agnostic.md @@ -0,0 +1,125 @@ +--- +schema_version: 1 +id: RAC-KZ0F0RG3N5XT +type: decision +--- +# ADR-149: Git Repository Truth Is Forge-Agnostic + +## Status + +Accepted + +## Category + +Architecture + +## Context + +ADR-080 correctly rejected a database as a second source of truth, but tied the +remaining contract too narrowly to a Git host's branch named `main`. AsDecided +now supports local-only operation, shared servers, and pinned corpus federation. +The same repository may be hosted on GitHub, Cursor Origin, GitLab, Forgejo, a +private bare remote, or nowhere at all. + +Forge-specific repository, pull-request, and checks APIs are useful workflow +integrations. They are not corpus storage semantics. If the engine derives +identity, resolution, or authority from GitHub, a remote named `origin`, or a +branch named `main`, moving the exact same reviewed bytes to another standard +Git forge would change the product's answer. + +## Decision + +The authoritative AsDecided state is the reviewed corpus committed to its +owning Git repository. Git is the versioned system of record; no particular +forge, remote name, default branch spelling, or hosted API is part of corpus +identity or resolution. + +- Core read, validation, federation, retrieval, enforcement, export, cache, and + MCP behavior MUST derive from the selected local tree and explicit AsDecided + configuration. These paths MUST NOT require a `.git` directory or network. +- Teams choose the branch or protected reference that represents reviewed + truth. Documentation may use `main` in examples, but engine semantics MUST + NOT infer authority from that spelling. Commands comparing revisions accept + an explicit ref; a compatibility default is convenience, not identity. +- A remote is optional. If present, its name and URL are operational data. A + clone from Cursor Origin, GitHub, GitLab, Forgejo, another standard Git + server, or a local bare repository yields the same AsDecided answer from the + same bytes. +- Forge adapters MAY create repositories, push branches, open reviews, publish + checks, or format host-native output. They MUST remain optional integration + layers outside the deterministic corpus engine and MUST NOT become a second + source of truth. +- Shared-server freshness is deployment policy: the operator advances its + selected reviewed checkout. The server remains a read-only consumer and does + not make one forge or branch name canonical for every installation. +- Release hosting, issue links, and GitHub Actions in this repository remain + project delivery choices. Their presence does not make GitHub a runtime or + storage dependency of AsDecided corpora. + +This decision amends ADR-080's hard-coded Git-host `main` language while +preserving its central conclusion: Git-backed Markdown, not a database or +hosted AsDecided control plane, is authoritative. + +## Consequences + +Organizations can move or mirror a corpus between standard Git forges without +migration of its AsDecided identity. Origin-native repositories need no core +adapter for clone, pull, push, validation, federation, or MCP; only optional +Origin-native review and checks operations need an integration. + +Documentation and deployment templates must distinguish examples such as +`origin/main` from normative product semantics. Tests must prove version-2 +federation and its stable status output work in a plain materialised directory +without `.git` and without checkout paths entering identity. + +The trade-off is that AsDecided does not declare which branch is authoritative +for a team. Repository protections, review policy, and deployment configuration +make that choice, exactly as they do for application code. + +## Alternatives Considered + +### Make GitHub the canonical storage platform + +Rejected. It would couple deterministic local reads to one commercial forge, +exclude Origin-native and self-hosted repositories, and confuse optional review +automation with corpus truth. + +### Make Cursor Origin the new canonical storage platform + +Rejected for the same reason. Origin is a compatible standard Git forge and a +valuable optional integration, not a replacement control plane for AsDecided. + +### Treat a hosted AsDecided service as canonical + +Rejected. It recreates the second mutable representation and reconciliation +problem that ADR-080 ruled out. + +### Require the branch name `main` + +Rejected. Branch spelling is repository policy and carries no semantic content. +An explicit comparison or deployment ref is sufficient. + +## Related Decisions + +- adr-001 +- adr-002 +- adr-016 +- adr-018 +- adr-032 +- adr-055 +- adr-065 +- adr-080 +- adr-089 +- adr-098 +- adr-144 +- adr-145 + +## Related Roadmaps + +- corpus-federation +- lore-at-team-scale + +## Related Requirements + +- parent-corpus-inheritance +- federated-resolution-provenance diff --git a/decisions/designs/corpus-federation-graph-composition.md b/decisions/designs/corpus-federation-graph-composition.md new file mode 100644 index 00000000..5160cf47 --- /dev/null +++ b/decisions/designs/corpus-federation-graph-composition.md @@ -0,0 +1,590 @@ +--- +schema_version: 1 +id: RAC-KZN54DB6S476 +type: design +--- +# Design: Corpus Federation Graph Composition + +## Status + +Accepted + +This design extends the accepted one-parent foundation in +`corpus-federation-mechanism` without rewriting its history. Accepted ADR-144 +through ADR-148 authorize the version-2 graph implementation. ADR-133, ADR-136, +and ADR-143 remain historical version-1 records; their accepted successors +preserve that compatibility behavior while replacing their version-2 boundary. + +## Context + +The first federation increment establishes the hard substrate: stable +`corpus.source` identity, source-aware artifact and path keys, verified offline +materialisation, one composed read model, explicit overrides, source-neutral +ranking, inherited root-code enforcement, bounded MCP provenance, composed +exports, and versioned serving state. Its accepted topology is deliberately one +direct leaf parent. + +The broader use case needs independent corpora for concerns such as security, +engineering, regulation, and product policy. Those corpora may inherit shared +standards or each other. Flattening them into one synthetic repository hides +ownership and policy lineage. Concatenating several directory walks creates +order-dependent resolution, loses alias scope, double-counts diamonds, and +cannot authenticate transitive topology because digest v1 does not include the +parent manifest. + +The graph extension must remain AsDecided: deterministic, offline, Git-native, +reviewable, source-attributable, and focused on supplying decisions to coding +agents. It is not a generic knowledge graph, hosted control plane, registry, +network fetcher, vector platform, or probabilistic retrieval system. + +## User Need + +A root repository needs to inherit several independently owned corpora, follow +their pinned dependencies, cite any visible artifact, apply every effective +Decision to root code, and retain the complete history of explicit exceptions. +The same checked-out bytes must produce the same answer on every machine. + +An operator must be able to understand and update the graph using ordinary Git +diffs. A missing, stale, conflicting, cyclic, or oversized closure must fail +before any read or enforcement surface sees a partial result. + +## Design + +The graph is an explicit version-2 semantic mode built on the accepted +source-aware substrate. The following sections define its compatibility, +manifest, pin, topology, resolution, override, serving, and certification +contracts as one coherent design. + +## Compatibility Modes + +Federation behavior is selected by the operational manifest version. + +| Mode | Topology | Qualification | Collisions and overrides | Serving compatibility | +| --- | --- | --- | --- | --- | +| No manifest | One local corpus | Existing forms | Existing rules | Byte-identical single-corpus path | +| Manifest v1 | One direct leaf parent | Child-local `alias::canonical-id` | Cross-layer canonical collision; no chains | Existing v1 findings, digest, output, and pin provenance | +| Manifest v2 | Bounded acyclic source graph | Global `source::canonical-id`, plus scoped direct aliases | Cross-source equal ids are legal but unqualified-ambiguous; explicit chains may converge | Closure-wide generation and store v3 | + +V2 is an explicit semantic mode. It does not reinterpret a v1 root. A v2 edge +may target a corpus whose own manifest is absent, v1, or v2. A nested v1 node +keeps its one-parent/leaf-only relationship, collision, alias, and override +rules in its own context. Its validated effective projection and any valid v1 +override hop are then normalized to source-aware keys for the enclosing v2 +graph; a v2 ancestor may explicitly extend that hop. + +## Graph Model + +The runtime owns one immutable `VerifiedFederation`: + +```text +FederationRoot = (source, config, manifest, local snapshot, writable locator) +FederationNode = (source, canonical v2 digest, config, manifest, local snapshot) +FederationEdge = (owner source, alias, target source, root, corpus, declared pin) +ArtifactKey = (source, canonical_id) +ArtifactPath = (source, relative_path) +PhysicalRoute = (declaring root, canonical materialisation root, corpus root) +``` + +The root is the only writable node. Every non-root record is `inherited` +relative to the invocation, even when it is local in its own repository. Stable +public identity never contains a checkout path, materialisation route, or edge +alias. + +The logical graph is keyed by explicit `corpus.source`. Every physical edge is +verified before logical deduplication: + +- same source and same canonical v2 node digest: one logical node; +- same source and different canonical digest: divergent-pin error; +- source repeated on the active ancestry stack: cycle error; +- same direct source twice in one manifest: duplicate-parent error. + +All verified physical roots remain in the read-only set, including duplicate +routes to a deduplicated diamond node. Local corpus discovery excludes that +entire set, and every mutation/output guard rejects it. + +## Manifest Version 2 + +Inheritance remains in `.decided/corpus.md`, outside artifact discovery, +search, relationships, and exports. Headings are exact lowercase Markdown. + +````markdown +# Corpus + +## inherits + +```yaml +version: 2 +parents: + - alias: standards + source: acme/standards + root: vendor/standards + corpus: decisions + digest: sha256-v2:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + - alias: security + source: acme/security + root: vendor/security + corpus: decisions + digest: sha256-v2:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 +``` +```` + +The sequence is unordered and has one to 32 strict records. Unknown or duplicate +keys, YAML merge keys, duplicate aliases, duplicate direct sources, malformed +source identities, and out-of-bound values fail. The parser accepts no dormant +future fields. + +The `## inherits` mapping selects the whole manifest's semantic version. The +optional `## overrides` mapping must have the same version; a mixed-version pair +or overrides without valid inheritance fails. In v2, operational mappings and +every governing config are restricted to mappings, sequences, and scalars with +at most 32 levels and 16,384 nodes. Anchors, aliases, custom tags, and merge +keys are rejected before construction. V1 and no-manifest parsing remain exact. + +Edge paths are POSIX-relative UTF-8. Absolute, empty, `.`, `..`, backslash, +drive-prefix, UNC, symlink, and canonical-escape forms are rejected. A root is +relative to and strictly inside the repository that declares it. Therefore +every transitive route remains inside the invocation repository. Overlapping +sibling roots are rejected unless they are the same verified logical route; +legitimate nesting follows a declared ancestry edge. + +Each `root` or `corpus` value is bounded to 4,096 UTF-8 bytes, 64 components, +and 255 UTF-8 bytes per component. Materialisation and corpus roots are real +directories; configs, present manifests, and Markdown artifacts are real +regular files. Symlinks, Windows reparse points, files with more than one hard +link, and mount/volume/junction crossings are rejected. If the platform cannot +provide the required file identity, link, volume/mount, and reparse metadata, +v2 fails closed as an unsupported filesystem. + +No AsDecided command materialises or refreshes a source. Submodules and vendored +directories are equivalent local byte carriers. The operator updates them with +Git and explicitly updates pins. + +## Digest Version 2 + +V2 authenticates a node's source, governing config, exact manifest state, and +owned local Markdown snapshot: + +```text +domain = "asdecided-corpus-digest-v2\0" +frame(0x01, source UTF-8) +frame(0x02, exact .decided/config.yaml bytes) +frame(0x03, 0x00 for absent corpus.md or 0x01 for present corpus.md) +frame(0x04, exact .decided/corpus.md bytes) # present only +for file in owned Markdown sorted by relative UTF-8 path bytes: + frame(0x05, corpus-relative POSIX path) + frame(0x06, exact file bytes) +``` + +Every frame is one tag byte, an unsigned 64-bit big-endian byte length, and the +exact payload. Owned Markdown excludes `.decided/` and every declared direct +materialisation subtree. Nothing is normalized. The digest excludes checkout +path, timestamps, traversal order, YAML meaning, and Markdown meaning. + +The exact manifest bytes bind the outgoing edges and their pins. Those pins bind +descendant manifests in turn, producing a Merkle-like commitment without a +self-referential closure hash. Adding, removing, reordering, or commenting a +manifest requires a repin even though parent order does not change semantic +precedence. + +The existing v1 domain, framing, `sha256:` prefix, known vector, and operator +command remain exact. V2 uses: + +```text +decided corpus digest --version 2 --root --corpus +``` + +and emits `sha256-v2:`. + +## Secure Discovery and Verification + +The loader follows this fail-closed sequence: + +1. Capture and bound the root config and manifest. +2. Strictly parse direct declarations only far enough to establish lexical + local-walk exclusions. +3. Sort outgoing edges by `(source, digest, alias, root, corpus)`. +4. For each target, resolve containment without following an unverified nested + declaration; capture its bounded config, manifest state, and owned local + Markdown bytes. +5. Calculate and compare the incoming edge pin. +6. After the target pin succeeds, detect an active-stack source repeat as a + cycle; otherwise compare a completed source's canonical node digest for + diamond deduplication or divergent-pin failure. +7. For every previously unseen canonical physical capture, traverse and verify + the child paths named by that physical copy's manifest even when its logical + source and digest will deduplicate. An identical canonical physical capture + may reuse its already verified route result. +8. After all distinct physical routes pass, deduplicate logical nodes and + validate them bottom-up with source-lexicographic topological + tie-breaking. +9. Compile visibility, relationships, overrides, catalog, and effective views. +10. Publish the generation only after the entire closure passes. + +Unverified manifest bytes never direct recursive filesystem traversal. Digest, +parse, validation, composition, persistence, and serving consume the captured +buffers. Secure no-follow opens are preferred; portable pre/open/post identity +checks reject swaps or shape changes during capture. + +The implementation stores unique nodes and edges, not every diamond route. +Every graph topology or inherited-node blocker carries one lexicographically +minimal source route plus `route_count`, the exact number of verified physical +routes represented by that finding. `route_count` is at least one; other +finding families omit it. + +## Qualification and Visibility + +Global qualification is stable: + +```text +acme/standards::STD-KWJ4VMKVSS65 +``` + +The left side is an exact visible `corpus.source`; the right side must be a +canonical id. It selects the historical record in that source and never follows +an override. + +A direct alias such as `standards::STD-...` is a convenience scoped to the +manifest that declares it. Alias lookup is keyed by `(referrer source, alias)`. +Intermediate aliases do not leak to root callers; root public lookup uses the +root alias table. Persisted keys, export links, findings, and provenance use the +global source, never the alias. + +References resolve in the view rooted at their authoring source. Root-authored +references see the complete root closure. Source A's immutable references see A +and A's declared closure, not an unrelated sibling introduced by an ancestor. +The catalog endpoint stores the authored token plus a sorted non-empty set of +historical candidate `ArtifactKey`s. Qualified or unique tokens have one +candidate. A bare token with several candidates may resolve only when all have +one terminal; the catalog keeps every original candidate and the effective +projection stores that separate terminal. No arbitrary historical winner is +invented. A later ancestor override can change only the effective terminal. + +Equal canonical ids in different sources are legal. An unqualified reference +resolves only if its visible effective candidates reduce to one key. Otherwise +the reference is ambiguous and must be source-qualified or explicitly +converged. Duplicate ids within one source remain errors. No source, layer, +depth, or manifest position wins implicitly. + +## Override Graph + +V2 overrides are explicit and globally targeted: + +````markdown +## overrides + +```yaml +version: 2 +items: + - target: acme/standards::STD-KWJ4VMKVSS65 + with: APP-KWJ9ABCD1234 + rationale: APP-KWJ9D3C1S10N +``` +```` + +`target` must be inherited and visible from the declaring corpus. `with` must +be a same-type artifact local to that corpus. `rationale` must be a live local +Decision. All operands are canonical, a target appears once per manifest, and +mapping order has no meaning. The section version must equal the version under +`## inherits`. + +Each node first unions its direct parents' effective projections, then applies +its own mappings. A branch-local exception affects that branch only. All mapping +edges are retained with their owner and rationale. Chains across ancestry levels +are allowed; same-manifest indirect chains, inherited replacements, type +changes, and cycles fail. + +A shared ancestor may have divergent terminals in separate branches. The join +fails with `corpus-federation-override-divergence` unless the joining corpus +explicitly maps every live branch terminal to one local same-type replacement. +Only a unique terminal enters the root effective view. + +A valid nested-v1 override remains governed by v1 syntax and validation, then +becomes one source-aware hop in the v2 graph. Complete mapping provenance uses +the closure's parents-before-child, source-lexicographic Kahn owner rank and +then `(owner source, target key, replacement key, rationale key)` byte order. + +## Catalog and Effective Projections + +The composed model has explicit projections rather than one overloaded vector: + +- **Catalog:** every unique artifact and historical relationship endpoint, + including authored tokens, complete original candidate sets, overridden + history, and every rationale. +- **Effective:** root-local artifacts plus inherited terminal artifacts, with + relationship endpoints redirected through valid override chains. +- **Root local:** writable artifacts only, used by every mutation command and + root-owned review. + +Search, BM25 statistics, inbound graph counts, scope routing, grounding, Gate, +and Sentry use the effective projection. Qualified historical lookup and graph +inspection can select catalog history without leaking its edges into live +ranking. Viewer, documents, and graph exports include the unique catalog by +default; `--local-only` selects the root-local projection. + +Catalog order is `(source, relative_path, canonical_id)`. Ranking remains one +source-neutral deterministic BM25 and relationship index with no local quota or +source boost, the lexical graph-floor invariant, and `(source, relative_path)` +as the final tie order. Diamond rows and edges count once. Override +nonterminals contribute neither search rows nor live inbound popularity. + +## Validation and Finding Ownership + +Each unique node's local structural state is validated under its own config. +Its authored relationships resolve in that node's visibility context. Parent +`Applies To` syntax is validated there, while target existence and matching for +effective inherited Decisions are evaluated against the final root code tree. + +A node error blocks root composition through one deterministically selected, +sourced root-manifest finding; a diamond does not duplicate the same node error +per route. Parent warnings and review advisories remain owned by that source and +are not replayed in the root. Errors created only by combining branches—cycles, +divergent pins, ambiguities used by root artifacts, or override divergence—are +root composition findings. + +A failed descendant edge is attributed to the manifest source that declares it, +and the root blocker includes the source route. Remediation may tell the user +where to validate or repin but never instructs an AsDecided command to edit an +inherited path. + +## Routing, Enforcement, and Mutation + +Every effective inherited Decision participates in `decisions-for`, grounding, +MCP path lookup, Gate, Sentry, and `gate --code`. `Applies To` patterns and code +constraints match the invocation root's code, at any inheritance depth. A +terminal replacement governs; an override nonterminal does not. + +Sentry and full/diff code enumeration exclude every physical materialisation +and corpus root. Mutation commands receive only root-local items and reject all +direct, transitive, and duplicate-route roots, including nonexistent output +suffixes, symlinked ancestors, and lexical `..` forms. No enforcement or MCP +surface gains a local-only bypass. + +Review and advisory diagnostics remain root-subject views after central closure +verification, so inherited warnings are not multiplied. Explicitly physical +historical diagnostics may remain local when they cannot materialise a graph at +another revision; they are never enforcement paths. + +## MCP, Audit, and Exports + +The six MCP tools remain unchanged. Every request uses one request-current +verified generation for resolution, search, relationships, scope, summary, and +content. Cache-on, cache-off, resident, and store-hit behavior is identical. + +Artifact responses carry source and inherited/root layer; inherited records +also carry their owning-node pin, while root-local records omit `pin`. Complete +override-chain provenance is indivisible under hard budgets: optional content +and result-list entries reduce first, then the tool returns +`response_budget_exceeded` rather than an incomplete chain. Audit stays bounded +to returned path, source, layer, and optional inherited pin; it does not record +bodies, excerpts, topology, aliases, or mappings. Any closure preparation error +produces one audited error event with an empty returned list. + +Viewer, documents, and graph exports emit each diamond record once when source, +id, pin, path, and body agree. Any mismatch is an aggregation conflict. Graph +endpoints are source-aware. Every historical and terminal artifact and every +override hop remains attributable. Portal keys and routes use `(source, id)` so +three or more records may share a canonical id without collapsing. OKF and +generated agent rules remain root-local unless separately decided. + +## Generation, Store, and Freshness + +The closure generation is the exact SHA-256 `sha256-v3:` framed-byte stream in +ADR-148: domain, root snapshot, sorted node snapshots, sorted edges, the totally +ordered mapping table, compiled terminal redirects, fixed limits, recursive +mode, stable root-relative corpus path, and the five closed subsystem +fingerprints. It excludes physical checkout paths. + +Graph persistence uses `store/v3`; older stores are misses. Before any resident +or persistent reuse, the engine captures and verifies the full closure and +confirms the model's sources, pins, identity rows, terminals, and relationship +projections. Cache flags control persistence, not verification. + +Freshness observes every root, config, manifest path including absence, and +captured artifact. Create, content/identity/type change, remove, rename, watcher +overflow, or lost coverage performs a full candidate recomposition in the +initial graph implementation. Failure makes the previous model inaccessible; +stale fallback is prohibited. Inherited Git recency is absent unless derived +from that source's own materialisation history. + +## Deterministic Bounds + +| Dimension | Version-2 limit | +| --- | ---: | +| Manifest or config | 1 MiB each | +| Alias / source identity | 64 / 255 bytes | +| Root/corpus path | 4,096 bytes, 64 components, 255 bytes/component | +| YAML depth / nodes | 32 / 16,384 | +| Direct parents per manifest | 32 | +| Inheritance depth | 16 edges | +| Unique inherited sources | 256 | +| Declared edges | 1,024 | +| Override declarations | 4,096 | +| Unique inherited Markdown files | 50,000 | +| Individual inherited Markdown file | 16 MiB | +| Unique inherited captured bytes | 256 MiB | +| Physical verification bytes | 512 MiB | +| Visited filesystem entries | 200,000 | + +The exact limit succeeds; plus one fails without a partial overlay. Logical +bytes are unique inherited config, present-manifest, and owned-Markdown bytes +after verified diamond deduplication. Physical bytes charge those inputs once +per distinct canonical `(materialisation root, corpus root)` capture. Override +and edge declarations count across distinct physical manifests before logical +deduplication. Visited entries include ignored, excluded, rejected, and later +deduplicated entries examined below inherited roots. Counters stop and report +the saturated observed value `limit + 1`; every edge counts and every distinct +physical route is charged. + +## Acceptance Strategy + +The implementation is complete only when the following families are covered: + +- no-manifest byte goldens and the complete v1 suite remain unchanged; +- v2 one-parent parity, two and three direct parents, byte-identical root-list + permutations, and semantically identical nested permutations after repinning; +- deep transitivity, same-pin diamonds, divergent pins, self/two-node/long + cycles, and root-source recurrence; +- scoped aliases with repeated spellings, global transitive qualification, + canonical-only suffixes, equal ids across three sources, and unqualified + ambiguity; +- `A -> B -> C` overrides, same-id chains, divergent forks, explicit diamond + reconciliation, wrong types, nonlocal replacements, dead rationales, and + full provenance; +- original versus effective relationship endpoints, diamond graph counts, + source-neutral ranking, and large-parent hard negatives; +- inherited scope and enforcement from every depth against root code; +- all mutation commands and output targets against every materialisation route; +- v2 digest known vectors for manifest absence/presence, CRLF, comments, + ordering, config, path, and artifact byte changes; +- cold, warm, persistent, cache-disabled, and event-rebuilt byte parity, plus + old-store misses and stale-leaf refusal; +- six MCP tools, tight budgets, one error audit event, full-catalog exports, + root-only local exports, and source-aware Portal navigation; and +- Linux, macOS, and Windows containment and stable-order fixtures. + +Security fixtures also cover YAML depth/node/alias bombs, path component and +length boundaries, hard-link aliases, bind/mount or volume crossings, Windows +reparse points, and unsupported file-identity capabilities. + +Every deterministic bound has an at-limit and limit-plus-one fixture. A +many-diamond fixture proves work scales with nodes, edges, and verified physical +bytes rather than the number of possible routes. + +## Constraints + +- Materialised local bytes only; no network operation in any engine path. +- One writable root; inherited bytes are never mutated. +- One verified closure and one contextual source-aware resolver for every + consumer. +- No source, local-layer, graph-depth, or manifest-order precedence. +- Provenance survives every response, finding, audit identity, and export. +- Root enforcement cannot be bypassed with a local-only projection. +- No-manifest and v1 observable compatibility are load-bearing. + +## Non-Goals + +- Adjacent-checkout, absolute-path, URL, registry, or live-fetched parents. +- Automatic materialisation refresh or pin writing. +- Writes into any inherited source. +- Hosted federation, per-artifact ACLs, or a control plane. +- Embedding-based resolution, vector search, probabilistic source selection, or + source-biased ranking. +- A new MCP tool or an unbounded audit record. +- Cross-revision graph materialisation for Watchkeeper in this increment. +- Federation of OKF or generated agent-rule write projections without a + separate decision. + +## Rationale + +A verified source DAG is the smallest model that preserves independent corpus +ownership, shared ancestry, and explicit exception history without importing +load order. Exact manifest-bound pins make the topology reviewable in Git, and +source-contextual resolution prevents an unrelated sibling from changing an +immutable parent's authored meaning. Separate catalog and effective projections +preserve audit history without letting it govern live agent work. + +## Alternatives + +- **Publish one synthetic combined parent.** Rejected because it hides source + ownership, duplicates upstream policy, and moves conflict handling outside + the decision corpus. +- **Allow several direct parents but reject transitivity.** Rejected because it + still needs collision and order semantics while forcing shared hierarchies to + be flattened. +- **Use manifest order as precedence.** Rejected because a YAML reorder would + silently change governance. +- **Resolve through a hosted graph service.** Rejected because federation must + remain offline, materialised, Git-native, and available to every user. + +## Accessibility + +Human output must name source identities, pins, and source routes in text rather +than relying on colour, indentation, or graph visualization alone. JSON and +SARIF retain equivalent structured identity. Cycle and divergent-pin findings +show one bounded canonical route and a route count so they remain usable in +terminals, screen readers, and CI annotations without dumping every diamond +path. + +## Style Guidance + +Examples use exact lowercase manifest headings, lower-case slash-namespaced +sources, POSIX-relative paths, and complete pins. Documentation calls aliases +local conveniences and `corpus.source` durable identity. It describes catalog +history separately from the effective governing view and never presents +materialisation refresh as an AsDecided network operation. + +## Open Questions + +None within this accepted design. Changes to topology limits, precedence, +network materialisation, inherited writes, qualification, or override ownership +require a new or superseding decision rather than an implementation default. + +## Ratification Record + +ADR-144 through ADR-148 are Accepted and authorize engine implementation within +this design. ADR-133, ADR-136, and ADR-143 are Superseded for version 2; the +narrower amendments to ADR-134, ADR-137, ADR-138, ADR-141, and ADR-142 are +recorded without erasing their retained rules; and ADR-135 carries ADR-144's +same-source/same-digest physical-route clarification. The accepted +`corpus-source-identity` requirement references the new authority set, requires +one explicit source per unique logical node, and permits only same-digest +physical diamond reuse. The two implementation requirements are Accepted and +the federation roadmap is Achieved by the v0.29.0 certification evidence. + +## Related Decisions + +- adr-002 +- adr-018 +- adr-026 +- adr-065 +- adr-080 +- adr-089 +- adr-103 +- adr-123 +- adr-127 +- adr-128 +- adr-134 +- adr-135 +- adr-137 +- adr-138 +- adr-139 +- adr-140 +- adr-141 +- adr-142 +- adr-144 +- adr-145 +- adr-146 +- adr-147 +- adr-148 + +## Related Requirements + +- parent-corpus-inheritance +- federated-resolution-provenance +- corpus-source-identity +- deterministic-decision-code-enforcement + +## Related Roadmaps + +- corpus-federation +- corpus-sync diff --git a/decisions/designs/corpus-federation-mechanism.md b/decisions/designs/corpus-federation-mechanism.md index 6206e32a..ea101263 100644 --- a/decisions/designs/corpus-federation-mechanism.md +++ b/decisions/designs/corpus-federation-mechanism.md @@ -480,17 +480,19 @@ one-parent implementation. - adr-123 - adr-127 - adr-128 -- adr-133 - adr-134 - adr-135 -- adr-136 - adr-137 - adr-138 - adr-139 - adr-140 - adr-141 - adr-142 -- adr-143 +- adr-144 +- adr-145 +- adr-146 +- adr-147 +- adr-148 ## Related Roadmaps diff --git a/decisions/requirements/corpus-source-identity.md b/decisions/requirements/corpus-source-identity.md index 5822c40e..8b553cff 100644 --- a/decisions/requirements/corpus-source-identity.md +++ b/decisions/requirements/corpus-source-identity.md @@ -31,11 +31,11 @@ deduplication will disagree. - [REQ-001] All three export projections MUST stamp one consistently derived corpus identity. The nearest `.decided/config.yaml` MUST accept an optional `corpus.source` string, and the shared derivation MUST use explicit `corpus.source` when present, else a deterministic value derived from `repository_key`, else the current directory-basename fallback. An explicit value MUST be independent of checkout location and directory spelling and MUST use a lower-case, slash-namespaced form suitable for display and deterministic comparison. - [REQ-002] The derived value MUST land in every projection's existing source field and additively as `corpus.source` in the viewer payload's corpus block. The existing viewer `corpus.name` field MUST remain byte-unchanged (ADR-007, ADR-063). - [REQ-003] The derivation MUST be deterministic and spelling-independent: equivalent argument spellings of the same initialised corpus and any checkout location MUST produce the same source value and byte-identical export output (ADR-002). -- [REQ-004] Documentation MUST publish the consumer-side aggregation recipe: N corpora merge by concatenating documents streams and unioning graph nodes and edges, keyed globally on `(source, id)` (ADR-026). The recipe MUST require distinct source identities wherever fallback identities could collide. A shared parent exported through N children MAY deduplicate only when the source, canonical id, record body, and verified pin agree; differing copies MUST surface as an aggregation conflict rather than last-writer-wins. -- [REQ-005] Source identity alone MUST NOT introduce inheritance, cross-corpus resolution, cross-corpus validation, or relationship-resolution changes. Those federation semantics remain owned by `corpus-federation` and ADR-133 through ADR-143. +- [REQ-004] Documentation MUST publish the consumer-side aggregation recipe: N corpora merge by concatenating documents streams and unioning graph nodes and edges, keyed globally on `(source, id)` (ADR-026). The recipe MUST require distinct source identities wherever fallback identities could collide. A shared inherited record exported through N roots MAY deduplicate only when the source, canonical id, record body, and verified owning-node pin agree; differing copies MUST surface as an aggregation conflict rather than last-writer-wins. +- [REQ-005] Source identity alone MUST NOT introduce inheritance, cross-corpus resolution, cross-corpus validation, or relationship-resolution changes. Those federation semantics remain owned by `corpus-federation` and ADR-134, ADR-135, and ADR-137 through ADR-142 as amended by ADR-144 through ADR-148. The new decisions explicitly preserve the applicable version-1 behavior recorded by superseded ADR-133, ADR-136, and ADR-143. - [REQ-006] The value-precedence change MUST ship with a migration note for consumers keyed on the old basename value. A repository with neither `corpus.source` nor `repository_key` MUST produce byte-identical source values and export bytes to the released fallback behaviour. -- [REQ-007] Federation MUST require explicit, non-empty, distinct `corpus.source` values for the child and inherited layer and MUST use those same values in composite artifact and path keys, provenance, MCP responses, findings, caches, and exports. -- [REQ-008] Repository keys MAY repeat across corpora because `source` is the outer namespace. A repeated canonical artifact id across distinct sources MUST remain distinguishable by `(source, id)` and MUST still follow federation's explicit collision and override rules inside one effective corpus. +- [REQ-007] Federation MUST require an explicit, non-empty `corpus.source` for the invocation root and every logical inherited node and MUST use those same values in composite artifact and path keys, provenance, MCP responses, findings, caches, and exports. Every logical node's source MUST be distinct. Several independently verified ancestry branches MAY reach one logical node only when their source and canonical version-2 node digest match; duplicate direct sources and same-source routes with divergent digests MUST fail. +- [REQ-008] Repository keys MAY repeat across corpora because `source` is the outer namespace. Equal canonical artifact ids in distinct sources MUST remain distinguishable by `(source, id)`. Version 1 MUST retain its accepted cross-layer collision behavior. Version 2 MUST retain every source-owned record: source-qualified lookup MUST remain exact, while unqualified use MUST be a deterministic sourced ambiguity unless explicit Decision-backed overrides converge every visible candidate on one terminal key. Duplicate canonical ids within one source remain invalid, and no source may receive implicit precedence. ## Acceptance Criteria @@ -51,6 +51,13 @@ deduplication will disagree. - A federated export fixture stamps parent and child records with their own configured source identities and deduplicates the same pinned parent across two child exports; a different-pin fixture reports a conflict. +- A version-2 diamond that reaches the same source and canonical node digest + through independent branches contributes one logical node; the same source + with a different digest reports a divergent-pin error. +- A version-2 fixture with the same canonical id in distinct sources keeps + every `(source, id)` record qualified-addressable and reports a bare-reference + ambiguity until explicit overrides converge all visible candidates on one + terminal. - The viewer payload gains `corpus.source` while `corpus.name` remains byte-identical, and the published export schemas validate every projection. @@ -69,7 +76,7 @@ deduplication will disagree. - A human changes `corpus.source` as if it were a display name. Mitigation: documentation calls it stable identity, while `corpus.name` and federation aliases remain the mutable display surfaces. -- Source identity is mistaken for federation. Mitigation: REQ-007 keeps +- Source identity is mistaken for federation. Mitigation: REQ-005 keeps aggregation additive and cross-corpus semantics behind the separately ratified federation ADR set. @@ -78,7 +85,8 @@ deduplication will disagree. - `repository_key` remains the artifact-id generation namespace, not the corpus-provenance identity. - Aggregation remains consumer-side; the engine emits one effective corpus per - invocation unless the federation manifest explicitly composes a parent. + invocation unless the federation manifest explicitly composes an inherited + closure. ## Related Decisions @@ -90,22 +98,25 @@ deduplication will disagree. - adr-080 - adr-085 - adr-089 -- adr-133 - adr-134 - adr-135 -- adr-136 - adr-137 - adr-138 - adr-139 - adr-140 - adr-141 - adr-142 -- adr-143 +- adr-144 +- adr-145 +- adr-146 +- adr-147 +- adr-148 ## Related Designs - corpus-export-shape-contract - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Roadmaps diff --git a/decisions/requirements/federated-resolution-provenance.md b/decisions/requirements/federated-resolution-provenance.md index 2448d377..24267ccf 100644 --- a/decisions/requirements/federated-resolution-provenance.md +++ b/decisions/requirements/federated-resolution-provenance.md @@ -7,94 +7,120 @@ type: requirement ## Status -Proposed +Accepted -Classification: `[internal]` — combine one verified parent with a child through -the existing deterministic read, routing, and enforcement paths while keeping -every artifact attributable. +Classification: `[internal]` — compose a verified corpus-source graph through +one deterministic read, routing, enforcement, MCP, cache, and export model while +keeping every artifact and explicit exception attributable. ## Problem -A verified parent is useful only if it participates in the same effective -corpus as local artifacts. The released Rust engine keys parsed items, -relationships, search entries, graph views, caches, and MCP results primarily -by an unqualified path or id. A directory-level concatenation would make -collisions and provenance ambiguous, and integrating only search would leave -`decisions-for` and `gate --code` unaware of inherited governance. +Source-aware keys make several corpora distinguishable, but a graph needs more +than a flat union. Aliases belong to the source that declares an edge, immutable +parent relationships must not change meaning when an unrelated sibling is +added, shared diamonds must count once, and overrides may form a recorded policy +chain. If each command or cache tier builds its own approximation, validation, +retrieval, enforcement, MCP, and exports can disagree about the effective +governance. + +The graph must preserve historical records and complete override provenance +without letting history leak into live ranking or code enforcement. ## Requirements -- [REQ-001] Inherited artifacts MUST enter one source-aware effective corpus as a read-only layer through the engine's existing seams before validation, resolution, search, graph, scope, export, cache, freshness, or MCP models are derived. Consumers MUST NOT merge corpus walks independently or introduce a second resolver, and the local walk MUST exclude the declared parent materialisation subtree so the same Markdown file cannot enter both layers. -- [REQ-002] A parent/child canonical-id collision MUST surface as an explicit, deterministic, stable-coded cross-corpus finding with no implicit precedence in either direction (ADR-089, ADR-136). A colliding legacy or title alias MAY coexist, but an unqualified reference to it MUST be ambiguous and require qualification. -- [REQ-003] Overrides MUST be explicit under `## overrides` in `.decided/corpus.md`: one qualified parent canonical id MUST map to one local same-type replacement and one live local Decision as rationale. An undeclared duplicate and an absent, ambiguous, cross-type, retired-rationale, chained, or parent-to-parent mapping MUST remain validation errors. -- [REQ-004] Provenance MUST be preserved end to end in resolution, search and grounding results, relationship and validation findings, code-enforcement reports, MCP responses and audit records, and all export projections (ADR-089, ADR-135). It MUST distinguish local from inherited, name the stable source, and identify the verified pin for inherited output. -- [REQ-005] Export composition MUST reuse `corpus-source-identity`, retain each record's own source, key global identity as `(source, id)`, and include inherited records by default (ADR-026, ADR-135, ADR-142). Repeated records MAY deduplicate only when their bodies and verified pins agree; a mismatch MUST be an explicit aggregation conflict, and `--local-only` MUST emit only the child projection without changing the default contract. -- [REQ-006] Determinism MUST hold: identical child and parent materialised bytes plus the same manifest MUST produce byte-identical validation, resolution, retrieval, enforcement, MCP, and export output across runs, machines, and clones (ADR-002). -- [REQ-007] Single-corpus behaviour MUST remain unchanged: without `.decided/corpus.md`, federation MUST add no output or behaviour change relative to the contemporaneous single-corpus engine after the export-schema and source-identity prerequisites, asserted by golden regression. -- [REQ-008] Child validation MUST never demand changes to parent bytes, no finding's remediation may require editing the read-only layer, and mutation commands MUST receive only the local writable layer (ADR-065, ADR-138). -- [REQ-009] Corpus items, validation rows, index entries, relationship endpoints, resolved artifacts, derived generations, and persistent-store records MUST carry enough source and layer identity to distinguish `(source, relative path)` and `(source, canonical id)`. -- [REQ-010] Existing unqualified references MUST resolve when exactly one artifact across both layers matches. The syntax `alias::canonical-id` MUST resolve only within the named source; aliases after `::` MUST be rejected, and no layer may receive implicit resolution precedence. -- [REQ-011] A valid override MUST make the local replacement effective for unqualified resolution, live retrieval, scope routing, and enforcement while leaving the original parent addressable by qualified id. Exports and responses MUST preserve the parent, its overridden state, and complete override provenance. -- [REQ-012] Inherited live Decisions MUST participate in `decisions-for`, `retrieve_grounding`, MCP `find_decisions` path lookup, `gate`, `sentry`, and `gate --code` through the existing scope and enforcement evaluators. Their `## Applies To` declarations and code-constraint globs MUST match against the child repository code tree, never the parent materialisation. -- [REQ-013] Enforcement and MCP MUST NOT expose a local-only bypass for a configured federated corpus. Human diagnostic reads and exports MAY accept `--local-only`. -- [REQ-014] Local and inherited artifacts MUST share one deterministic lexical and relationship index. Ranking MUST add no source boost or local-first quota, MUST retain the v0.28 lexical graph-floor gate, and MUST use `(source, relative path)` as the stable final tie order in a federated corpus. -- [REQ-015] The six-tool MCP surface MUST remain unchanged. Existing id arguments MUST accept qualified ids, and responses MUST retain source, layer, and pin provenance under the existing hard character budget; provenance MUST NOT be truncated before artifact content. -- [REQ-016] The logical generation and cache identity MUST include the existing child corpus and governing-config inputs, manifest bytes, parent source, verified parent digest, and overrides. The source-aware store MUST use a new internal layout version; an older segment MUST degrade to a cache miss and MUST NOT be interpreted as a federated answer. -- [REQ-017] The freshness tracker MUST observe both materialised corpus roots, both governing config files, and the child manifest, and MUST refuse to serve a generation after the parent no longer matches its pin. Inherited recency MUST NOT be derived from child Git history. -- [REQ-018] MCP audit records MUST extend ADR-127's bounded returned-identity object only with fixed source, layer, and pin fields. They MUST NOT copy artifact bodies, excerpts, override mappings, or full response provenance; ADR-141 is the explicit amendment to ADR-127's path-only shape. +- [REQ-001] Inherited artifacts MUST enter one source-aware verified closure before validation, resolution, search, graph, scope, export, cache, freshness, enforcement, or MCP models are derived. Consumers MUST NOT merge corpus walks independently or introduce a second resolver. The root-local walk MUST exclude every verified direct, transitive, diamond, and duplicate physical materialisation route. +- [REQ-002] A version-1 parent/child canonical-id collision MUST retain its explicit deterministic stable-coded finding and no implicit precedence. In version 2, equal canonical ids in distinct sources MUST remain separate catalog records; an unqualified use MUST surface a deterministic sourced ambiguity unless all visible candidates explicitly converge to one terminal `ArtifactKey`. Duplicate ids inside one source remain errors. +- [REQ-003] Overrides MUST remain explicit under the exact `## overrides` heading and its mapping version MUST equal the `## inherits` version. V1 MUST retain one qualified parent target, one local same-type replacement, one live local Decision rationale, and chain rejection. V2 MUST require one globally source-qualified inherited target, one same-type replacement local to the declaring corpus, and one live local Decision rationale, while permitting only the ancestry-level chains and explicit diamond convergence in ADR-147. A valid nested-v1 mapping in a v2 closure MUST normalize to one source-aware hop that a v2 ancestor may explicitly extend. Missing, ambiguous, mixed-version, cross-type, nonlocal, dead-rationale, duplicate-target, cyclic, order-dependent, and unresolved-fork mappings MUST be stable errors. +- [REQ-004] Provenance MUST be preserved end to end in resolution, search and grounding results, relationship and validation findings, code-enforcement reports, MCP responses and audit records, and every export projection (ADR-089, ADR-135). Fixed artifact origin MUST always contain stable source and root/inherited layer; inherited records MUST contain their owning-node pin and root-local records MUST omit `pin`. Full response/export override provenance MUST carry every hop's owner, target, replacement, rationale, and ADR-147 `overridden`/`replacement`/`lineage` state in its total order; ordinary artifact origin need not enumerate physical ancestry routes. Topology findings MUST carry the canonical route and route-count contract. Audit remains the narrower fixed identity in REQ-018. +- [REQ-005] Export composition MUST reuse `corpus-source-identity`, retain each record's own source and, for inherited records, pin, key global identity as `(source, id)`, and include the unique catalog by default (ADR-026, ADR-135, ADR-142). Inherited diamond records MAY deduplicate only when source, id, pin, path, and body agree; a mismatch MUST be an aggregation conflict. Root-local records MUST omit pin. Complete override-chain history MUST remain exported, and `--local-only` MUST emit only the root projection. +- [REQ-006] Determinism MUST hold: identical root and graph materialised bytes plus the same exact manifests MUST produce byte-identical validation, resolution, retrieval, enforcement, MCP, audit, and export output across runs, machines, and clones (ADR-002). Permuting only the root's direct-parent list MUST retain byte-identical public output although the exact-manifest generation changes. Permuting an inherited list MUST require bottom-up repinning and then retain the same effective non-provenance answer; its pin and generation provenance necessarily change. Filesystem iteration and checkout locations MUST NOT change answers or create precedence. +- [REQ-007] Single-corpus behavior MUST remain unchanged: without `.decided/corpus.md`, federation MUST add no output or behavior change relative to the contemporaneous engine. A v1 manifest MUST retain its accepted qualification, collision, override, finding, digest, pin-provenance, and output contract. V2 semantics MUST NOT reinterpret either mode. +- [REQ-008] Validation MUST never demand changes to inherited bytes, no finding's remediation may require editing a read-only node, and mutation commands MUST receive only the root-local projection (ADR-065, ADR-138). Every direct, transitive, and duplicate physical parent route MUST remain read-only to mutation and output commands. +- [REQ-009] Corpus items, validation rows, index entries, relationship endpoints, resolved artifacts, derived generations, and persistent-store records MUST distinguish `(source, relative_path)` and `(source, canonical_id)`. Physical locators and edge aliases MUST remain runtime/context data and MUST NOT become stable public identity. +- [REQ-010] Existing unqualified references MUST resolve when their visible effective candidates reduce to exactly one terminal key. When several historical candidates converge, the catalog endpoint MUST preserve the authored token and the complete candidate set sorted by `(source, canonical_id)`, plus the separate effective terminal; it MUST NOT invent a preferred original key. Version 2 MUST accept globally stable `corpus.source::canonical-id` for every visible source and direct `alias::canonical-id` only in the declaring corpus's lexical context; the suffix MUST be canonical-only. Authored references MUST resolve in the view rooted at their owning source, while public lookup uses the root context. No source, layer, depth, or manifest position may receive precedence. +- [REQ-011] A valid override MUST make its unique terminal effective for unqualified resolution, live retrieval, graph ranking, scope routing, and enforcement while global source-qualified lookup retains every original and intermediate record. The catalog MUST preserve every mapping owner and rationale. Historical relationship endpoints MUST retain the authored token and one-or-many sorted original candidate keys; effective endpoints MUST carry the unique redirected terminal or fail as ambiguous. +- [REQ-012] Effective inherited Decisions at every graph depth MUST participate in `decisions-for`, grounding, MCP path lookup, Gate, Sentry, and `gate --code`. Their `## Applies To` declarations and code-constraint patterns MUST match the invocation root code tree, never a materialisation tree. A terminal replacement governs; an override nonterminal does not. +- [REQ-013] Enforcement and MCP MUST NOT expose `--local-only`, cache-off, or another root-only bypass for a configured graph. Human review and advisory diagnostics MUST remain root-subject after central closure verification so inherited warnings are not duplicated. Viewer, documents, and graph exports MAY expose root-only output without changing their inherited-by-default contract. +- [REQ-014] Root-local and inherited terminal artifacts MUST share one deterministic lexical and relationship index. Ranking MUST add no source boost or local-first quota, MUST retain the v0.28 lexical graph-floor gate, and MUST use `(source, relative path)` as the stable final tie order in a federated corpus. It MUST also add no graph-depth boost, count diamond artifacts and edges once, and exclude override nonterminals from live popularity. +- [REQ-015] The six-tool MCP surface MUST remain unchanged. ID arguments in v2 MUST accept global source qualification and root direct aliases under the same contextual resolver. Every tool MUST use one request-current closure across cache-on, cache-off, resident, and store-hit paths. This requirement amends ADR-141's v2 response provenance to include the complete ADR-147 ordered chain atomically: optional content and list entries reduce first, and if the chain cannot fit the tool MUST return `response_budget_exceeded`. ADR-141/ADR-127 audit exclusions remain unchanged. +- [REQ-016] The logical generation and cache identity MUST be SHA-256 over ADR-148's exact `asdecided-federation-generation-v3\0` domain, tag/u64be-length framing, tag table, sorted node/edge/mapping/terminal rows, root and node snapshots, exact limit block, recursive/root-corpus inputs, and five literal subsystem fingerprints. Its canonical text MUST be `sha256-v3:` plus lowercase hex. Graph persistence MUST use `store/v3`; older segments MUST be cache misses and MUST NOT be interpreted as graph answers. +- [REQ-017] Freshness MUST observe the root and every inherited materialisation/corpus root, governing config, manifest path including absence, and captured artifact. Create, content/identity/type change, remove, rename, watcher overflow, and lost coverage on those inputs MUST trigger full candidate recapture in the first graph implementation. A failed edge, node, pin, bound, snapshot, or override MUST make the prior generation inaccessible; stale fallback is forbidden. Inherited recency MUST NOT be borrowed from root Git history. +- [REQ-018] MCP audit records MUST extend ADR-127's bounded returned-identity object only with path, source, layer, and optional pin: `pin` MUST be present for inherited records and omitted for root-local records. They MUST NOT copy artifact bodies, excerpts, topology, aliases, override mappings, or full response provenance. Any closure preparation or serving failure MUST emit exactly one audited error event with `returned: []`, without exposing a stale identity. +- [REQ-019] Every unique inherited node MUST be structurally and relationally validated in its owning source's visibility context. One node error MUST produce one deterministically selected sourced root blocker rather than one per physical route; parent warnings and review advisories MUST remain parent-owned. Cycle, divergent pin, cross-branch ambiguity, invalid override, and override divergence MUST be root composition findings. +- [REQ-020] The composed model MUST expose distinct catalog, root-effective, and root-local projections. Catalog MUST contain every unique historical and terminal record; effective MUST contain only root-local records and inherited terminals with redirected live endpoints; root-local MUST be the sole mutation and root-owned review projection. Qualified history MUST NOT leak catalog edges into live ranking or enforcement. +- [REQ-021] Portal identity and navigation MUST key every artifact and edge by `(source, id)` and MUST retain and independently address three or more records sharing one canonical id without collapse. Global qualified links MUST be copyable. OKF and generated agent-rule projections MUST remain root-local unless separately decided. +- [REQ-022] Override composition MUST be source-scoped and bottom-up. A node MUST union and deduplicate its direct parents' effective projections before applying its own mappings. A branch-local exception MUST NOT suppress the same shared ancestor still live through a sibling, and a join MUST reject divergent terminals unless its own explicit local mappings reconverge every live branch. Complete mapping rows MUST use parents-before-child, source-lexicographic Kahn owner rank and then bytewise `(owner source, target key, replacement key, rationale key)` order. +- [REQ-023] `decided corpus explain [directory] [--from source] [--json]` MUST call the central contextual graph resolver and expose, without reconstructing an overlay, the selected context, qualification mode, visible sources, direct aliases, complete historical candidate set, selected source-owned record, effective terminal, and ordered override provenance. Stable JSON schema version 1 MUST emit a deterministic outcome for resolved, ambiguous, missing, unknown-context, malformed-qualified, and qualified-alias cases. Diagnostic outcomes MUST retain useful output and exit nonzero; graph verification failures MUST emit no partial explanation. ## Acceptance Criteria -- A child relationship naming a parent Decision resolves in CLI and MCP, and a - qualified lookup returns source, inherited layer, and pin provenance. -- A same-canonical-id fixture fails with a sourced collision. A valid override - clears it, routes and enforces the local replacement, and leaves the parent - available through its qualified id. -- An inherited Decision scoped to a child path appears in `decisions-for` and - `retrieve_grounding`; a violating child code change fails `sentry` and - `gate --code` with the inherited Decision and source named in human, JSON, - and SARIF output. -- MCP returns the same six tools, stays within its hard character budgets, and - never drops provenance before optional artifact content. -- Audit extraction attributes a repeated artifact id to the exact source, - layer, and verified pin without recording artifact content or override detail. -- A parent-byte or manifest change invalidates the serving generation and - persistent cache. An old store layout degrades to rebuild without changing - the answer. -- A combined-corpus ranking fixture proves a strongly matched local or parent - artifact cannot be displaced by a weak but well-connected artifact below - the v0.28 graph floor. -- Two clones of the same pinned state produce byte-identical outputs, while a - no-manifest fixture remains byte-identical to the contemporaneous - single-corpus goldens. +- Root and inherited global qualification resolve through CLI and every MCP + tool; owner-scoped aliases with the same spelling resolve independently and + a qualified legacy/title alias is rejected. +- Three sources may carry the same canonical id and remain qualified-readable. + Bare use is ambiguous until every candidate explicitly converges on one + terminal. +- A parent-authored relationship is unchanged when an unrelated sibling is + added. Catalog graph lookup retains the original endpoint, while root live + graph lookup follows an explicit terminal override. +- `A -> B -> C` and an explicitly reconciled diamond retain every artifact, + rationale, and hop. An incomplete fork, chain cycle, inherited replacement, + wrong type, and dead rationale fail deterministically. +- A shared diamond artifact, relationship, warning owner, BM25 row, and inbound + edge count appear once. An override nonterminal cannot gain ranking or govern + root code. +- Qualified and unqualified `corpus explain` reports show immutable history, + the effective terminal, context-local aliases, and the complete explicit + override provenance; ambiguity and missing lookup remain machine-readable. +- Inherited Decisions from every depth appear in path routing and grounding and + fail violating root code through ordinary Sentry/Gate output with source and + pin in human, JSON, and SARIF forms. +- All six MCP tools agree across cold, resident, store-hit, and cache-disabled + paths. Tight budgets preserve the full chain or return the explicit budget + error. A stale leaf produces one empty-return audit error and no old identity. +- Default exports retain the unique full catalog and complete chains; + `--local-only` returns only root records. Portal keeps three same-id sources + distinct and navigable. +- Every direct and transitive materialisation is absent from full/diff Sentry + code enumeration and remains byte-identical after all mutation and output + commands. +- A topology, manifest, config, artifact, pin, edge, or override change + invalidates resident and persistent state. Old stores miss; cold, warm, + cache-disabled, and rebuilt outputs agree byte-for-byte. +- Complete no-manifest and v1 golden suites remain unchanged. ## Success Metrics -- A child repository cites, routes, and enforces a firm-wide Decision exactly - as it does a local Decision, while every consumer can identify its source. +- A root repository can cite, retrieve, route, and enforce decisions from a + valid bounded parent graph while every consumer identifies exact source and + pin. - Federation adds no MCP tool and no second implementation of resolution, - scope matching, or code enforcement. + ranking, scope, enforcement, freshness, or export composition. +- Explicit policy history survives graph composition without letting + historical rows change the root's live answer. ## Risks -- Different consumers build different effective corpora. Mitigation: REQ-001 - requires one source-aware loader before every derived model. -- A large parent changes relevance or consumes the response budget. - Mitigation: one ranking contract, the lexical graph-floor invariant, hard - budgets, and a federation track in the DecisionGrounding evaluation. -- An override silently deletes the governing history. Mitigation: the parent - remains qualified-addressable and carries explicit override provenance. -- The watcher serves a stale parent after a pin or materialisation change. - Mitigation: the parent digest and manifest are generation inputs and are - verified before serve. +- A flat alias map reinterprets immutable parent relationships. Mitigation: + one contextual resolver keyed by authoring source and edge-local alias. +- Branch policy silently suppresses a sibling. Mitigation: bottom-up scoped + effective views and explicit join reconciliation. +- Catalog history leaks into live ranking or enforcement. Mitigation: separate + catalog/historical and effective/redirected projections with parity tests. +- One cache or MCP fallback reconstructs a different closure. Mitigation: all + consumers receive one request-current `VerifiedFederation` generation. +- Long chains exceed response budgets. Mitigation: chain provenance is atomic + and fails explicitly after optional reductions. ## Assumptions -- `parent-corpus-inheritance` supplies one verified, materialised parent. -- `corpus-source-identity` and the export schemas land before source-aware - records are published. -- The current shared scope and Sentry evaluators can consume source-aware items - without introducing parallel matching semantics. +- `parent-corpus-inheritance` supplies the fully verified exact-byte closure and + complete read-only-root set. +- `corpus-source-identity` remains the stable outer namespace. +- Existing source-neutral ranking and root-code evaluators can consume the + effective source-aware projection without parallel semantics. +- ADR-146 through ADR-148 are accepted authority for resolution, chain, and + serving implementation. ## Related Decisions @@ -112,27 +138,27 @@ collisions and provenance ambiguous, and integrating only search would leave - adr-104 - adr-105 - adr-112 -- adr-117 - adr-119 -- adr-121 - adr-123 - adr-127 - adr-128 -- adr-133 -- adr-134 - adr-135 -- adr-136 - adr-137 - adr-138 - adr-139 - adr-140 - adr-141 - adr-142 -- adr-143 +- adr-144 +- adr-145 +- adr-146 +- adr-147 +- adr-148 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition - code-scope-consumption - sentry-code-constraint-evaluation @@ -144,7 +170,7 @@ collisions and provenance ambiguous, and integrating only search would leave ## Related Requirements - parent-corpus-inheritance -- export-contract-schemas - corpus-source-identity +- export-contract-schemas - rac-path-decisions-lookup - deterministic-decision-code-enforcement diff --git a/decisions/requirements/parent-corpus-inheritance.md b/decisions/requirements/parent-corpus-inheritance.md index 2e93ac0d..47ff17f7 100644 --- a/decisions/requirements/parent-corpus-inheritance.md +++ b/decisions/requirements/parent-corpus-inheritance.md @@ -7,101 +7,135 @@ type: requirement ## Status -Proposed +Accepted -Classification: `[internal]` — declare and verify one direct, read-only parent -corpus from materialised bytes. This is the declaration and materialisation -half of the `corpus-federation` programme. +Classification: `[internal]` — declare, verify, and capture a bounded graph of +offline read-only parent corpora. This is the topology and materialisation half +of the `corpus-federation` programme. ## Problem -The released engine has one corpus root. It cannot validate a child reference -to a firm-wide decision, route that decision into child work, or apply the -decision's mechanical constraints to child code. ADR-089 accepts a pinned, -offline parent under strict provenance and write-boundary constraints, but the -original proposal left the manifest home, parent cardinality, transitivity, -pin shape, and parent-validation behaviour unresolved. +One verified parent proves the source-aware federation substrate, but real root +repositories may need several independently owned corpora and those corpora may +share pinned ancestors. Flattening that graph outside AsDecided hides ownership +and exception lineage. Loading parent directories independently cannot define +cycles, diamonds, alias scope, divergent pins, or one atomic freshness boundary. + +The existing manifest and digest version must remain exact. Graph support +therefore needs an explicit v2 carrier that authenticates nested topology while +preserving the deterministic, offline, root-only write boundary. ## Requirements -- [REQ-001] A child corpus MUST declare inheritance through a pinned source reference under the exact lowercase heading `## inherits` in the fixed operational Markdown manifest `.decided/corpus.md` (ADR-089). The section MUST contain exactly one fenced YAML mapping with a full SHA-256 digest, and the manifest MUST remain outside the artifact walk, search index, relationship graph, and exports. -- [REQ-002] Parent resolution MUST read only materialised bytes already on disk: it MUST NOT clone, pull, fetch, refresh, or perform network I/O in validate, resolve, retrieve, enforce, export, or serve paths (ADR-002, ADR-089). Git submodules and vendored directories are supported only because both present ordinary local bytes; refreshing and repinning them remain explicit user Git operations outside AsDecided. -- [REQ-003] The parent MUST be an inherited, read-only layer: no AsDecided command may write, rename, migrate, scaffold, install generated rules, or recover a transaction under the parent root, and the child remains the only writable canonical state (ADR-018, ADR-065, ADR-080). -- [REQ-004] Validation MUST emit deterministic, distinct, stable-coded errors for missing materialisation, missing parent corpus, malformed declaration, source mismatch, unsupported transitivity, and digest mismatch. No parent artifact may enter the effective corpus after any verification failure. -- [REQ-005] The declaration MUST remain backward-compatible under ADR-089: engines predating federation continue to ignore `.decided/corpus.md` through the existing hidden-directory walk. An implementing engine MUST add no output or behaviour change without that manifest, measured against the contemporaneous single-corpus engine after its prerequisites. -- [REQ-006] Optional configuration defaults MAY assist materialisation discovery only through the established section-loader pattern; the Markdown manifest MUST remain the inheritance and pin source of truth and configuration MUST NOT silently create or update it (ADR-089). -- [REQ-007] The capability MUST be available to every user and MUST NOT be enterprise-gated (ADR-085, ADR-089). It MUST ship only within the human-accepted boundary of ADR-133 through ADR-143; this requirement does not substitute for those decisions. -- [REQ-008] When the mechanism ships, the ADR-088 profile scaffold MUST gain reserved parent-declaration guidance only when explicitly requested. Unconfigured profile output MUST remain byte-identical. -- [REQ-009] The declaration MUST contain `version`, a child-local parent `alias`, the parent's configured `source`, a repository-relative materialisation `root`, a parent-relative `corpus` directory, and a full lowercase `sha256` digest. The child and parent MUST each declare an explicit, non-empty, distinct `corpus.source` under `corpus-source-identity`. -- [REQ-010] The first federation increment MUST accept exactly one direct parent. A second parent and a materialised parent that itself declares inheritance MUST produce distinct stable error findings before overlay; neither case may be flattened or partially interpreted. -- [REQ-011] The parent materialisation MUST remain inside the canonical child repository root. Absolute paths, `..` components, unresolved paths, symlinks in the path to the parent config or any discovered artifact, and canonical paths outside the child repository MUST be rejected before those bytes enter the digest or corpus. Once verified, the materialisation subtree MUST be excluded from the local artifact walk. -- [REQ-012] Before overlay, the engine MUST verify the parent source identity and a canonical versioned SHA-256 corpus digest over a domain separator, parent source, governing parent `.decided/config.yaml` bytes, and sorted corpus-relative Markdown paths and bytes. The digest MUST exclude checkout location, timestamps, and filesystem iteration order, and the same pure calculation MUST be available to operators. -- [REQ-013] The parent MUST pass structural and relationship validation before overlay. A parent error MUST surface in the child as one sourced `parent-corpus-invalid` error and block overlay; parent warnings and review advisories MUST remain parent-owned and MUST NOT be repeated in every child. +- [REQ-001] A corpus MUST declare inheritance through pinned source references under the exact lowercase heading `## inherits` in the fixed operational Markdown manifest `.decided/corpus.md` (ADR-089). The section MUST contain exactly one fenced YAML mapping, and the manifest MUST remain outside artifact discovery, search, relationships, and exports. Version 1 MUST retain one mapping; version 2 MUST carry the strict `parents` sequence defined by ADR-145. +- [REQ-002] Parent resolution MUST read only materialised bytes already on disk: it MUST NOT clone, pull, fetch, refresh, query a registry, or perform network I/O in validation, resolution, retrieval, enforcement, export, or serving paths (ADR-002, ADR-089). Updating a submodule or vendored directory and repinning it MUST remain an explicit user Git operation outside AsDecided. +- [REQ-003] The invocation corpus MUST remain the only writable graph node. No AsDecided command may write, rename, migrate, scaffold, recover a transaction, install generated rules, or create output beneath any direct, transitive, diamond, or duplicate physical parent route (ADR-018, ADR-065, ADR-080). +- [REQ-004] Validation MUST emit deterministic, distinct, stable-coded errors for missing materialisation, missing parent corpus or config, malformed declaration, source mismatch, containment failure, digest mismatch, snapshot change, duplicate parent, cycle, divergent pin, invalid node, overlapping roots, override divergence, and exceeded limits. No inherited artifact may enter the effective corpus after any verification failure, and partial overlays are forbidden. +- [REQ-005] The declaration MUST remain backward-compatible under ADR-089. Engines predating federation continue to ignore `.decided/corpus.md`; no-manifest repositories MUST retain contemporaneous single-corpus output byte-for-byte. A version-1 manifest MUST retain its exact one-parent/leaf-only semantics, `sha256:` digest and known vector, stable findings, transitivity rejection, and observable output. +- [REQ-006] Optional configuration defaults MAY assist materialisation discovery only through the established section-loader pattern. The Markdown manifest MUST remain the inheritance and pin source of truth, and configuration MUST NOT silently create, update, reorder, fetch, or repin it (ADR-089). +- [REQ-007] The capability MUST remain available to every user and MUST NOT be enterprise-gated (ADR-085, ADR-089). Graph implementation MUST conform to accepted ADR-144 through ADR-148; this requirement does not substitute for or amend that authority. +- [REQ-008] Under ADR-088, `decided init --parent-corpus` MUST emit deterministic v2-first guidance only when explicitly requested, with or without a profile and for fresh or already-initialised repositories. It MUST name `.decided/corpus.md`, the exact lowercase `## inherits` and `## overrides` headings, materialisation before pinning, and `decided corpus digest --version 2`; it MUST NOT create a manifest, fetch anything, or write parent bytes. Init/profile human and JSON output without the flag MUST remain byte-identical, and JSON MUST add the guidance field only when requested. +- [REQ-009] A v1 declaration MUST retain its exact `version`, `alias`, `source`, `root`, `corpus`, and full `sha256` fields. A v2 declaration MUST contain `version: 2` and one to 32 strict parent records, each with exactly `alias`, `source`, `root`, `corpus`, and a full lowercase `sha256-v2` digest. The `## inherits` version MUST select the whole manifest semantic mode; a present `## overrides` mapping MUST use the same version, and mixed versions or overrides without valid inheritance MUST fail. Every graph node MUST declare an explicit valid `corpus.source`; aliases MUST be unique and source-local, and duplicate direct sources MUST fail. +- [REQ-010] A v1 root MUST accept exactly one direct leaf parent and retain current multiple/transitive findings. A v2 root MUST accept the bounded acyclic graph in ADR-144. Active-ancestry source recurrence MUST be a cycle; a completed source MAY deduplicate only when every independently verified route has the same canonical v2 node digest, and the same source with a different digest MUST be a divergent-pin error. +- [REQ-011] Every parent materialisation MUST remain inside the canonical repository root that declares it and the complete closure MUST remain inside the invocation repository. V2 `root` and `corpus` values MUST be POSIX-relative UTF-8 and at most 4,096 bytes, 64 components, and 255 bytes per component. Absolute, empty-segment, `.`, `..`, backslash, drive, UNC, unresolved, and canonical-escape paths MUST fail. Materialisation/corpus roots MUST be real directories; configs, present manifests, and Markdown artifacts MUST be real regular files. Symlink/reparse traversal, hard-linked inherited files, and mount/volume/junction boundary crossing MUST fail before bytes enter a digest or corpus. If required identity metadata is unavailable, v2 MUST fail as an unsupported filesystem. The root-local walk MUST exclude every verified physical materialisation subtree. +- [REQ-012] Before overlay, the engine MUST verify source identity and a canonical versioned SHA-256 digest. V1 MUST retain its exact domain and config/artifact framing. V2 MUST use ADR-145's domain and framing over source, exact governing config, explicit nested-manifest presence, exact nested-manifest bytes when present, and sorted owned corpus-relative Markdown paths and bytes, excluding parent subtrees, checkout location, timestamps, iteration order, and normalization. The same pure calculations MUST be available through the existing operator command and explicit `--version 2` mode. +- [REQ-013] Every unique inherited node MUST pass structural and relationship validation in its source visibility context before root composition. A node error MUST surface as one deterministically selected sourced root blocker rather than one copy per diamond route; parent warnings and review advisories MUST remain parent-owned and MUST NOT be repeated in descendants. Remediation MUST NOT direct an AsDecided command to edit inherited bytes. +- [REQ-014] Version-2 operational mappings and every governing config parsed during graph capture MUST use only YAML mappings, sequences, and scalars, with at most 32 levels and 16,384 nodes, and MUST reject anchors, aliases, custom tags, and merge keys. Version-2 manifests MUST also reject unknown or duplicate YAML keys, malformed source values, aliases outside `^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$`, aliases over 64 bytes, and source identities over 255 bytes. Parent order MUST grant no precedence, and discovery, validation, findings, catalog construction, and output MUST use fixed bytewise source-aware ordering. No-manifest and standalone v1 YAML behavior MUST remain unchanged. +- [REQ-015] Verification MUST capture and bound a target config and manifest, parse only declarations needed to establish safe local-walk exclusions, capture owned local bytes, and verify the incoming pin before following child paths named by that manifest. Digesting, parsing, validation, composition, and serving MUST consume one immutable snapshot; a file or directory identity change during capture MUST fail closed. +- [REQ-016] Version 2 MUST enforce the fixed limits in ADR-144 and ADR-145: 1 MiB config/manifest; path, YAML, alias, and source bounds; 32 direct parents; depth 16; 256 unique inherited sources; 1,024 edges; 4,096 overrides; 50,000 unique inherited Markdown files; 16 MiB per inherited file; 256 MiB unique inherited bytes; 512 MiB physical verification bytes; and 200,000 visited entries. Exactly-at-limit MUST succeed. On plus one, the counter for the exceeded dimension MUST stop, report `observed = limit + 1`, and fail without truncation through `corpus-federation-limit-exceeded`. +- [REQ-017] Every physical edge MUST be path, configured-source, and declared-pin verified even when its logical node later deduplicates. Every previously unseen canonical physical capture MUST verify its own outgoing descendant edges; only an identical canonical capture may reuse a verified route result. Logical bytes MUST sum exact config, present-manifest, and owned-Markdown bytes once per unique inherited node after verified deduplication; root bytes are excluded. Physical bytes MUST charge those inputs once per distinct canonical `(materialisation root, corpus root)` capture, including failed or later-deduplicated routes. Edge and override declarations MUST count across distinct physical manifests before logical node deduplication. Visited entries MUST include ignored, excluded, rejected, and later-deduplicated directory entries examined below inherited roots. The loader MUST store unique nodes and edges rather than enumerate every possible diamond path. +- [REQ-018] The loader MUST retain every canonical physical materialisation and corpus root in one read-only set. Sibling roots MUST NOT overlap except for the same verified logical route; valid nesting MUST follow a declared ancestry edge. Full/diff code enumeration and every mutation/output guard MUST use the complete set, including symlinked ancestors, lexical `..`, and nonexistent target suffixes. +- [REQ-019] A v2 edge MAY target a corpus with no manifest, a v1 manifest, or a v2 manifest. A nested v1 node MUST apply its exact one-parent/leaf-only relationship, collision, alias, and override rules in its own context. Its valid effective projection and any v1 override MUST then be normalized to source-aware keys as graph input, and a v2 ancestor MAY explicitly chain from that local replacement. During a v2-root run, every captured node MUST receive a canonical v2 node digest for deduplication and provenance while any declared legacy v1 edge pin and mapping spelling remain separately checked and attributable. +- [REQ-020] Core federation semantics MUST depend only on the explicitly materialised repository tree, governing config and manifests, stable source identities, and verified pins. They MUST NOT require a `.git` directory, remote name, default branch, forge URL, GitHub API, Cursor Origin API, or another host-specific service. Local-only repositories and checkouts from any standard Git remote MUST produce the same answer from identical bytes. Optional clone, review, or check integrations MUST remain outside graph verification and MUST NOT change corpus identity or resolution. +- [REQ-021] `decided corpus status [directory] [--json]` MUST verify the complete version-2 closure through the central immutable loader before rendering. It MUST report every logical source, canonical pin, manifest version, canonical source route, exact physical-route count, declared edge and alias, repository-relative materialisation/corpus path, catalog/effective/root-local count, override count, graph depth, and read-only boundary. Stable JSON schema version 1 MUST state that networking was absent, pins were verified, and snapshots were immutable. A closure failure MUST emit no partial report, and checkout location MUST NOT enter stable output. ## Acceptance Criteria -- A child with one vendored or submodule-backed parent validates fully with - networking disabled and resolves the same bytes across two clones. -- Removing the parent, changing its governing config or one parent Markdown - byte without updating the digest, or adding a parent-of-parent declaration - produces the corresponding stable error and no effective overlay. -- Absolute, escaping, and symlink-traversing parent paths are rejected, and - tests prove that every command leaves the complete parent tree - byte-identical. -- A parent with an error yields one sourced child error and no inherited - results. Parent warnings do not duplicate into the child report. -- A repository without the manifest preserves all contemporaneous - single-corpus CLI, MCP, export, and cache goldens byte-for-byte. +- Existing no-manifest CLI, MCP, export, and cache goldens and the complete + valid/error v1 suite remain byte-identical. +- Equivalent v1 and v2 one-parent fixtures produce the same effective records; + v2 additionally carries its graph pin and provenance. +- Two and three direct parents produce byte-identical public output under every + root-list permutation. A nested-list permutation requires bottom-up repinning + and then preserves effective identities/ranking/findings while pin and + generation provenance changes. A transitive inherited Decision resolves and + enforces against root code. +- A same-pin diamond emits one logical catalog record after verifying every + physical route. A divergent-pin diamond, root-source recurrence, self-cycle, + and longer cycle each fail with stable order and no overlay. +- Absolute, escaping, symlink, overlapping-sibling, and nested path attacks fail + at every depth. Tests prove every physical parent tree remains byte-identical + after every command, including nonexistent output targets through symlink or + `..` spellings. +- V2 known vectors change for config, manifest presence/bytes, path, or Markdown + bytes and remain stable across checkout path, timestamp, and filesystem order. +- Each fixed resource limit succeeds at the boundary and fails at plus one. A + many-diamond fixture proves work scales with verified nodes, edges, and bytes, + not the number of possible routes. +- A captured-file or directory-shape swap cannot mix hashed bytes with parsed or + served bytes; verification fails closed. +- `corpus status` reports a same-pin diamond's one logical shared node and both + independently verified physical routes, is byte-identical across checkout + locations, works without `.git`, and emits no stdout after either copy is + tampered. +- The runnable federation example validates from a plain local tree and makes + no GitHub, Cursor Origin, branch-name, remote-name, or network assumption. +- YAML depth/node/anchor bombs, overlong path components, hard links, + mount/volume crossings, junctions, and Windows reparse points fail within the + fixed bounds rather than changing acceptance by platform. ## Success Metrics -- A repository can pin one firm-wide standards corpus in an ordinary reviewed - diff and validate it completely offline. -- Updating the parent is an explicit materialisation and pin change; no command - silently changes the governing bytes. +- A root repository can pin several independent corpora and their shared + ancestors in ordinary reviewed Git diffs, with networking disabled. +- Updating any graph edge or node is an explicit materialisation and bottom-up + pin change; no command silently changes governing bytes. +- A shared source is verified on every physical route but represented once in + the logical corpus and warning ownership. +- Identical materialised bytes remain portable between local Git, Cursor + Origin, GitHub, and other standard Git forges without a federation adapter. ## Risks -- Pin verification is skipped for speed. Mitigation: verification precedes - overlay and the composite generation/cache key includes the verified digest. -- A repository-relative path still escapes through a symlink. Mitigation: - canonical containment and symlink-root rejection occur before walking. -- Repeated parent warnings create fleet-wide noise. Mitigation: only a parent - error blocks composition; the parent owns its warnings and review queue. -- One-parent scope is mistaken for the permanent model. Mitigation: multiple - and transitive parents are explicit future decisions, not silently accepted - syntax. +- Unverified nested manifest bytes direct traversal. Mitigation: verify the + target's v2 node pin before following its declared child paths. +- A diamond hides a tampered copy. Mitigation: every physical route is checked + before source-and-pin deduplication. +- Large or adversarial graphs exhaust a server. Mitigation: fixed versioned + limits cover topology, files, logical bytes, physical work, and visited entries. +- A path escapes through symlink or nonexistent suffix handling. Mitigation: + canonical containment, no-follow capture, and the union read-only-root guard. +- Graph behavior leaks into v1. Mitigation: explicit semantic modes and complete + v1 byte/error regression. ## Assumptions -- A submodule or vendored directory inside the child repository covers the - first real adoption path and keeps clones reproducible. -- The source identity requirement lands before parent records reach exports or - MCP. -- ADR-133, ADR-134, ADR-135, ADR-138, and ADR-143 are separately ratified before - parent loading, identity, composition, or cache behavior ships. +- Every parent is a submodule or vendored directory beneath its declaring + repository root. +- `corpus-source-identity` remains the stable global node identity and + `repository_key` remains only the artifact-id generation namespace. +- ADR-144 and ADR-145 are accepted authority for recursive loader work. ## Related Decisions - adr-002 -- adr-016 - adr-018 -- adr-055 - adr-065 - adr-080 - adr-085 - adr-088 - adr-089 -- adr-117 -- adr-133 - adr-134 - adr-135 - adr-138 -- adr-143 +- adr-144 +- adr-145 +- adr-148 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition ## Related Roadmaps diff --git a/decisions/roadmaps/corpus-federation.md b/decisions/roadmaps/corpus-federation.md index 448ae38c..2e7aa5ce 100644 --- a/decisions/roadmaps/corpus-federation.md +++ b/decisions/roadmaps/corpus-federation.md @@ -7,196 +7,290 @@ type: roadmap ## Status -Planned - -ADR-089 accepts federation in principle and fixes five non-negotiable -constraints. The refreshed `corpus-federation-mechanism` design now resolves the -first-increment questions against the released Rust engine: one direct parent, -one fixed Markdown manifest, verified materialised bytes, explicit source -identity and overrides, one source-aware read model, and inherited code -enforcement. ADR-133 through ADR-143 separate those choices for individual -human ratification. They are now Accepted and govern the engine work. -Execution order and task state live in the GitHub epic named in -`## Related Tickets` under ADR-093. +Achieved -## Context +The v0.29.0 release delivers graph-complete corpus federation, not +only the one-parent foundation. The historical ADR-133 through ADR-143 set and +its implementation establish the source-aware substrate and exact v1 +compatibility. Accepted ADR-144 through ADR-148 authorize a bounded multi-parent +DAG, topology-binding pins, global source qualification, explicit override +chains, and closure-wide serving state. -The released engine is single-corpus. `relationships::corpus_items` and its -downstream validation, resolution, index, graph, cache, freshness, enforcement, -and MCP models identify artifacts by an unqualified path or id. Org-wide reach -exists today through ADR-117's two-endpoint topology, but the child cannot -validate a reference to the org corpus or apply an org Decision's code -constraints to child code. +Execution history remains in GitHub under ADR-093. This roadmap records the +durable contract and the release evidence that completes it. -The original federation proposal named retired Python seams and left seven -mechanism choices open. The design refresh closes those questions without -turning AsDecided into a live cross-repository service: the parent is one -read-only layer already materialised inside the child repository, and every -answer remains a deterministic function of reviewed bytes. +## Context -Two additive `corpus-sync` capabilities precede published federation output: -machine-checkable export schemas (`export-contract-schemas`) and one stable -source identity (`corpus-source-identity`). The remaining point-in-time, -delta, and section-anchor work does not block federation. +The released engine began from one corpus root. The federation foundation makes +identity, relationships, retrieval, scope, cache, MCP, enforcement, and exports +source-aware through one read model. It verifies one materialised parent and +preserves no-manifest output exactly. + +Organisations rarely have only one shared concern. A service may need a firm +standard, a security policy, a regulatory corpus, and product decisions; those +sources may share ancestors or make recorded exceptions at intermediate levels. +A synthetic combined parent would hide ownership and move conflict handling +outside AsDecided. A naive parent list would introduce order precedence, +double-count diamonds, mis-scope aliases, and leave transitive topology outside +the direct pin. + +Graph federation extends the same deterministic substrate. Every source remains +pre-materialised inside the root repository, every edge and node is pinned, +the root remains the only writable truth, and every consumer uses one verified +closure. AsDecided remains the decision layer for coding agents rather than a +generic graph platform or hosted synchronisation service. + +Machine-checkable export schemas and `corpus.source` are still the only +`corpus-sync` prerequisites. Point-in-time export, change feeds, and section +anchors do not block federation. ## Outcomes -- A child repository declares and verifies one pinned parent in - `.decided/corpus.md`, fully offline. -- Child artifacts cite parent artifacts with ordinary or qualified - relationships, and collisions never acquire implicit precedence. -- Applicable inherited Decisions appear in grounding and path lookup and apply - their deterministic code constraints to child code. -- Local and inherited artifacts share one deterministic read model while every - result, finding, audit record, and export remains attributable to its source. -- A valid local override records the replacement and its live Decision - rationale without deleting or rewriting parent history. -- The same pinned parent deduplicates across N child exports on `(source, id)`; - differing pins surface as a conflict. -- Adding federation leaves repositories without a federation manifest - byte-identical to the contemporaneous single-corpus path. +- A root repository declares several independently pinned direct parents in a + strict `.decided/corpus.md` v2 manifest and recursively verifies their + materialised ancestry without networking. +- Cycles, divergent pins, duplicate direct sources, escaping paths, invalid + nodes, unresolved policy forks, and fixed-limit excesses fail before any + partial overlay reaches a consumer. +- Same-source/same-pin diamonds verify every physical route and deduplicate to + one logical catalog record, relationship, warning owner, search row, and + export record. +- Any visible artifact is durably addressable as + `corpus.source::canonical-id`; direct aliases remain context-local + conveniences. Equal ids in distinct sources remain distinct and bare use is + ambiguous rather than order-resolved. +- Decision-backed overrides can form explicit ancestry chains and reconcile + diamond policy branches without deleting any original, intermediate, + rationale, or mapping history. +- One root-effective projection feeds retrieval, relationship ranking, path + routing, Gate, Sentry, all six MCP tools, and summaries. Inherited Decisions + at every depth govern root code. +- One closure generation feeds cold, resident, persistent, cache-disabled, and + event-rebuilt reads and fails closed after any leaf or topology change. +- Viewer, documents, and graph exports retain the unique full catalog and + complete chain provenance; `--local-only` remains root-only and cannot bypass + MCP or enforcement. +- Operators can verify and inspect the complete source graph, pins, physical + routes, projections, overrides, and read-only roots, then explain any + contextual lookup from immutable history through its effective terminal. +- Federation consumes a local materialised tree and is independent of `.git`, + remote names, default branches, or forge APIs; Cursor Origin, GitHub, and + local-only repositories do not change corpus semantics. +- No-manifest and v1 repositories retain their accepted observable behavior. ## Initiatives -### Mechanism design (`corpus-federation-mechanism`) - -The design synthesis resolves the manifest home, one-parent cardinality, -transitivity rejection, parent finding ownership, export opt-out, repository-key -interaction, and MCP budget behaviour. It also records the current Rust seams, -source-aware identity, code-scope semantics, enforcement, cache invalidation, -and write boundary that the original proposal omitted. - -### Federation decision set - -ADR-133 through ADR-143 were independently human-ratified on PR #451. Together -they decide the topology, parent declaration and verification, source identity, -qualified resolution, explicit overrides, unified read model, ranking, -child-code enforcement, bounded MCP provenance, export composition, and -versioned generation/cache contract. Engine implementation may now begin -within their combined boundary; ADR-141 explicitly amends ADR-127 for the MCP -provenance change. - -### Source and export prerequisites - -Land the export schemas before adding source-aware fields, then land -`corpus.source` as the one identity used by exports and federation. Pull these -two initiatives from `corpus-sync`; do not pull point-in-time export, change -feeds, or section anchors into the federation critical path. - -### Resolver, validation, routing, and enforcement - -Load one verified parent through a central source-aware layer set before -deriving validation, resolution, search, graph, scope, cache, freshness, and MCP -models. Support `alias::canonical-id`, explicit manifest overrides, sourced -collision findings, inherited `decisions-for` and grounding, and inherited -Sentry/`gate --code` evaluation against the child code tree. Mutation commands -receive only the local layer. - -### Profile unhollowing (ADR-088) - -Once the mechanism ships, expose the reserved parent declaration guidance only -when explicitly requested. Unconfigured profile and init output remain -byte-identical. - -### Composition with corpus-sync - -Viewer, documents, and graph exports stamp parent and child records with their -own `corpus.source`, using the published schemas and global `(source, id)` key. -Inherited records are the default projection; `--local-only` is a diagnostic -and export view, never an MCP or enforcement bypass. +### Graph authority and contract + +ADR-144 through ADR-148 are Accepted. ADR-133, ADR-136, and ADR-143 are +Superseded for version 2; the narrower amendments to ADR-134, ADR-137, ADR-138, +ADR-141, and ADR-142 are recorded; ADR-135 carries the same-source/same-digest +physical-route clarification; and ADR-139 and ADR-140 remain substantively +active. The graph design is Accepted and `corpus-source-identity` +REQ-005/007/008 reference the new authority set, unique logical nodes, +same-digest diamond routes, and version-2 equal-id ambiguity. The implementation +requirements are Accepted and the roadmap is Achieved. The accepted one-parent +design remains the historical compatibility foundation. + +### Version-2 manifest and verified closure + +Add a strict unordered `parents` carrier, digest v2 including exact nested +manifest state, recursive secure capture, per-edge verification, canonical +cycle diagnostics, same-pin diamond deduplication, divergent-pin rejection, +fixed resource bounds, restricted v2 YAML, portable path limits, hard-link and +mount/reparse rejection, and a union of every read-only physical root. The +inherits version governs the optional matching-version overrides section. V1 +parsing, digest vectors, findings, and output remain exact. + +### Contextual resolution and override graph + +Replace the singular parent identity with a verified source graph. Resolve +authored references in their owning source's visibility context, expose global +source qualification, retain aliases only on declaring edges, allow legal +cross-source equal ids, and compile explicit local-replacement override chains. +Represent a converged bare relationship with its complete sorted historical +candidate set and separate effective terminal. Normalize valid nested-v1 hops +without changing their local rules. Maintain separate catalog/historical and +root-effective/redirected relationship projections. + +### Root routing, validation, and enforcement + +Route every read command through the central closure. Validate each node in its +own context, collapse inherited errors without importing warnings, evaluate all +effective inherited scope and code constraints against root code, exclude every +materialisation from full/diff enumeration, and keep all writes root-local. + +### Closure generations and persistence + +Key the logical generation through ADR-148's exact SHA-256 v3 domain, frame-tag +table, ordering, limit block, root/closure bytes, node digests, topology, +override/terminal tables, and five subsystem fingerprints. Introduce store v3, +verify before every reuse, watch every config, manifest, corpus, and +materialisation root, and use full recomposition for graph changes until a +later byte-parity-proven acceleration is accepted. + +### MCP and bounded audit + +Keep the six-tool surface. Make global qualification, contextual relationships, +complete chain provenance, effective/history graph behavior, and stale-closure +refusal identical across cache modes. Preserve whole chains or return the hard +budget error. Keep audit to fixed returned path/source/layer/pin identity and +record every closure error exactly once with an empty returned set. + +### Export and Portal composition + +Deduplicate verified diamond records, preserve all override history and +source-aware endpoints, fail conflicting copies, and keep root-local export as +an explicit projection. Portal keys and routes remain `(source, id)` even when +three or more sources share an id. OKF and generated agent rules remain +root-local. + +### Evaluation and certification + +Extend the existing DecisionGrounding family with several direct parents, +transitive standards, diamonds, sibling alias collisions, equal-id hard +negatives, override chains, and high-inbound weak matches. Certify v1/no-manifest +compatibility, v2 digest vectors, every graph bound, path security, mutation +isolation, YAML structural bombs, overlong paths, hard links, mount/reparse +boundaries, unsupported filesystem identity, cold/warm/no-cache parity, MCP +budgets/audit, exports/Portal, and Linux/macOS/Windows containment. + +Implementation evidence now includes a committed track using the existing +DecisionGrounding scorer and metric gate: three direct parents, nested v1 and +v2 ancestry, a 40-artifact transitive standards source, a 32-inbound weak hard +negative, combined source-neutral ranking, and transitive search/relationship +retrieval. The release certification matrix closes the remaining platform, +security, compatibility, cache, MCP, export, and containment evidence. + +### Profile guidance + +Update explicit `decided init --parent-corpus` guidance to show the v2 manifest +and `decided corpus digest --version 2` without creating a manifest, fetching a +source, or changing ordinary init/profile bytes. + +The opt-in guidance now recommends manifest version 2, the bounded `parents` +sequence, and the explicit version-2 digest command while retaining the +version-1 command for existing single-parent manifests. Focused CLI contracts +prove fresh, idempotent, profiled, and unrequested flows; no manifest or parent +bytes are written. + +### Operator observability and portable example + +Expose the retained graph through `decided corpus status` with human and stable +JSON forms, including exact verification state, logical nodes, canonical pins, +source and physical routes, edges, relative materialisation paths, projection +counts, override counts, graph depth, and read-only boundaries. Add +`decided corpus explain` as a direct projection of the contextual resolver: +history, qualification, visible sources, aliases, selected record, effective +terminal, and complete override provenance. Publish a runnable four-source +diamond that validates without Git metadata or networking and produces no +checkout-dependent stable identity. ## Constraints -- The five ADR-089 non-negotiables govern the programme: available to all; - deterministic and offline; one writable child truth plus read-only inherited - parent; Git-native human-readable declaration; provenance end to end. -- Exactly one direct parent in the first increment. Multiple and transitive - inheritance fail loudly. -- The parent is materialised inside the child repository, with canonical path - containment and a verified full digest before overlay. -- One source-aware read model feeds every consumer. No command, MCP tool, or - enforcement surface merges directories independently. -- No source boost or implicit precedence enters resolution or ranking; the - v0.28 lexical graph-floor invariant remains in force. -- Inherited code scope is evaluated against the child code tree. Enforcement - cannot be disabled with `--local-only`. -- Parent writes and live network fetches are prohibited. -- Output changes are additive, except an internal persistent-store layout bump; - old caches degrade to misses. +- Materialised, reviewed local bytes only. No clone, fetch, refresh, registry, + or other network path enters federation. +- One writable invocation root; every inherited physical route is read-only. +- One exact captured closure and one source-contextual resolver feed every + consumer. +- No manifest order, graph depth, local layer, or source receives precedence or + ranking boost. +- Every graph node has explicit stable `corpus.source`; physical path and alias + are not identity. +- Complete provenance survives responses, findings, enforcement, audit + identity, and exports within their deliberately different bounds. +- Inherited scope and constraints evaluate against root code and cannot be + bypassed by a local-only mode. +- Fixed graph resource limits fail before overlay and never truncate. +- No-manifest and v1 compatibility are load-bearing release gates. +- Engine implementation MUST remain within accepted ADR-144 through ADR-148. +- Core semantics MUST remain forge-neutral. Repository materialisation and + optional review/check APIs belong outside federation and cannot affect pins, + identity, or resolution. ## Non-Goals -- Multiple parents, parent DAGs, or transitive inheritance. - Absolute, adjacent-checkout, URL, registry, or live-fetched parents. -- Cross-corpus writes or automatic parent refresh and pin updates. -- Enterprise-only behaviour, per-artifact ACLs, or a hosted control plane. -- Embeddings, semantic resolution, a vector index, or source-biased ranking. -- Point-in-time export, incremental feeds, and section anchors; those remain in - `corpus-sync`. -- A new MCP tool; any additive wording must remain within the measured standing - MCP surface budget. +- Automatic materialisation refresh, pin updates, or cross-corpus writes. +- Manifest-order precedence, child-wins, parent-wins, depth-wins, or source + ranking preference. +- Enterprise-only behavior, per-artifact ACLs, or a hosted control plane. +- Embeddings, semantic identity resolution, vector indexing, or probabilistic + source selection. +- A new MCP tool or unbounded audit/provenance records. +- Point-in-time export, change feeds, and section anchors from `corpus-sync`. +- Cross-revision graph materialisation for Watchkeeper in this programme. +- Federated OKF or generated agent-rule output without a separate decision. +- Forge authentication, repository creation, push, pull-request creation, or + checks APIs. Cursor Origin and GitHub may have optional adapters outside core. ## Success Measures -- A child with one vendored or submodule-backed parent validates, resolves, - retrieves, serves, and enforces fully offline with byte-identical output - across two clones. -- Missing, escaping, source-mismatched, transitive, and stale parent fixtures - fail before overlay with distinct stable findings. -- A child relationship to a parent Decision resolves; source and pin survive - CLI, MCP, audit, finding, and export serialization without copying artifact - content into the audit record. -- An inherited Decision scoped to a child path appears in `decisions-for` and - fails a violating child code change through the ordinary Sentry engine. -- Collision and override fixtures prove there is no resolution-order - precedence and that parent history remains qualified-addressable. -- A large-parent DecisionGrounding fixture includes hard negatives and proves - the v0.28 lexical floor still protects the stronger match under the existing - response budget. -- The parent tree is byte-unchanged after every command, old index segments - rebuild as cache misses, and federation leaves contemporaneous no-manifest - goldens byte-identical. +- A root with at least three direct parents and a transitive same-pin diamond + validates, resolves, retrieves, serves, exports, and enforces fully offline + with byte-identical output across two clones and root-list permutations. + Nested-list permutations preserve effective semantics after bottom-up + repinning while pin/generation provenance changes as authenticated. +- Missing, escaping, cyclic, duplicate, divergent-pin, stale, oversized, and + invalid-override fixtures fail before overlay with deterministic sourced + findings. +- An inherited Decision from every tested depth appears in path routing and + grounding and fails a violating root code change through ordinary Sentry and + Gate output. +- Equal ids in three sources remain independently qualified and Portal-visible; + bare use is ambiguous until an explicit complete convergence exists. +- An override chain and reconciled diamond preserve every original, + intermediate, rationale, mapping owner, and terminal while only the terminal + affects live ranking and enforcement. +- Diamond artifacts and relationships count once in retrieval and export, while + every physical copy is verified and protected from writes. +- A changed leaf, nested manifest, edge, pin, or override invalidates resident + and persistent state; cold, warm, cache-disabled, and rebuilt answers agree. +- The six MCP tools remain within their hard budgets, whole chain provenance is + never partially emitted, and closure errors produce one empty-return audit + event. +- Complete no-manifest and v1 CLI, MCP, export, cache, and enforcement goldens + remain unchanged. +- Status and explain reports are deterministic, machine-readable, derived from + the central verified graph, and fail before partial output on stale or + tampered routes. The public diamond example passes those reports without a + `.git` directory. ## Assumptions -- One materialised parent covers the initial organisation-standards use case. -- ADR-117's shared-endpoint topology remains the unmerged reach path and can - coexist during migration. -- Export schemas and source identity land before federation publishes - source-aware records. -- Implementation remains within the accepted federation ADR set; any change to - those boundaries requires a new or superseding decision. +- The one-parent implementation is retained as v1 compatibility substrate, not + discarded or silently broadened. +- Submodules and vendored directories inside each declaring repository cover + the graph materialisation model. +- Source identity and export schemas remain available before graph output is + published. +- ADR-144 through ADR-148 were explicitly ratified and their authority + transitions were recorded before engine work left the v1 boundary. ## Risks -- Source-aware identity is added to one surface but omitted from another. - Mitigation: the central layer set feeds all derivations, and acceptance spans - CLI, MCP, enforcement, cache, audit, and export. -- A large parent changes ranking or exhausts the response budget. Mitigation: - one combined deterministic ranking, no source boost, the graph-floor gate, - hard budgets, and a federation evaluation fixture. -- Pin verification or watcher invalidation is weakened for performance. - Mitigation: verification precedes overlay and the pin is part of generation - and cache identity. -- Overrides become undocumented local exceptions. Mitigation: every override - requires a local same-type replacement and a live local Decision rationale, - both validated and exported as provenance. -- The first increment expands into arbitrary federation. Mitigation: multiple, - transitive, external, and live parents are explicit Non-Goals requiring new - decisions. +- Singular parent assumptions survive in one consumer. Mitigation: activate all + reads through `VerifiedFederation` and certify cache-on/off plus cold/warm + parity across the complete surface. +- Contextual aliases are flattened globally. Mitigation: resolution is keyed by + authoring source and tests add siblings with the same alias spelling. +- A diamond copy or branch exception is silently preferred. Mitigation: verify + all physical routes, deduplicate only same source+pin, and require explicit + policy convergence. +- Graph verification creates unbounded work. Mitigation: fixed topology, file, + logical-byte, physical-byte, and filesystem-entry limits with boundary tests. +- History leaks into live ranking or enforcement. Mitigation: separate catalog + and effective artifacts and relationship endpoints. +- Implementation drifts beyond the accepted graph contract. Mitigation: each + engine PR traces behavior to ADR-144 through ADR-148 and the ratified design. ## Related Decisions - adr-002 -- adr-005 - adr-007 -- adr-016 - adr-018 - adr-026 -- adr-033 -- adr-055 - adr-065 -- adr-066 - adr-080 - adr-085 - adr-088 @@ -204,30 +298,27 @@ and export view, never an MCP or enforcement bypass. - adr-093 - adr-094 - adr-103 -- adr-104 -- adr-105 -- adr-112 -- adr-117 -- adr-119 -- adr-121 - adr-123 - adr-127 - adr-128 -- adr-133 - adr-134 - adr-135 -- adr-136 - adr-137 - adr-138 - adr-139 - adr-140 - adr-141 - adr-142 -- adr-143 +- adr-144 +- adr-145 +- adr-146 +- adr-147 +- adr-148 ## Related Designs - corpus-federation-mechanism +- corpus-federation-graph-composition - code-scope-consumption - sentry-code-constraint-evaluation diff --git a/decisions/roadmaps/corpus-sync.md b/decisions/roadmaps/corpus-sync.md index a275baa5..c24495a4 100644 --- a/decisions/roadmaps/corpus-sync.md +++ b/decisions/roadmaps/corpus-sync.md @@ -114,9 +114,10 @@ explicit `corpus.source` first, repository key next, directory basename as the compatibility fallback — plus a documented consumer-side aggregation recipe keyed on `(source, id)`. Federation requires the explicit value and reuses it, but source identity alone adds no inheritance or cross-corpus -resolution or validation (ADR-089 untouched). Identical parent records may -deduplicate only when their verified pins agree; different pins are an -aggregation conflict. +resolution or validation (ADR-089 untouched). Identical inherited records may +deduplicate across exports or a verified graph closure only when source, +canonical id, verified node pin, path, and body agree. A differing pin or body +is an aggregation conflict rather than last-writer-wins. ### Scale and retrieval evidence diff --git a/docs/cli.md b/docs/cli.md index e8a54236..791eb472 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -42,6 +42,7 @@ repin the parent. ```bash decided corpus digest --root vendor/standards --corpus decisions +decided corpus digest --version 2 --root vendor/standards --corpus decisions ``` `--root` is the parent repository root and bounds configuration discovery to @@ -70,6 +71,82 @@ escape, and traversed symlinks are rejected with stable `parent-corpus-*` errors. Exit `0` means the digest was calculated; exit `1` means the bounded materialisation could not be safely snapshotted. +Manifest version 2 uses the explicit `--version 2` command. Its +`sha256-v2:<64 lowercase hex>` digest also commits to whether +`.decided/corpus.md` is present and, when present, its exact bytes. Those bytes +bind the parent's own outgoing edges and pins, so graph updates are repinned +bottom-up. The owned Markdown snapshot excludes every direct materialisation +subtree declared by that manifest. The default command, version-1 digest, +`sha256:` prefix, and single-parent workflow remain unchanged. + +--- + +## corpus status + +Verify and inspect a manifest-version-2 federation closure from its current +local bytes: + +```bash +decided corpus status [DIRECTORY] +decided corpus status [DIRECTORY] --json +``` + +`DIRECTORY` defaults to `decisions/` when that conventional directory exists, +otherwise to the current directory. The command uses the same immutable graph +loader as validation, retrieval, enforcement, export, cache, and MCP. It does not +fetch, refresh, repin, or write anything. Output is produced only after every +physical route, configured source, canonical pin, inherited artifact, and +override has passed verification. + +The human report shows logical sources, canonical source routes, physical-route +counts, exact pins, edges, materialisation paths, artifact projections, +overrides, graph depth, and read-only boundaries. The stable JSON response has +`schema_version: "1"`, `status: "verified"`, and these top-level objects: + +- `verification` — no network access, exact pins verified, immutable snapshots; +- `summary` — source/edge/route/depth and catalog/effective/local counts; +- `sources[]` — identity, layer, pin, route, paths, counts, and writability; +- `edges[]` — owner-local alias, target, declared/canonical pin, and physical path; +- `read_only_roots[]` — every protected physical materialisation. + +Exit `0` means the complete closure verified. Exit `1` means the manifest is +not version 2 or verification failed; a closure failure emits no partial JSON. +Exit `2` means the requested directory is unavailable. + +--- + +## corpus explain + +Explain a source-contextual artifact resolution without changing the corpus: + +```bash +decided corpus explain [DIRECTORY] +decided corpus explain [DIRECTORY] --json +decided corpus explain [DIRECTORY] --from +``` + +`DIRECTORY` uses the same `decisions/`-then-current-directory default and the +context defaults to the root source. +`--from` evaluates the same reference from another immutable source in the +verified closure, which is useful when aliases or visible parents differ by +authoring context. + +The report distinguishes historical candidates, the selected source-owned +record, and the root-effective terminal. It lists context-visible sources and +direct aliases, then shows every applicable override's owner, target, +replacement, Decision rationale, and `overridden` / `replacement` / `lineage` +state. Qualified references select history while still showing its effective +terminal; unqualified references select the one converged terminal. + +The JSON response has `schema_version: "1"` and a stable `outcome`: +`resolved`, `ambiguous`, `not-found`, `unknown-context`, `invalid-reference`, or +`canonical-id-required`. A resolved result exits `0`. Every diagnostic outcome +still emits the complete human or JSON explanation and exits `1`. Loader and +pin failures fail closed before an explanation is rendered. + +See [Corpus Federation](federation.md) and the +[runnable diamond example](https://github.com/asdecided/core/tree/main/examples/federation). + --- ## validate @@ -845,17 +922,25 @@ artifacts — existing output is overwritten. - **Input:** `decided export [directory]` — scanned recursively for `*.md` (default: current directory). - **Modes:** *(default)* viewer JSON to stdout · `--html` (self-contained Portal file) · `--okf` (OKF v0.2 Markdown bundle) · `--documents` (JSONL for memory/RAG backends) · `--graph` (typed node+edge JSON for graph backends) · `--schema ` (the packaged JSON Schema, without reading a corpus) · `--agent-rules` (per-client agent-context files; see its own behaviour) -- **Options:** `--out ` (only `--html`/`--okf`/`--agent-rules`; the stdout modes are pipeable) · `--json` (no-op for the default mode) +- **Options:** `--out ` (only `--html`/`--okf`/`--agent-rules`; the stdout modes are pipeable) · `--json` (no-op for the default mode) · `--local-only` (viewer/HTML, documents, and graph projections only) - **Exit codes:** `0` success · `2` not a directory, or `--out` given to a stdout mode ```bash decided export decisions/ # viewer JSON to stdout decided export decisions/ --documents # JSONL, one record per artifact decided export decisions/ --graph # typed node+edge graph +decided export decisions/ --local-only # writable child records only decided export --schema documents # Draft 2020-12 record schema decided export decisions/ --html --out asdecided.html ``` +When `.decided/corpus.md` declares a verified parent, viewer, documents, and +graph exports include inherited records by default. Records and edges carry +their own source, layer, and verified-pin provenance; explicit overrides retain +both the parent history and local replacement. `--local-only` is a human +diagnostic/export projection of the writable child records. OKF bundles and +generated agent rules remain local-only and do not accept the flag. + The three machine-readable payload contracts, compatibility rules, and direct schema links are documented on the [Export contracts](export-contracts.md) page. @@ -1094,7 +1179,6 @@ decided templates --json } ``` - --- ## init @@ -1120,7 +1204,7 @@ for fallback and aggregation behaviour. - **Input:** `decided init [directory]` — defaults to the current directory. - **Options:** `--key KEY` (default `RAC`; 2–10 uppercase alphanumeric characters starting with a letter) · `--ticketing PROVIDER` · `--profile NAME` - · `--org-endpoint URL` · `--json` + · `--org-endpoint URL` · `--parent-corpus` · `--json` - **`--ticketing PROVIDER`** records the external ticketing system for `## Related Tickets` references (ADR-087) as `ticketing.provider` in `.decided/config.yaml` — one of `jira`, `github`, `linear`, `azure-devops`, @@ -1139,8 +1223,8 @@ for fallback and aggregation behaviour. Profiles are creation-time configuration, composable with `--key`/`--ticketing` and the [`quickstart`](#quickstart) scaffold. Plain `decided init` (no `--profile`) - is unchanged. A parent-corpus line is added once corpus federation ships - (ADR-089); until then the enterprise profile is hollow on it. + is unchanged. Parent-corpus setup is available separately through the explicit + `--parent-corpus` request; no profile creates a live inheritance declaration. - **`--org-endpoint URL`** wires the shared **org AsDecided endpoint** (ADR-117): it ensures an `asdecided-org` entry — `{"type": "http", "url": URL}` — under `mcpServers` in `.mcp.json` and `.cursor/mcp.json`. Unlike a profile, org @@ -1150,6 +1234,34 @@ for fallback and aggregation behaviour. same URL writes nothing. The URL must start with `http://` or `https://`. Composes with `--profile` (local `asdecided` and `asdecided-org` side by side). See [Org Grounding](org-grounding.md). +- **`--parent-corpus`** prints deterministic setup guidance for the operational + `.decided/corpus.md` manifest, including the exact `## inherits` and + `## overrides` headings. Its recommended version-2 flow tells you to + materialise every parent inside the repository first, calculate each pin with + `decided corpus digest --version 2 --root --corpus + `, and declare one to 32 records in the `parents` sequence. + It also retains the original version-1 digest command for existing + single-parent manifests. The flag works on fresh and already-initialized + repositories, with or without a profile. It is guidance only: it never + creates the manifest, fetches a parent, or writes parent bytes. Without the + flag, init files and human/JSON output are unchanged. + + The version-2 manifest it describes has this shape (repeat the parent record + as needed; list order grants no precedence): + + ````markdown + ## inherits + + ```yaml + version: 2 + parents: + - alias: standards + source: acme/standards + root: vendor/standards + corpus: decisions + digest: sha256-v2:<64-lowercase-hex> + ``` + ```` - **Exit codes:** `0` initialized, or already initialized with the same key (idempotent) · `1` a different key is already established (never silently rewritten), or a client config exists but cannot be merged into (malformed @@ -1167,6 +1279,7 @@ decided init --key PROJ decided init --key ACME --ticketing jira decided init --key ACME --profile enterprise decided init --org-endpoint https://asdecided.example.com/mcp +decided init --parent-corpus decided init docs/ --json ``` @@ -1182,6 +1295,24 @@ decided init docs/ --json } ``` +With `--parent-corpus --json`, the response additionally includes: + +```json +{ + "parent_corpus_guidance": { + "materialise_first": true, + "manifest": ".decided/corpus.md", + "inherits_heading": "## inherits", + "overrides_heading": "## overrides", + "digest_command": "decided corpus digest --root --corpus ", + "recommended_manifest_version": 2, + "parents_field": "parents", + "multiple_parents": true, + "digest_command_v2": "decided corpus digest --version 2 --root --corpus " + } +} +``` + --- diff --git a/docs/examples.md b/docs/examples.md index 2c8541e3..744754a0 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -4,6 +4,12 @@ Short, realistic artifacts you can adapt. These show the *shape* of each type blank starter with section guidance, use `decided schema --template` ([cli.md](cli.md#schema)). +For a complete repository-shaped example, see +[`examples/federation/`](https://github.com/asdecided/core/tree/main/examples/federation): +two direct parents, a shared diamond ancestor, exact pins, and a reviewed +override that can be inspected with `decided corpus status` and +`decided corpus explain`. + ## A requirement ```markdown diff --git a/docs/export-contracts.md b/docs/export-contracts.md index eea0f42c..5a4bf100 100644 --- a/docs/export-contracts.md +++ b/docs/export-contracts.md @@ -29,8 +29,10 @@ The default `decided export` projection is one JSON object containing: - `schema_version` - `corpus`: `name`, `source`, `rac_version`, and `artifact_count` - `artifacts[]`: `id`, `aliases`, `type`, `status`, `title`, `path`, and - `body_html` -- `relationships[]`: `from`, `to`, and the flattened `relates-to` `type` + `body_html`; manifest-backed exports add record `provenance` +- `relationships[]`: `from`, `to`, and the flattened `relates-to` `type`; + manifest-backed exports add source-aware `from_identity`, `to_identity`, and + edge `provenance` `rac_version` is a retained v1 machine key. It carries the version of the AsDecided CLI that produced the payload; it is not a current product or command @@ -47,7 +49,8 @@ non-empty line is independently validated against the documents schema and contains: - `schema_version`, `id`, `type`, `status`, `title`, and Markdown `text` -- `metadata`: `path`, `aliases`, `tags`, and `source` +- `metadata`: `path`, `aliases`, `tags`, and the record-owning `source`; + manifest-backed exports add `provenance` The schema describes one line. A consumer should split the UTF-8 stream on line boundaries and validate each record separately. @@ -57,7 +60,9 @@ boundaries and validate each record separately. `decided export --graph` is one JSON object containing `schema_version`, `source`, `nodes`, and `edges`. Nodes carry `id`, `type`, `status`, and `title`. Edges carry `source`, `target`, `type`, `directed`, `resolved`, `external`, and -nullable `provider` provenance. +nullable `provider` provenance. Manifest-backed nodes and edges add +`provenance`; edges also add source-aware `source_identity` and nullable +`target_identity` objects alongside the retained ID fields. The graph edge `type` is the engine's real relationship kind. It is not the viewer projection's flattened `relates-to` value. @@ -85,11 +90,18 @@ For a non-federated export, AsDecided derives the source in this order: 2. the lower-case `repository_key`; 3. the existing corpus-directory basename when neither value is configured. -The viewer exposes the value as `corpus.source`. Documents records expose it -as `metadata.source`; the graph exposes it as its top-level `source`. A graph -edge's own `source` field remains the source *node ID* and is not corpus -provenance. `corpus.name` remains the existing display value and is not an -identity. +The viewer exposes the child value as `corpus.source`. Documents records expose +their owning value as `metadata.source`; the graph retains the child value as +its top-level `source`. In a manifest-backed export, each record's `provenance` +object carries its own `source` and `layer`, plus the full verified `pin` for an +inherited record. A graph edge's existing `source` field remains the source +*node ID* and is not corpus provenance. `corpus.name` remains the existing +display value and is not an identity. + +Override provenance is an ordered `provenance.overrides` array. Each entry +names its `overridden` or `replacement` role and the source-aware `parent`, +`replacement`, and live local `rationale` identities. The original inherited +record and its local replacement are both exported. The repository key continues to namespace newly generated artifact IDs. It is not globally unique, and different corpora may legitimately use the same key. @@ -99,11 +111,10 @@ never relies on either fallback. ## Aggregating corpora Consumers aggregate documents streams by concatenating their records and -keying each artifact on `(metadata.source, id)`. They aggregate graph exports -by lifting the graph's top-level source onto every node: a node key is -`(graph.source, node.id)`, and each edge endpoint is resolved in that same -namespace before the node and edge sets are unioned. The viewer's -`corpus.source` provides the equivalent namespace for its artifacts. +keying each artifact on `(metadata.source, id)`. For a manifest-backed viewer +or graph export, use `(provenance.source, id)` on every artifact or node and the +explicit source-aware identity objects on edges. The top-level child source +remains the fallback namespace for a non-federated payload. Configure distinct explicit sources whenever repository-key or basename fallbacks could collide. Source identity alone does not make cross-corpus @@ -116,6 +127,10 @@ canonical ID, record body, and verified pin all agree. A different body or pin for the same `(source, id)` is an aggregation conflict, never a last-writer-wins update. +Viewer, documents, and graph exports include the inherited layer by default. +`--local-only` requests the child projection for these modes only. OKF bundles +and generated agent rules remain local-only in the first federation increment. + ### Migration from basename sources Before this contract, documents and graph exports stamped the corpus-directory @@ -135,8 +150,10 @@ All schema objects allow unknown additional properties. This is intentional: an additive producer release must remain readable by an existing consumer. Consumers should ignore fields they do not understand. -Every field emitted today is nevertheless declared and required. Removing a -required field, changing its type incompatibly, or changing its meaning is a -breaking contract change and requires a `schema_version` bump plus a new -versioned schema file. Adding a field requires updating the current schema and -its producer drift test in the same change. +Every unconditional field emitted today is nevertheless declared and required. +Federation-only properties are declared but optional so a no-manifest payload +retains its released bytes. Removing a required field, changing its type +incompatibly, or changing its meaning is a breaking contract change and +requires a `schema_version` bump plus a new versioned schema file. Adding a +field requires updating the current schema and its producer drift test in the +same change. diff --git a/docs/federation.md b/docs/federation.md new file mode 100644 index 00000000..ca3767f7 --- /dev/null +++ b/docs/federation.md @@ -0,0 +1,150 @@ +# Corpus Federation + +Corpus federation lets one repository inherit several independently owned +AsDecided corpora without flattening their identity or moving policy into a +hosted service. A root may have up to 32 direct parents; parents may have their +own parents, including shared ancestors reached through a diamond. + +The contract is deliberately local and deterministic: + +- every source is materialised inside the repository that declares it; +- every edge names a stable `corpus.source` and an exact content pin; +- the complete bounded graph is verified before any artifact is exposed; +- inherited sources and every physical route are read-only; +- no validation, lookup, enforcement, export, or MCP path performs networking; +- the root repository remains the only writable authoring layer. + +## Manifest version 2 + +The root declares its direct parents in `.decided/corpus.md`: + +````markdown +# Corpus + +## inherits + +```yaml +version: 2 +parents: + - alias: platform + source: example/platform + root: vendor/platform + corpus: decisions + digest: sha256-v2:<64 lowercase hexadecimal characters> + - alias: security + source: example/security + root: vendor/security + corpus: decisions + digest: sha256-v2:<64 lowercase hexadecimal characters> +``` +```` + +Each `root` is relative to the repository containing that manifest. The parent +must declare the same `corpus.source` in its own `.decided/config.yaml`. Parent +order grants no precedence. + +A version-2 digest authenticates the source identity, exact config bytes, +nested-manifest presence and bytes, and every owned Markdown path and body. It +excludes nested materialisation subtrees, so graph updates are pinned from the +leaves upward: + +```bash +decided corpus digest --version 2 \ + --root vendor/platform/vendor/shared \ + --corpus decisions + +decided corpus digest --version 2 \ + --root vendor/platform \ + --corpus decisions +``` + +AsDecided never performs either materialisation or repinning automatically. + +## Inspect the verified closure + +`corpus status` verifies the complete closure from current local bytes before +rendering anything: + +```bash +decided corpus status decisions/ +decided corpus status decisions/ --json +``` + +The report includes: + +- every logical source, layer, canonical pin, manifest version, and artifact count; +- the canonical source route and exact physical-route count; +- every declared edge, alias, pin, and materialisation path; +- catalog, root-effective, and root-local projection counts; +- override count, graph depth, and every read-only boundary. + +The stable JSON form has `schema_version: "1"`. A successful report states +`pins_verified: true`, `network_access: false`, and +`immutable_snapshots: true`. If either side of a same-source diamond is +tampered, verification fails before stdout is emitted. + +## Explain a resolution + +`corpus explain` shows why a reference selects its answer: + +```bash +decided corpus explain example/shared::SHR-01K000000001 decisions/ +decided corpus explain SHR-01K000000001 decisions/ --json +decided corpus explain shared::SHR-01K000000001 decisions/ \ + --from example/platform +``` + +The report separates immutable historical candidates from the effective +terminal. It identifies whether the reference was qualified, lists visible +sources and direct aliases in the selected source context, and retains every +explicit override's owner, target, replacement, rationale, and provenance +state. Ambiguous and missing results still emit a structured explanation but +return exit code `1`. + +Aliases are local to the source that declares the edge. A root cannot use a +transitive parent's alias as if it were global; use the durable +`source::canonical-id` form when crossing that boundary. + +## Diamonds and overrides + +Every physical edge is independently verified. If two routes reach the same +source with the same canonical pin, AsDecided retains both verified routes but +deduplicates their content into one logical source. Different pins for the same +source fail as a divergent graph instead of silently choosing one copy. + +Equal artifact IDs in different sources remain different records. Bare lookup +is legal only when the visible historical candidates resolve to one effective +terminal. Policy replacement is never implicit: a manifest must map a globally +qualified inherited target to a same-type local artifact and cite a live local +Decision as its rationale. + +## Git, not GitHub + +Federation consumes a working tree; it does not depend on a particular forge, +remote name, default branch, pull-request API, or `.git` directory. A corpus can +be local-only or cloned from any standard Git remote. GitHub integrations such +as Actions and Code Scanning are optional delivery conveniences, not corpus +storage or resolution authority. + +[Cursor Origin exposes ordinary Git clone, pull, and push](https://cursor.com/docs/origin/git), +so an Origin-native repository can hold exactly the same corpus without a core +engine adapter. Future forge-specific operations—creating repositories, +reviews, or checks—belong in optional integrations outside federation and must +not change local resolution. + +## Runnable example + +[`examples/federation/`](https://github.com/asdecided/core/tree/main/examples/federation) +contains a complete four-source diamond with two direct parents, two physical +copies of one shared source, and an explicit application override. It is +already materialised and pinned, so the status, explain, and validation commands +can be run directly after cloning the repository. + +## Compatibility and limits + +Repositories without a manifest retain single-corpus behavior. Manifest +version 1 retains its exact one-parent format, `sha256:` digest, and released +semantics. Version 2 is bounded to a depth of 16, 256 inherited sources, 1,024 +edges, 4,096 overrides, and fixed file, byte, path, and YAML limits. Cycles, +duplicate direct sources, escaping paths, unsupported filesystem boundaries, +invalid inherited artifacts, stale pins, and limit excesses fail closed. diff --git a/docs/quickstart.md b/docs/quickstart.md index f6580e25..17e5be92 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -30,7 +30,7 @@ In CI, pin a release tag rather than `latest`, or pin by digest for immutable builds (the release run prints the pushed digest in its summary): ```bash -docker pull ghcr.io/asdecided/core:v0.28.0 +docker pull ghcr.io/asdecided/core:v0.29.0 docker pull ghcr.io/asdecided/core@sha256: ``` @@ -41,7 +41,7 @@ can run script steps): ```yaml rac-gate: image: - name: ghcr.io/asdecided/core:v0.28.0 + name: ghcr.io/asdecided/core:v0.29.0 entrypoint: [""] script: - decided gate decisions/ @@ -55,7 +55,7 @@ pipelines: '**': - step: name: decided gate - image: ghcr.io/asdecided/core:v0.28.0 + image: ghcr.io/asdecided/core:v0.29.0 script: - decided gate decisions/ ``` @@ -64,7 +64,7 @@ Jenkins (declarative pipeline, docker agent): ```groovy pipeline { - agent { docker { image 'ghcr.io/asdecided/core:v0.28.0' } } + agent { docker { image 'ghcr.io/asdecided/core:v0.29.0' } } stages { stage('decided gate') { steps { sh 'decided gate decisions/' } diff --git a/docs/shared-server.md b/docs/shared-server.md index 597df283..5054e17a 100644 --- a/docs/shared-server.md +++ b/docs/shared-server.md @@ -1,20 +1,26 @@ # Operating a Shared AsDecided Server +This guide uses a branch named `main` and a remote named `origin` in examples. +Neither is normative: configure the reviewed ref and standard Git remote used +by your repository. Corpus semantics are forge-, remote-, and branch-name-neutral +under ADR-149. + By default AsDecided runs as one `decided-mcp` process per developer, over stdio, against that developer's own checkout. That is the right model for almost everyone and needs no operations at all. At organisation scale a team may instead want **one always-current endpoint** -every agent queries, so reads come from a single `main`-backed source of truth +every agent queries, so reads come from a single reviewed-branch checkout rather than checkouts that lag between pulls. This page is the recipe for standing that up: a container running the HTTP transport, an authenticating -proxy in front, a step that keeps the checkout current with `main`, and where +proxy in front, a step that keeps the checkout current with that ref, and where observability lives. It assumes you have read the [MCP Server](mcp.md) page. Everything here is deployment wrapper — containers, proxies, collectors — around an unchanged engine. AsDecided gains no hosted service, no database, and no authentication code; git stays the source of truth -([ADR-080](https://github.com/asdecided/core/blob/main/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md)). +([ADR-080](https://github.com/asdecided/core/blob/main/decisions/decisions/adr-080-single-source-of-truth-git-not-database.md), +[ADR-149](https://github.com/asdecided/core/blob/main/decisions/decisions/adr-149-git-repository-truth-is-forge-agnostic.md)). ## 1. Do you need it? diff --git a/examples/federation/README.md b/examples/federation/README.md new file mode 100644 index 00000000..af41dd4c --- /dev/null +++ b/examples/federation/README.md @@ -0,0 +1,36 @@ +# Federation example + +This repository-shaped example demonstrates AsDecided manifest version 2 with +two direct parents, a shared transitive source reached through a diamond, and +one explicit root override. + +```mermaid +flowchart TD + APP[example/app] -->|platform| PLATFORM[example/platform] + APP -->|security| SECURITY[example/security] + PLATFORM -->|shared| SHARED[example/shared] + SECURITY -->|shared| SHARED +``` + +Every source is already materialised beneath `app/vendor/`. The two physical +copies of `example/shared` have identical source identity and pins, so the +verified graph deduplicates them into one logical source while retaining both +routes as evidence. + +From this directory, run: + +```bash +decided corpus status app/decisions +decided corpus status app/decisions --json +decided corpus explain example/shared::SHR-01K000000001 app/decisions +decided validate app/decisions +``` + +The qualified explain command selects the immutable shared record, then shows +that the application corpus replaces it with `APP-01K000000001` under the reviewed +`ADR-01K000000001` rationale. An unqualified `SHR-01K000000001` lookup selects the effective +replacement directly. + +No command in this walkthrough fetches, refreshes, or repins a source. The +example works from any ordinary Git checkout: local-only, Cursor Origin, +GitHub, GitLab, Forgejo, or another forge does not change corpus semantics. diff --git a/examples/federation/app/.decided/config.yaml b/examples/federation/app/.decided/config.yaml new file mode 100644 index 00000000..861aa58a --- /dev/null +++ b/examples/federation/app/.decided/config.yaml @@ -0,0 +1,3 @@ +repository_key: APP +corpus: + source: example/app diff --git a/examples/federation/app/.decided/corpus.md b/examples/federation/app/.decided/corpus.md new file mode 100644 index 00000000..f3b68a2a --- /dev/null +++ b/examples/federation/app/.decided/corpus.md @@ -0,0 +1,28 @@ +# Application corpus + +## inherits + +```yaml +version: 2 +parents: + - alias: platform + source: example/platform + root: vendor/platform + corpus: decisions + digest: sha256-v2:9c0a54055aa8417089d100645d3e4e2288b6e9040ddb78b8797c8a70df5d3f63 + - alias: security + source: example/security + root: vendor/security + corpus: decisions + digest: sha256-v2:f9f225328f14138998246f91ea753a9771f8c903879f97ac9e4ba4707de60746 +``` + +## overrides + +```yaml +version: 2 +items: + - target: example/shared::SHR-01K000000001 + with: APP-01K000000001 + rationale: ADR-01K000000001 +``` diff --git a/examples/federation/app/decisions/application-override.md b/examples/federation/app/decisions/application-override.md new file mode 100644 index 00000000..fe7b1e0a --- /dev/null +++ b/examples/federation/app/decisions/application-override.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: ADR-01K000000001 +type: decision +--- +# Override the shared default + +## Status + +Accepted + +## Category + +Technical + +## Context + +The application has constraints that the organization-wide default does not cover. + +## Decision + +Replace `example/shared::SHR-01K000000001` with `APP-01K000000001` in the application projection. + +## Consequences + +The exception is explicit, reviewed, attributable, and visible in resolution provenance. diff --git a/examples/federation/app/decisions/application-policy.md b/examples/federation/app/decisions/application-policy.md new file mode 100644 index 00000000..d64c104e --- /dev/null +++ b/examples/federation/app/decisions/application-policy.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: APP-01K000000001 +type: decision +--- +# Application policy + +## Status + +Accepted + +## Category + +Technical + +## Context + +The application needs a narrower policy than the shared engineering default. + +## Decision + +The application uses its reviewed local policy as the effective terminal. + +## Consequences + +The shared record stays inspectable as history while application reads use this decision. diff --git a/examples/federation/app/vendor/platform/.decided/config.yaml b/examples/federation/app/vendor/platform/.decided/config.yaml new file mode 100644 index 00000000..323c3656 --- /dev/null +++ b/examples/federation/app/vendor/platform/.decided/config.yaml @@ -0,0 +1,3 @@ +repository_key: PLATFORM +corpus: + source: example/platform diff --git a/examples/federation/app/vendor/platform/.decided/corpus.md b/examples/federation/app/vendor/platform/.decided/corpus.md new file mode 100644 index 00000000..082a18dc --- /dev/null +++ b/examples/federation/app/vendor/platform/.decided/corpus.md @@ -0,0 +1,13 @@ +# Platform corpus + +## inherits + +```yaml +version: 2 +parents: + - alias: shared + source: example/shared + root: vendor/shared + corpus: decisions + digest: sha256-v2:6401342ceb2b37f098444874966d7ff126ff0470ae600c96d9644a94b217e40e +``` diff --git a/examples/federation/app/vendor/platform/decisions/platform-policy.md b/examples/federation/app/vendor/platform/decisions/platform-policy.md new file mode 100644 index 00000000..bc6ac6be --- /dev/null +++ b/examples/federation/app/vendor/platform/decisions/platform-policy.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: PLT-01K000000001 +type: decision +--- +# Platform policy + +## Status + +Accepted + +## Category + +Technical + +## Context + +Platform teams need a stable deployment baseline. + +## Decision + +Use repeatable builds for every deployment artifact. + +## Consequences + +Application teams inherit one attributable platform decision. diff --git a/examples/federation/app/vendor/platform/vendor/shared/.decided/config.yaml b/examples/federation/app/vendor/platform/vendor/shared/.decided/config.yaml new file mode 100644 index 00000000..98914c12 --- /dev/null +++ b/examples/federation/app/vendor/platform/vendor/shared/.decided/config.yaml @@ -0,0 +1,3 @@ +repository_key: SHARED +corpus: + source: example/shared diff --git a/examples/federation/app/vendor/platform/vendor/shared/decisions/shared-policy.md b/examples/federation/app/vendor/platform/vendor/shared/decisions/shared-policy.md new file mode 100644 index 00000000..ab8af6f2 --- /dev/null +++ b/examples/federation/app/vendor/platform/vendor/shared/decisions/shared-policy.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: SHR-01K000000001 +type: decision +--- +# Shared engineering policy + +## Status + +Accepted + +## Category + +Technical + +## Context + +Every engineering corpus needs a deterministic default. + +## Decision + +Use the shared engineering default unless a reviewed child decision overrides it. + +## Consequences + +The default remains traceable through every inheriting corpus. diff --git a/examples/federation/app/vendor/security/.decided/config.yaml b/examples/federation/app/vendor/security/.decided/config.yaml new file mode 100644 index 00000000..a931f0e2 --- /dev/null +++ b/examples/federation/app/vendor/security/.decided/config.yaml @@ -0,0 +1,3 @@ +repository_key: SECURITY +corpus: + source: example/security diff --git a/examples/federation/app/vendor/security/.decided/corpus.md b/examples/federation/app/vendor/security/.decided/corpus.md new file mode 100644 index 00000000..9b6dd111 --- /dev/null +++ b/examples/federation/app/vendor/security/.decided/corpus.md @@ -0,0 +1,13 @@ +# Security corpus + +## inherits + +```yaml +version: 2 +parents: + - alias: shared + source: example/shared + root: vendor/shared + corpus: decisions + digest: sha256-v2:6401342ceb2b37f098444874966d7ff126ff0470ae600c96d9644a94b217e40e +``` diff --git a/examples/federation/app/vendor/security/decisions/security-policy.md b/examples/federation/app/vendor/security/decisions/security-policy.md new file mode 100644 index 00000000..b76e3f0d --- /dev/null +++ b/examples/federation/app/vendor/security/decisions/security-policy.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: SEC-01K000000001 +type: decision +--- +# Security policy + +## Status + +Accepted + +## Category + +Technical + +## Context + +Application changes need a consistent security review boundary. + +## Decision + +Review authentication and authorization changes before merge. + +## Consequences + +Application teams inherit one attributable security decision. diff --git a/examples/federation/app/vendor/security/vendor/shared/.decided/config.yaml b/examples/federation/app/vendor/security/vendor/shared/.decided/config.yaml new file mode 100644 index 00000000..98914c12 --- /dev/null +++ b/examples/federation/app/vendor/security/vendor/shared/.decided/config.yaml @@ -0,0 +1,3 @@ +repository_key: SHARED +corpus: + source: example/shared diff --git a/examples/federation/app/vendor/security/vendor/shared/decisions/shared-policy.md b/examples/federation/app/vendor/security/vendor/shared/decisions/shared-policy.md new file mode 100644 index 00000000..ab8af6f2 --- /dev/null +++ b/examples/federation/app/vendor/security/vendor/shared/decisions/shared-policy.md @@ -0,0 +1,26 @@ +--- +schema_version: 1 +id: SHR-01K000000001 +type: decision +--- +# Shared engineering policy + +## Status + +Accepted + +## Category + +Technical + +## Context + +Every engineering corpus needs a deterministic default. + +## Decision + +Use the shared engineering default unless a reviewed child decision overrides it. + +## Consequences + +The default remains traceable through every inheriting corpus. diff --git a/mkdocs.yml b/mkdocs.yml index 699ba1a5..0d404a8b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ nav: - Shared Server: shared-server.md - Deployment Hardening: deployment-hardening.md - Org Grounding: org-grounding.md + - Corpus Federation: federation.md - CLI Reference: cli.md - Export Contracts: export-contracts.md - Context Cost: context-cost.md diff --git a/rac-localview/VIEWER_CONTRACT.md b/rac-localview/VIEWER_CONTRACT.md index b99ac55c..9df92091 100644 --- a/rac-localview/VIEWER_CONTRACT.md +++ b/rac-localview/VIEWER_CONTRACT.md @@ -32,7 +32,11 @@ A single JSON document, as emitted by `decided export --json`. "status": "Accepted", "title": "ADR-027: CI test topology", "path": "decisions/decisions/adr-027-ci-test-topology.md", - "body_html": "

" + "body_html": "

", + "provenance": { + "source": "asdecided/core", + "layer": "local" + } } ], "relationships": [ @@ -68,13 +72,14 @@ Ordered by `path`. | field | type | meaning | | ----------- | -------- | ------------------------------------------------------ | -| `id` | string | Opaque stable artifact ID, unique within the corpus (`RAC-KTQ63DSC8SZW`). | +| `id` | string | Opaque stable artifact ID, unique within its owning source (`RAC-KTQ63DSC8SZW`). | | `aliases` | string[] | Human aliases as emitted by Core identity, e.g. `["adr-027", "adr-027-ci-test-topology"]`. May be empty. | | `type` | string | Artifact family (`decision`, `requirement`, …). Open set; the viewer derives its type filter from the values present. | | `status` | string | Lifecycle status in its authored casing (`Accepted`, `Proposed`, `Superseded`, …). Open set — see case handling below. | | `title` | string | Plain text. | | `path` | string | Source path within the repository. Shown as a muted provenance line on the detail view. | | `body_html` | string | The artifact body **rendered to HTML at export time** (see trust model). | +| `provenance` | object | Optional on legacy/no-manifest payloads. Manifest-backed records carry owning `source`, `layer`, inherited `pin`, and any ordered override mappings. | #### Alias display @@ -83,7 +88,10 @@ a **display name**: deterministically, the first alias that differs from the `id`, else the `id` itself. The display name is used on list rows, the detail heading, and related-artifact links; the opaque `id` stays visible on the detail view's provenance line (alongside `path`) -and remains the routing key (`#/artifact/`). +and remains the legacy routing key (`#/artifact/`). For manifest-backed +records, the viewer keys and routes on `(provenance.source, id)`, encoded as one +`::` hash segment. This retains both records in a valid same-ID +override. A payload without provenance keeps its exact bare-ID routes. #### Status case handling @@ -96,11 +104,14 @@ render plain. ### `relationships[]` — edges -Each edge is `{ "from": ID, "to": ID-or-alias, "type": string }` and -reads "`from` `type` `to`". Ordered by (from, to). Core emits **only** -`relates-to`; richer edge typing is a future Core decision. `to` may be -an unresolved alias preserved verbatim — the viewer renders those as -"(not in corpus)" rather than dropping them. +Each edge retains `{ "from": ID, "to": ID-or-alias, "type": string }` and +reads "`from` `type` `to`". A manifest-backed edge also carries +`from_identity` and nullable `to_identity` `{source,id}` objects plus the +declaring artifact's provenance. The viewer uses those identities for graph, +inbound/outbound, and detail links. Ordered by source-aware endpoint identity. +Core emits **only** `relates-to`; richer edge typing is a future Core decision. +`to` may be an unresolved alias preserved verbatim — the viewer renders those +as "(not in corpus)" rather than dropping them. The type set stays open for forward compatibility. The viewer keeps inverse labels for types a future Core might emit (accepted if they @@ -216,7 +227,7 @@ cited ids and aliases in text nodes are linkified). The viewer performs ## 4. Viewer behaviour summary - Read-only; no router dependency — state is hash-based - (`#/` list, `#/artifact/` detail) so deep links work from + (`#/` list, `#/artifact/` detail) so deep links work from `file://`. - List view: every artifact as a row (display name + title + chips); filter toggles for type and status derived from the corpus (status diff --git a/rac-localview/src/viewer/App.tsx b/rac-localview/src/viewer/App.tsx index 32791e4f..4a57d015 100644 --- a/rac-localview/src/viewer/App.tsx +++ b/rac-localview/src/viewer/App.tsx @@ -64,21 +64,24 @@ export function App() { () => (data ? buildIndex(data) : null), [data], ); + const indexRef = useRef(index); + indexRef.current = index; // Editor-host bridge (v0.21.7): announce readiness and apply the host's // reveal requests. Inert in a standalone Portal (no host). useEffect(() => { const unsubscribe = onRevealArtifact((id) => { - setActiveId(id); + const key = indexRef.current?.citationLookup.get(id.toLowerCase()) ?? id; + setActiveId(key); // In the graph view a reveal just roots/highlights the node; it does not // navigate away. Elsewhere it opens the detail page, as before. if (viewRef.current === 'graph') return; - const target = `#/artifact/${encodeURIComponent(id)}`; + const target = `#/artifact/${encodeURIComponent(key)}`; if (window.location.hash === target) { revealedRef.current = null; // already here — nothing to suppress return; } - revealedRef.current = id; + revealedRef.current = key; window.location.hash = target; }); postReady(); diff --git a/rac-localview/src/viewer/DetailView.tsx b/rac-localview/src/viewer/DetailView.tsx index 7c80e0ca..49a42222 100644 --- a/rac-localview/src/viewer/DetailView.tsx +++ b/rac-localview/src/viewer/DetailView.tsx @@ -1,7 +1,12 @@ import { useEffect, useRef } from 'react'; import { KeyboardHint, Panel } from '../components'; import type { CorpusIndex } from './data'; -import { displayName, linkifyCitations } from './data'; +import { + displayName, + linkifyCitations, + relationshipSourceKey, + relationshipTargetKey, +} from './data'; import type { Relationship } from './types'; import { ArtifactChips } from './chips'; @@ -55,10 +60,14 @@ function RelatedGroup({ heading, edges, index, direction }: RelatedGroupProps) {