From cb7e3293d42366b9bdae9dbc7d7692d80bf225c3 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 29 Jul 2026 20:29:08 +0000 Subject: [PATCH] Give roadmap nodes a durable identity as a required field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A roadmap node's id was its authored slug, so a rename was structurally a delete plus a create: edges stop resolving, acceptance receipts name a node that no longer exists, and any attempt or session joined to it is orphaned. Not hypothetical — the 2026-07-27 authority refresh deleted the node carrying the only execution contract and no claim noticed. std.occurrence_identity already states the right law: identity may not derive from filename, span, authored name, structural equality or content hash. The LAW is reused; the TYPE deliberately is not. OccurrenceId is an ordinal from a per-graph allocator starting at zero, so inserting a node at the front renumbers everything after it and two branches mint the same integer — positional identity wearing an allocator's clothes. Right for occurrences inside one parse, wrong for an entity that must survive edits, reordering, regeneration and independent branches. What occurrence identity does not claim is PERSISTENCE SCOPE, and that is the only thing missing. IDENTITY IS A REQUIRED FIELD, NOT A SIDE REGISTRY. The first cut carried a hand-written (identity, slug) list beside the authority. It compiled cleanly and was wrong twice over: two authorities that must be kept in step (DESIGN 3), and a node still authorable WITHOUT an identity, detectable only by a later witness — validation standing where construction was available (DESIGN 5). As a field it is unwritable instead, and the index is DERIVED from declared_roadmap_nodes() so the two cannot disagree. The change measured its own necessity: adding the field produced 50 hard failures — 28 struct literals the compiler rejected outright, then 22 constructor call sites failing with `undefined variable: identity`. Every one was a site where a node could previously come into existence without durable identity, and the side registry would have compiled past all fifty. 62 collision-resistant identities are minted and persisted at AUTHORING time. Nothing mints during render, compile or regen; a value re-derivable by evaluation would reintroduce the property this exists to remove. TOMBSTONES, because a base-versus-head diff alone cannot enforce non-reuse. Without persisted retirement: PR A removes node X, a diff observes the removal once, after merge X is simply absent from base, PR B reuses X's identity, and the next comparison sees present-in-head/absent-in-base — indistinguishable from a legitimately new identity. The reuse passes every check. So retirement is a STATE: TombstonedIdentity keeps the identity forever, carries the slug it last wore so historical links resolve, and names its successor. The roster is empty today and the witnesses are arithmetic (registry == active + tombstoned) rather than pinning zero, so the first real retirement will not red them. RESOLUTION IS EXACTLY-ONE. filter-map-first answers a duplicate registry with a plausible first row — the fabricated-plausible-output arm DESIGN 5 forbids, and it fails exactly when the registry is corrupt and a confident wrong answer is worst. IdentityResolved | IdentityUnknown | IdentityAmbiguous{matches} keeps the three remedies distinct. The discriminating witness is identity_survives_a_slug_rename. Every other claim here — uniqueness, totality, resolution — is equally true of a broken registry where identity IS the slug; only this one fails if the separation collapses. STILL OWED: the permanence gate. Every claim reads the current tree, so an identity silently REPLACED between base and head satisfies all of them. The carrier lists the admitted and refused transitions concretely; the tombstone above is what makes that gate implementable at all. ROADMAP.md is unchanged: identity is machinery, the slug remains display. Verified: whole-tree compile EXIT=0, 0 blocking errors; ROADMAP.md regenerated; 241 witnesses green across 15 roadmap suites. Seven wet witnesses fail identically on plain main (hermetic mode refusing to fabricate a host Check) and are unrelated — confirmed by running them on origin/main before attributing. Co-Authored-By: Claude Opus 5 (1M context) --- dag/gunbc/roadmap_authority.dag | 100 +++++++++++++++-- dag/gunbc/roadmap_identity.dag | 99 +++++++++++++++++ dag/gunbc/roadmap_model.dag | 5 + .../claim/roadmap_altitude_witness_test.dag | 7 +- dag/test/claim/roadmap_authority_test.dag | 1 + .../roadmap_belt_actuate_witness_test.dag | 2 + dag/test/claim/roadmap_belt_witness_test.dag | 1 + ...roadmap_dispatch_actuator_witness_test.dag | 3 +- dag/test/claim/roadmap_document_test.dag | 3 + dag/test/claim/roadmap_emit_test.dag | 4 + .../claim/roadmap_frontier_witness_test.dag | 5 + dag/test/claim/roadmap_gate_test.dag | 12 +- .../claim/roadmap_identity_witness_test.dag | 103 ++++++++++++++++++ dag/test/claim/roadmap_model_test.dag | 3 + dag/test/claim/roadmap_page_witness_test.dag | 31 +++--- .../claim/roadmap_spawner_witness_test.dag | 6 + 16 files changed, 350 insertions(+), 35 deletions(-) create mode 100644 dag/gunbc/roadmap_identity.dag create mode 100644 dag/test/claim/roadmap_identity_witness_test.dag diff --git a/dag/gunbc/roadmap_authority.dag b/dag/gunbc/roadmap_authority.dag index 191df349333..33273d06956 100644 --- a/dag/gunbc/roadmap_authority.dag +++ b/dag/gunbc/roadmap_authority.dag @@ -8,6 +8,7 @@ import gunbc.roadmap_document { import std.types { ContentHash } import gunbc.roadmap_model { RoadmapNode, + RoadmapNodeIdentity, RoadmapEdge, Pointer, RoadmapNodeId, @@ -71,8 +72,10 @@ fn signed(by: String, works: RedControlEvidence, scope_equivalent: HandbackEvide data roadmap_execution_contract_frontier_note: String = "Every node below is ExecutionContractUnspecified: the declared, fail-closed migration frontier for gunbc.roadmap_execution_contract. A node states the exact validation operations that close it before the actuator will spawn for it, so dispatch refuses with a typed environment-admission diagnostic rather than guessing validations or dependencies. The prior binding lived on a pre-refresh operator-TODO node deleted by the 2026-07-27 authority refresh; re-binding is per-node roadmap authoring, never a merge-time default." -fn authored(id: String, done: Bool, content: String) -> RoadmapNode { +fn authored(identity: String, + id: String, done: Bool, content: String) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: AuthoredLine { done: done, content: content }, carriers: [], @@ -83,8 +86,10 @@ fn authored(id: String, done: Bool, content: String) -> RoadmapNode { } } -fn derivable(id: String, prs: List, title: String, description: String) -> RoadmapNode { +fn derivable(identity: String, + id: String, prs: List, title: String, description: String) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: description }, carriers: [], @@ -96,6 +101,7 @@ fn derivable(id: String, prs: List, title: String, description: String) -> } fn derivable_wi( + identity: String, id: String, prs: List, title: String, @@ -105,6 +111,7 @@ fn derivable_wi( repo: String ) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: description }, carriers: [], @@ -121,6 +128,7 @@ fn derivable_wi( } fn authored_wi( + identity: String, id: String, done: Bool, content: String, @@ -129,6 +137,7 @@ fn authored_wi( repo: String ) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: AuthoredLine { done: done, content: content }, carriers: [], @@ -144,8 +153,10 @@ fn authored_wi( } } -fn ticket_row(id: String, done: Bool, t: TicketFields) -> RoadmapNode { +fn ticket_row(identity: String, + id: String, done: Bool, t: TicketFields) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: TicketLine { done: done, ticket: t }, carriers: [], @@ -157,6 +168,7 @@ fn ticket_row(id: String, done: Bool, t: TicketFields) -> RoadmapNode { } fn ticket_wi( + identity: String, id: String, done: Bool, t: TicketFields, @@ -165,6 +177,7 @@ fn ticket_wi( repo: String ) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: TicketLine { done: done, ticket: t }, carriers: [], @@ -180,8 +193,10 @@ fn ticket_wi( } } -fn superseded(id: String, by: String, content: String) -> RoadmapNode { +fn superseded(identity: String, + id: String, by: String, content: String) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: SupersededLine { by: by, content: content }, carriers: [], @@ -194,6 +209,7 @@ fn superseded(id: String, by: String, content: String) -> RoadmapNode { fn sign(rn: RoadmapNode, s: Signoff) -> RoadmapNode { RoadmapNode { + identity: rn.identity, node: rn.node, line: rn.line, carriers: rn.carriers, @@ -233,6 +249,7 @@ fn fields( } fn active_sized( + identity: String, id: String, owner: String, path: String, @@ -241,6 +258,7 @@ fn active_sized( t: TicketFields ) -> RoadmapNode { RoadmapNode { + identity: identity as RoadmapNodeIdentity, node: nid(s: id), line: TicketLine { done: false, ticket: t }, carriers: carrier(path: path), @@ -256,8 +274,10 @@ fn active_sized( } } -fn active(id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { +fn active(identity: String, + id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { active_sized( + identity: identity, id: id, owner: owner, path: path, @@ -267,8 +287,10 @@ fn active(id: String, owner: String, path: String, t: TicketFields) -> RoadmapNo ) } -fn active_small(id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { +fn active_small(identity: String, + id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { active_sized( + identity: identity, id: id, owner: owner, path: path, @@ -278,8 +300,10 @@ fn active_small(id: String, owner: String, path: String, t: TicketFields) -> Roa ) } -fn active_large(id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { +fn active_large(identity: String, + id: String, owner: String, path: String, t: TicketFields) -> RoadmapNode { active_sized( + identity: identity, id: id, owner: owner, path: path, @@ -369,6 +393,7 @@ fn caret_parse_smoke_seed_growth_fields(j: SeedGrowthJustification) -> TicketFie fn declared_roadmap_nodes() -> List { [ active_sized( + identity: "rn_44VKSHNX2ZNTDRCPGGNSFJWCWN", id: "2-scm-git-upstream-model", owner: "source-intent", path: "docs/plans/dag-scm-design.md", @@ -385,6 +410,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_sized( + identity: "rn_5JX128MQ9XYR760B5CFC3VECTN", id: "2-scm-mercurial-upstream-model", owner: "source-intent", path: "docs/plans/dag-scm-design.md", @@ -401,6 +427,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_sized( + identity: "rn_5MGS40RC9NE9HPFA4V5M2VVR19", id: "2-scm-pijul-upstream-model", owner: "source-intent", path: "docs/plans/dag-scm-design.md", @@ -417,6 +444,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_sized( + identity: "rn_629P2SF909R5C71V2HPCK4TJMD", id: "2-claim-indexed-evidence", owner: "deduction", path: "docs/plans/dag-scm-design.md", @@ -434,6 +462,7 @@ fn declared_roadmap_nodes() -> List { ), active( + identity: "rn_52ZXVWC7S94RYF4YJHZCN00GCS", id: "namespace-occurrence-transport", owner: "namespace", path: "docs/plans/namespace-unique-on-chain-operational-plan.md", @@ -448,6 +477,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_KH24WPK0FFKWHK5J0QM40AYMZZ", id: "namespace-canonical-binding", owner: "namespace", path: "docs/plans/namespace-unique-on-chain-operational-plan.md", @@ -462,6 +492,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_F8Q9J015C5V8JKZMAGW556KVP0", id: "namespace-bound-only-consumers", owner: "namespace", path: "docs/plans/namespace-unique-on-chain-operational-plan.md", @@ -476,6 +507,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_154WYEVN9KBKQ6FVGPSQR4Y2H2", id: "namespace-v2-import-completion", owner: "namespace", path: "docs/plans/namespace-unique-on-chain-operational-plan.md", @@ -490,6 +522,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_HPYW6X0NEMYVNAKZJFA6TQPK0Q", id: "namespace-legacy-choice-retirement", owner: "namespace", path: "docs/plans/namespace-unique-on-chain-operational-plan.md", @@ -505,6 +538,7 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_E414RDCS728VRFA9QZ0NX76SYN", id: "pderive-typesafe-nullary-reflection", owner: "self-host", path: "docs/plans/v2-self-hosting.md", @@ -519,6 +553,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_HVYDDT9844FY806ZKBDV5KVQDR", id: "pderive-static-supplemental-contract", owner: "self-host", path: "docs/plans/v2-self-hosting.md", @@ -533,6 +568,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_0CPDNZRQA7JMPR0SDG5XSG5987", id: "pderive-source-slot-binding", owner: "self-host", path: "docs/plans/v2-self-hosting.md", @@ -547,6 +583,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_W3JP9BYW55W695XXAGS4KRVS8K", id: "pderive-v2-emitter-consumer", owner: "self-host", path: "src/v2/compiler/05_emit.dag", @@ -561,6 +598,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_DR9YT28ZT3CW3QBQJE4FDVAGFB", id: "pderive-error-movement-receipt", owner: "self-host", path: "docs/plans/v2-self-hosting.md", @@ -576,12 +614,14 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_R5GXMKEWDKNV87EQQ3BJRBS998", id: "observation-scoped-run-seed-growth-justification", owner: "ci-observation", path: "dag/gunbc/plans/cli_run_hollowing_plan.dag", t: seed_growth_fields(j: observation_scoped_run_seed_growth_justification()) ), active( + identity: "rn_RADD6MACVYS70705B1S830DHNX", id: "observation-scoped-run-consumer", owner: "ci-observation", path: "docs/plans/progress-observation-design.md", @@ -596,6 +636,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_CYFS96A1S7K88Y1JDCDCG9N6KC", id: "observation-ci-event-grouping", owner: "ci-observation", path: "docs/plans/progress-observation-design.md", @@ -610,6 +651,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_JV9JZNAZDQCB041MSNKCW0FSEW", id: "observation-collapse-watchdog-restatement", owner: "ci-observation", path: "docs/plans/progress-observation-design.md", @@ -624,6 +666,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_81MBTXHVQFB0HJ6XPA0WXEGVVW", id: "observation-raw-print-retirement", owner: "ci-observation", path: "docs/plans/progress-observation-design.md", @@ -639,6 +682,7 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_6JR237TB4G7T9S52M93JCP3PK7", id: "placement-live-roster-preflight", owner: "ci-placement", path: "docs/plans/ci-two-tier-placement-redesign.md", @@ -653,6 +697,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_9MFAAQX8CWS2ZTGFZ1BZGTAR38", id: "placement-two-host-warm-qualification", owner: "ci-placement", path: "docs/plans/ci-two-tier-placement-redesign.md", @@ -667,6 +712,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_KM2MW4V0T1NY4CNCAJ066P7NPZ", id: "placement-shadow-equality", owner: "ci-placement", path: "docs/plans/ci-two-tier-placement-redesign.md", @@ -681,6 +727,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_XA5X71V90AVWZZ6BSKYH6MG0Q4", id: "placement-atomic-authority-flip", owner: "ci-placement", path: "docs/plans/ci-two-tier-placement-redesign.md", @@ -696,6 +743,7 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_SZKN5YRA7FH46J1Z6THGN080G1", id: "v1-test-hygiene-producer-retirement", owner: "self-host", path: "docs/plans/v2-self-hosting.md", @@ -710,6 +758,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_KSJNTEDB15YG37HBQXP8P62XJ8", id: "v2-emitter-producer-provenance", owner: "self-host", path: "src/v2/compiler/self_host/frontier.dag", @@ -724,6 +773,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_B7PXF6JQRHY19AEQPXGBP12DCT", id: "v2-emitter-direct-rust-door", owner: "self-host", path: "src/v2/compiler/self_host/candidate_generation.dag", @@ -738,6 +788,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_KD37MRQ3ZA6F502DBKE6FE0VB8", id: "v2-emitter-first-behavioral-module", owner: "self-host", path: "src/v2/compiler/self_host/frontier.dag", @@ -752,6 +803,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_5E7W23B34MW5Z7XMATBBJ11QFS", id: "v2-emitter-native-bootstrap", owner: "self-host", path: "src/v2/compiler/self_host/frontier.dag", @@ -766,6 +818,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_C90239W2TBQX58KQHHPXE9S9VP", id: "v1-emitter-fixed-point", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -780,6 +833,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_M5WCJ1GDZVJ67JXQN2NBPJKB58", id: "v1-honest-frontier", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -794,6 +848,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_53JPH6BB7G588K7DMZNWM0E3AS", id: "v1-materialization-kernel", owner: "self-host", path: "docs/plans/witness-realization-plan.md", @@ -808,6 +863,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_X9RY6N9HJX29GHHGDRGP86T6NV", id: "v1-verification-ledger", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -822,6 +878,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_FWSQ19QP41ARPXBM0HFW4MFXGG", id: "v1-ci-floor-cutover", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -836,6 +893,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_HM8EEVB6DDD417CJ8RM79T4F3M", id: "v1-hand-queue-drain", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -850,6 +908,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_0X0GRWB1KQ6KR0SAK1HDKEDGPR", id: "v1-test-migration", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -864,6 +923,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_HZJEM68DT207HQH449FPR3VZPA", id: "v1-seed-honesty-decision", owner: "operator", path: "dag/gunbc/v1_deletion_plan.dag", @@ -878,6 +938,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_C99CKWVBCHDQ35TQRST1AT3N8V", id: "v1-emitter-quarantine", owner: "self-host", path: "src/v1/05_emit_rust.dag", @@ -892,6 +953,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_1QZDB4P83FP5GPBQMVHPJ4JWQZ", id: "v1-emitter-delete", owner: "self-host", path: "src/v1/05_emit_rust.dag", @@ -906,6 +968,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_J4Q81A4BJ8TXN6ZE4481RMB3Y9", id: "v1-interpreter-quarantine", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -920,6 +983,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_SSW55NRPT0931Q12PB8YQ3Q2BH", id: "v1-interpreter-delete", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -934,6 +998,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_NTQJM3ECTNN49NG2SSR9TNX2RX", id: "v1-products-v1-free", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -948,12 +1013,14 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_VK5TWKCDCMR1G8PQY0GK9TKSRR", id: "caret-parse-smoke-seed-growth-justification", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", t: caret_parse_smoke_seed_growth_fields(j: caret_parse_smoke_seed_growth_justification()) ), active_large( + identity: "rn_RSBHA36N72JKQ0NAVKJ5AV153R", id: "v1-zero-hand-maintained-rust", owner: "self-host", path: "dag/gunbc/v1_deletion_plan.dag", @@ -969,6 +1036,7 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_7T6WSS1JYCV6GQNW1PZG73B0G3", id: "shell-typed-invocation", owner: "shell", path: "docs/plans/shell-to-dag-residual-census-and-arc-completion.md", @@ -983,6 +1051,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_MRCH9RMA0WPEEM73AKTS5HX9TT", id: "shell-effectplan-to-bash", owner: "shell", path: "docs/plans/shell-to-dag-residual-census-and-arc-completion.md", @@ -997,6 +1066,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_F4VSZD7T1FNFMGB57YZK1EKQAP", id: "shell-gate-migration", owner: "shell", path: "docs/plans/shell-to-dag-residual-census-and-arc-completion.md", @@ -1011,6 +1081,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_Y27H49GJ9JCXHQRG5HD3N3FDMJ", id: "shell-residue-zero", owner: "shell", path: "docs/plans/shell-to-dag-residual-census-and-arc-completion.md", @@ -1026,6 +1097,7 @@ fn declared_roadmap_nodes() -> List { ), active_large( + identity: "rn_E8Y0PHW82DNMMYZ6SB2KKX2K76", id: "roadmap-runtime-identity-join", owner: "roadmap-runtime", path: "docs/plans/roadmap-workspace-ux-plan.md", @@ -1040,6 +1112,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_YXN9NF28719TKA46VYQ5JA9ZP9", id: "roadmap-runtime-ready-override", owner: "roadmap-runtime", path: "docs/plans/roadmap-workspace-ux-plan.md", @@ -1054,6 +1127,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_S6E48C3P7A3GCT4D12Y4M095EQ", id: "roadmap-runtime-dispatch-consumer", owner: "roadmap-runtime", path: "docs/plans/roadmap-workspace-ux-plan.md", @@ -1068,6 +1142,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_WJDVJZSNQSWQ3XS55NM5VXV18R", id: "roadmap-runtime-readback", owner: "roadmap-runtime", path: "docs/plans/roadmap-workspace-ux-plan.md", @@ -1083,6 +1158,7 @@ fn declared_roadmap_nodes() -> List { ), active_small( + identity: "rn_WWVQ07RC0HF55DPM2CXNKWY8RR", id: "fleet-state-frontier", owner: "fleet", path: "docs/plans/host-converge-inventory.md", @@ -1097,6 +1173,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_PEH90XVR97GYYA5FXGDJXZ70PF", id: "fleet-bmc-policy-convergence", owner: "fleet", path: "docs/runbooks/bmc-redfish-operator-access.md", @@ -1111,6 +1188,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_TAPSSCRTTBBV274TFK1PAGAP49", id: "fleet-closed-loop-converge", owner: "fleet", path: "docs/plans/host-effect-orchestration.md", @@ -1125,6 +1203,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_Y33HNQA0BZ3S6FGMNT96M526MM", id: "fleet-dashboard-instance-membership", owner: "fleet", path: "docs/plans/gunbc-served-dashboard-design.md", @@ -1139,6 +1218,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_DMMM7YQHT05Q01CD10Q7B6R1DK", id: "fleet-anti-entropy-hygiene", owner: "fleet", path: "docs/plans/fleet-acceptance-criteria.md", @@ -1153,6 +1233,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_MDB59T769G88WS89Y6AD2M8MSS", id: "fleet-receipt-qualified-control", owner: "fleet", path: "docs/plans/fleet-acceptance-criteria.md", @@ -1168,6 +1249,7 @@ fn declared_roadmap_nodes() -> List { ), active( + identity: "rn_3B3802MNXFPG4VS2VFT7BNBN1Y", id: "toolchain-pin-model", owner: "toolchain", path: "docs/plans/hermetic-tool-provisioning-design.md", @@ -1182,6 +1264,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_large( + identity: "rn_VTF6P0GG5N1A2SQ9X70TPH6K49", id: "toolchain-single-resolver", owner: "toolchain", path: "docs/plans/hermetic-tool-provisioning-design.md", @@ -1196,6 +1279,7 @@ fn declared_roadmap_nodes() -> List { ) ), active( + identity: "rn_P57S06JDC8MG7V71CTYX3NRJHH", id: "toolchain-rust-hermetic", owner: "toolchain", path: "docs/plans/hermetic-tool-provisioning-design.md", @@ -1210,6 +1294,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_sized( + identity: "rn_MVG7ADQA5NTV1Z3PJRTQZ73BF0", id: "toolchain-residue-zero", owner: "toolchain", path: "docs/plans/hermetic-tool-provisioning-design.md", @@ -1226,6 +1311,7 @@ fn declared_roadmap_nodes() -> List { ) ), active_small( + identity: "rn_EK3HGNHSK769BWC9Z34MAN6X8J", id: "toolchain-system-floor", owner: "toolchain", path: "docs/plans/hermetic-tool-provisioning-design.md", diff --git a/dag/gunbc/roadmap_identity.dag b/dag/gunbc/roadmap_identity.dag new file mode 100644 index 00000000000..22b3faa4012 --- /dev/null +++ b/dag/gunbc/roadmap_identity.dag @@ -0,0 +1,99 @@ +module gunbc.roadmap_identity + +import std.types { String, NonEmptyStr, Bool, Int, List } +import gunbc.roadmap_model { RoadmapNode, RoadmapNodeId, RoadmapNodeIdentity } +import gunbc.roadmap_authority { declared_roadmap_nodes } + +data roadmap_identity_index_derivation_note: String = "THE INDEX IS DERIVED, NEVER AUTHORED. An earlier cut carried a hand-written list of (identity, slug) rows beside the authority, which created two things that had to be kept in step and could disagree — the DESIGN 3 fork, with a witness standing in for a construction (DESIGN 5). Identity is now a required field on RoadmapNode, so a node without one is unwritable, and everything below is a projection of declared_roadmap_nodes(). There is exactly one place a node's identity can be stated, and the index cannot drift from it because it has no independent existence." + +data roadmap_identity_lifecycle_note: String = "PERMANENT NON-REUSE NEEDS A TOMBSTONE, and a base-versus-head diff alone cannot supply it. Without persisted retirement the sequence is: PR A removes node X and its row; a diff gate observes the removal once; after merge X is simply absent from the base registry; PR B mints a new node and reuses X's identity; the next base-versus-head comparison sees an identity that is present in head and absent in base — indistinguishable from a legitimately NEW identity. The reuse passes every check.\\n\\nSo retirement is a state, not a deletion. A Tombstoned row keeps the identity in the registry forever, carries the slug it last wore so historical links still resolve, and names the identity that superseded it when there is one. Retirement is then observable rather than inferred from absence, and 'may never become active again' becomes checkable." + +type RoadmapIdentityRecord + = ActiveIdentity { + identity: RoadmapNodeIdentity, + slug: RoadmapNodeId, + } + | TombstonedIdentity { + identity: RoadmapNodeIdentity, + former_slug: RoadmapNodeId, + superseded_by: RoadmapNodeIdentity?, + } + +data roadmap_tombstone_roster_note: String = "Empty today, and that is the honest state rather than a placeholder: no roadmap node has yet been retired under this model. The row exists so the first retirement has a home that is not deletion — the failure the 2026-07-27 authority refresh already demonstrated, when a node carrying the only execution contract was removed and nothing noticed. A retirement lands here in the same change that removes the node from declared_roadmap_nodes()." + +fn roadmap_tombstoned_identities() -> List { + [] +} + +fn roadmap_active_identities() -> List { + declared_roadmap_nodes() |> map(n => ActiveIdentity { + identity: n.identity, + slug: n.node, + }) +} + +fn roadmap_identity_registry() -> List { + concat(roadmap_active_identities(), roadmap_tombstoned_identities()) +} + +fn record_identity(r: RoadmapIdentityRecord) -> String { + match r { + ActiveIdentity { identity, slug: _ } => identity as String + TombstonedIdentity { identity, former_slug: _, superseded_by: _ } => identity as String + } +} + +fn record_slug(r: RoadmapIdentityRecord) -> String { + match r { + ActiveIdentity { identity: _, slug } => slug as String + TombstonedIdentity { identity: _, former_slug, superseded_by: _ } => former_slug as String + } +} + +fn record_is_active(r: RoadmapIdentityRecord) -> Bool { + match r { + ActiveIdentity { identity: _, slug: _ } => true + TombstonedIdentity { identity: _, former_slug: _, superseded_by: _ } => false + } +} + +data roadmap_identity_resolution_note: String = "Resolution is EXACTLY-ONE, not first-pick. A filter-map-first lookup answers a duplicate registry with a plausible first row, which is the fabricated-plausible-output arm DESIGN 5 forbids — and it fails at exactly the moment the registry is corrupt, when a confident wrong answer is worst. The three outcomes are distinct because their remedies are: a MISSING identity is an unknown reference; a DUPLICATE is a corrupt registry; found is found. Callers match the sum rather than unwrapping an Optional that cannot tell those apart." + +type RoadmapIdentityResolution + = IdentityResolved { identity: RoadmapNodeIdentity } + | IdentityUnknown { slug: String } + | IdentityAmbiguous { slug: String, matches: Int } + +fn resolve_slug(slug: String) -> RoadmapIdentityResolution { + let hits = roadmap_identity_registry() |> filter(r => record_slug(r: r) == slug) + if count(hits) == 0 { + IdentityUnknown { slug: slug } + } else if count(hits) > 1 { + IdentityAmbiguous { slug: slug, matches: count(hits) } + } else { + match hits |> map(r => record_identity(r: r)) |> first { + Present { value: i } => IdentityResolved { identity: i as RoadmapNodeIdentity } + Absent => IdentityUnknown { slug: slug } + } + } +} + +fn identity_count() -> Int { count(roadmap_identity_registry()) } + +fn distinct_count(values: List) -> Int { + count(fold(values, init: [], f: fn(acc, v) { + if any(acc, e => e == v) { acc } else { append(acc, [v]) } + })) +} + +fn distinct_identity_count() -> Int { + distinct_count(values: roadmap_identity_registry() |> map(r => record_identity(r: r))) +} + +fn distinct_active_slug_count() -> Int { + distinct_count(values: roadmap_active_identities() |> map(r => record_slug(r: r))) +} + +fn active_identity_count() -> Int { count(roadmap_active_identities()) } + +data roadmap_identity_permanence_gate_note: String = "WHAT A SNAPSHOT CAN AND CANNOT PROVE, stated so the gap is not mistaken for coverage. The claims below are statements about the CURRENT tree: identities unique across active and tombstoned rows, active slugs unique, every declared node carrying one, resolution exactly-one. None can see history, and permanence is historical — an identity silently REPLACED between base and head produces a tree satisfying every one of them.\\n\\nThe missing mechanism is a base-versus-head comparison of this registry, which the CI floor already has the shape for in its merge-base diff observation. ADMITTED: an active identity retained across a slug change; a new identity added; an active identity becoming Tombstoned. REFUSED: an active identity replaced by a different value for the same work; a tombstoned identity returning to active; an identity disappearing from the registry entirely rather than being tombstoned; two records sharing one identity; one active slug resolving to two identities.\\n\\nThe tombstone carrier above is what makes that gate implementable at all — reuse is only detectable because retirement persists. Until the gate runs, permanence rests on review, and this row keeps that fact countable rather than forgotten." diff --git a/dag/gunbc/roadmap_model.dag b/dag/gunbc/roadmap_model.dag index bb5240edc50..35a2ae12c8c 100644 --- a/dag/gunbc/roadmap_model.dag +++ b/dag/gunbc/roadmap_model.dag @@ -12,6 +12,10 @@ import gunbc.roadmap_execution_contract { WorkItemExecutionContract } type RoadmapNodeId = NonEmptyStr where brand("RoadmapNodeId") +data roadmap_node_identity_law: String = "A roadmap node's identity is MINTED, never derived. Slug, title, criteria digest, containment position, dependency neighbourhood, declaration order and file position are FORBIDDEN identity inputs — the law std.occurrence_identity states for occurrences, generalized to an entity that must outlive the graph it is read from.\n\nThe law is reused; the TYPE deliberately is not. std.occurrence_identity.OccurrenceId is an ordinal from a per-graph allocator starting at zero, so it is unique only INSIDE one constructed graph: inserting a node at the front renumbers every node after it, and two concurrent branches mint the same next integer. That is positional identity wearing an allocator's clothes — right for occurrences within one parse, wrong for a work item that must survive edits, reordering, regeneration and independent branches. What occurrence identity deliberately does not claim is PERSISTENCE SCOPE, and that is precisely what a roadmap node needs.\n\nIdentity is a REQUIRED FIELD rather than a side registry keyed by slug. A registry beside the authority leaves a node authorable without identity, detectable only by a later witness — validation standing where construction was available (DESIGN 5), and two authorities that must be kept in step (DESIGN 3). As a field, a node without durable identity is unwritable, and the slug index is DERIVED from the nodes rather than independently authored, so the two can never disagree." + +type RoadmapNodeIdentity = NonEmptyStr where brand("RoadmapNodeIdentity") + type Pointer { label: String, path: String } type Intricacy @@ -35,6 +39,7 @@ type NodeSizing | Sized { intricacy: Intricacy, volume: Volume, repo: String, acceptance: Acceptance } type RoadmapNode { + identity: RoadmapNodeIdentity node: RoadmapNodeId line: RoadmapItem carriers: List diff --git a/dag/test/claim/roadmap_altitude_witness_test.dag b/dag/test/claim/roadmap_altitude_witness_test.dag index 63f355de966..9537267f5e9 100644 --- a/dag/test/claim/roadmap_altitude_witness_test.dag +++ b/dag/test/claim/roadmap_altitude_witness_test.dag @@ -76,8 +76,9 @@ fn doc_html(nodes: List) -> String { } fn done_signed_node() -> RoadmapNode { - let base = authored(id: "alt-done", done: true, content: "finished work") + let base = authored(identity: "rnfx_WA4EZDD0ZGMCQMTKQA21", id: "alt-done", done: true, content: "finished work") RoadmapNode { + identity: "rnfx_2K15QJ1CVZY55K15AHJK" as RoadmapNodeIdentity, node: base.node, line: base.line, carriers: base.carriers, @@ -89,7 +90,7 @@ fn done_signed_node() -> RoadmapNode { } fn open_node() -> RoadmapNode { - authored(id: "alt-open", done: false, content: "working item") + authored(identity: "rnfx_140ZV0QZTY3R9PTENK99", id: "alt-open", done: false, content: "working item") } data fold_by_statuses_note: String = "The fold decision proven over the RENDERED page, not the predicate alone: an all-routine section serializes its details WITHOUT the open attribute (folded — the summary band still carries the counts, so nothing is hidden silently), and adding one working member to the same section flips the SAME serialization to open. The two fixtures differ by exactly that member, so the discrimination is located in the status data — hand-curation has no input to reach. Fixture lesson, kept: a done-but-UNSIGNED node renders review (the sign-off gate), and review is WORKING under the law — the operator's pending gesture holds a section open, which is the attention law agreeing with the sign-off discipline rather than a coincidence. Routine therefore requires the signed fixture." @@ -108,7 +109,7 @@ test fn altitude_working_member_holds_section_open() -> Bool { } test fn altitude_unsigned_done_holds_section_open() -> Bool { - let unsigned_done = doc_html(nodes: [authored(id: "alt-unsigned", done: true, content: "done but unsigned")]) + let unsigned_done = doc_html(nodes: [authored(identity: "rnfx_0A9N7HZG1Q9HRP760WD7", id: "alt-unsigned", done: true, content: "done but unsigned")]) return string_contains(s: unsigned_done, pattern: "
") && string_contains(s: unsigned_done, pattern: "1 review") } diff --git a/dag/test/claim/roadmap_authority_test.dag b/dag/test/claim/roadmap_authority_test.dag index 0f9162079d4..a1f815feef1 100644 --- a/dag/test/claim/roadmap_authority_test.dag +++ b/dag/test/claim/roadmap_authority_test.dag @@ -195,6 +195,7 @@ fn active_node_admissible(n: RoadmapNode) -> Bool { fn red_fixture(line: RoadmapItem, signoff: Signoff) -> RoadmapNode { RoadmapNode { + identity: "rnfx_8E5PG4F7CAZ148VZ8MNY" as RoadmapNodeIdentity, node: "red-fixture", line: line, carriers: [Pointer { label: "authority", path: "DESIGN.md" }], diff --git a/dag/test/claim/roadmap_belt_actuate_witness_test.dag b/dag/test/claim/roadmap_belt_actuate_witness_test.dag index 5dd9addef6e..2a6fad847c6 100644 --- a/dag/test/claim/roadmap_belt_actuate_witness_test.dag +++ b/dag/test/claim/roadmap_belt_actuate_witness_test.dag @@ -65,6 +65,7 @@ fn nid(s: String) -> RoadmapNodeId { s } fn ready_node(id: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_RWT4XRCGS4GR90YZXK0Y" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: [1], title: id, description: "" }, carriers: [], @@ -315,6 +316,7 @@ test fn witness_find_node_misses() -> Bool { fn owned_node(id: String, owner: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_F2RWDMNTDKS0ZR1P4VNZ" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: [1], title: id, description: "" }, carriers: [], diff --git a/dag/test/claim/roadmap_belt_witness_test.dag b/dag/test/claim/roadmap_belt_witness_test.dag index 69768b04736..c60299f5cd0 100644 --- a/dag/test/claim/roadmap_belt_witness_test.dag +++ b/dag/test/claim/roadmap_belt_witness_test.dag @@ -39,6 +39,7 @@ fn nid(s: String) -> RoadmapNodeId { s } fn ready_node(id: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_P2E65A1P1148Z5Y175QT" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: [1], title: id, description: "" }, carriers: [], diff --git a/dag/test/claim/roadmap_dispatch_actuator_witness_test.dag b/dag/test/claim/roadmap_dispatch_actuator_witness_test.dag index 6a44bf686a5..f1e61d42c1f 100644 --- a/dag/test/claim/roadmap_dispatch_actuator_witness_test.dag +++ b/dag/test/claim/roadmap_dispatch_actuator_witness_test.dag @@ -97,6 +97,7 @@ fn nid(s: String) -> RoadmapNodeId { s } fn sized_derivable(id: String, prs: List, title: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_R2PE6DBK7RVVR2NHNQN0" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: "" }, carriers: [], @@ -563,7 +564,7 @@ test fn witness_session_lease_shape_reused() -> Bool { } fn ticket_fixture_node() -> RoadmapNode { - gunbc.roadmap_authority.ticket_wi(id: "ticket-fixture", done: false, t: TicketFields { + gunbc.roadmap_authority.ticket_wi(identity: "rnfx_QSK1R8XNYS14GRZEVM6C", id: "ticket-fixture", done: false, t: TicketFields { headline: "Fixture Headline", brief: "A short brief.", displaced_cost: "the real displaced cost", diff --git a/dag/test/claim/roadmap_document_test.dag b/dag/test/claim/roadmap_document_test.dag index f87fce5d1b4..5f4cb58098d 100644 --- a/dag/test/claim/roadmap_document_test.dag +++ b/dag/test/claim/roadmap_document_test.dag @@ -16,6 +16,7 @@ data child_id: RoadmapNodeId = "host-fed-status-bridge" fn audit_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_J19302B1RYEQZVMM6TG9" as RoadmapNodeIdentity, node: audit_id, line: AuthoredLine { done: true, content: "**fail-open audit**" }, carriers: [Pointer { label: "plan", path: "docs/plans/fail-closed-lockdown.md" }], @@ -28,6 +29,7 @@ fn audit_node() -> RoadmapNode { fn status_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_AJ7G9SDD8FFX86WHFYQB" as RoadmapNodeIdentity, node: status_id, line: DerivableLine { prs: [5491], title: "**PR-checkbox status**", description: "" }, carriers: [], @@ -39,6 +41,7 @@ fn status_node() -> RoadmapNode { } fn child_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_ABYCNZ3BDSY98CHYC51T" as RoadmapNodeIdentity, node: child_id, line: AuthoredLine { done: false, content: "**host-fed status bridge**" }, carriers: [], diff --git a/dag/test/claim/roadmap_emit_test.dag b/dag/test/claim/roadmap_emit_test.dag index f1b8aa8250b..c9af1948aad 100644 --- a/dag/test/claim/roadmap_emit_test.dag +++ b/dag/test/claim/roadmap_emit_test.dag @@ -15,6 +15,7 @@ data child_id: RoadmapNodeId = "pr-checkbox-status" fn parent_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_5E27VQFME81E3RP60RE6" as RoadmapNodeIdentity, node: parent_id, line: AuthoredLine { done: false, content: "**invert hand-maintained**" }, carriers: [Pointer { label: "plan", path: "docs/plans/invert-hand-maintained.md" }], @@ -27,6 +28,7 @@ fn parent_node() -> RoadmapNode { fn child_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_8P7AJ3QVQCM2E8W3AMR7" as RoadmapNodeIdentity, node: child_id, line: DerivableLine { prs: [5491], title: "**PR-checkbox status**", description: "" }, carriers: [], @@ -39,6 +41,7 @@ fn child_node() -> RoadmapNode { fn signed_child_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_D1QNZ9290W7D8GPH4XJQ" as RoadmapNodeIdentity, node: child_id, line: DerivableLine { prs: [5491], title: "**PR-checkbox status**", description: "" }, carriers: [], @@ -110,6 +113,7 @@ test fn witness_multi_parent_dependencies_render_complete_set() -> Bool { fn superseded_child_node() -> RoadmapNode { RoadmapNode { + identity: "rnfx_HSDEAR3FG68K530CRD70" as RoadmapNodeIdentity, node: child_id, line: SupersededLine { by: "successor-lane", content: "**PR-checkbox status**" }, carriers: [], diff --git a/dag/test/claim/roadmap_frontier_witness_test.dag b/dag/test/claim/roadmap_frontier_witness_test.dag index 79f59685cfa..25c8c9f65e5 100644 --- a/dag/test/claim/roadmap_frontier_witness_test.dag +++ b/dag/test/claim/roadmap_frontier_witness_test.dag @@ -26,6 +26,7 @@ fn nid(s: String) -> RoadmapNodeId { s } fn sized_derivable(id: String, prs: List, title: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_JAGJGPTHTYSG1GEDN9BA" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: "" }, carriers: [], @@ -38,6 +39,7 @@ fn sized_derivable(id: String, prs: List, title: String) -> RoadmapNode { fn accepted_node(base: RoadmapNode) -> RoadmapNode { RoadmapNode { + identity: "rnfx_QBNJEXMAX2KQWS838BE3" as RoadmapNodeIdentity, node: base.node, line: base.line, carriers: base.carriers, @@ -50,6 +52,7 @@ fn accepted_node(base: RoadmapNode) -> RoadmapNode { fn unsized_derivable(id: String, prs: List, title: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_CMY81J4ZCKAVZFQEAEFV" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: "" }, carriers: [], @@ -177,6 +180,7 @@ test fn witness_page_renders_four_buckets() -> Bool { fn superseded_node_fixture(id: String, by: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_7WGSEGZK2FRN2VY0DWFC" as RoadmapNodeIdentity, node: nid(s: id), line: SupersededLine { by: by, content: "Old Row" }, carriers: [], @@ -189,6 +193,7 @@ fn superseded_node_fixture(id: String, by: String) -> RoadmapNode { fn signed_sized_superseded_node_fixture(id: String, by: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_2M0H9GXC2H0A5CZ9V7HW" as RoadmapNodeIdentity, node: nid(s: id), line: SupersededLine { by: by, content: "Accepted Old Row" }, carriers: [], diff --git a/dag/test/claim/roadmap_gate_test.dag b/dag/test/claim/roadmap_gate_test.dag index f36c1748566..e22d1b37fd1 100644 --- a/dag/test/claim/roadmap_gate_test.dag +++ b/dag/test/claim/roadmap_gate_test.dag @@ -27,7 +27,7 @@ test fn witness_hand_edit_drifts() -> Bool { test fn witness_signed_emits_checked() -> Bool { let node = sign( - rn: derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), + rn: derivable(identity: "rnfx_Y1YV36GYPSWXS09CN0K1", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), s: signed(by: "operator", works: RedControlExecuted { witness_module: "test.fixture", witness_fn: "fixture_red_control", executed_on: "2026-07-28" }, scope_equivalent: HandbackDelivered { first_artifact: "fixture-handback", further_artifacts: [] }, as_expected: true), ) let out = emit_node(node: node, merged: []) @@ -36,7 +36,7 @@ test fn witness_signed_emits_checked() -> Bool { } test fn witness_unsigned_with_evidence_awaiting() -> Bool { - let node = derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: "") + let node = derivable(identity: "rnfx_82B698TW1CS8DZ9WGJBX", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: "") let out = emit_node(node: node, merged: [5491]) string_contains(s: out, pattern: "[ ] **signoff witness**") && string_contains(s: out, pattern: "⏳ awaiting sign-off") @@ -45,7 +45,7 @@ test fn witness_unsigned_with_evidence_awaiting() -> Bool { test fn witness_asserted_signoff_without_red_control_evidence_refuses() -> Bool { let node = sign( - rn: derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), + rn: derivable(identity: "rnfx_C384A795R2E031HBB319", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), s: signed(by: "operator", works: RedControlNotRun, scope_equivalent: HandbackDelivered { first_artifact: "fixture-handback", further_artifacts: [] }, as_expected: true), ) let out = emit_node(node: node, merged: []) @@ -55,7 +55,7 @@ test fn witness_asserted_signoff_without_red_control_evidence_refuses() -> Bool test fn witness_asserted_signoff_without_handback_evidence_refuses() -> Bool { let node = sign( - rn: derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), + rn: derivable(identity: "rnfx_B6CG7QC8YE59XGN3SC0H", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), s: signed(by: "operator", works: RedControlExecuted { witness_module: "test.fixture", witness_fn: "fixture_red_control", executed_on: "2026-07-28" }, scope_equivalent: HandbackNotDelivered, as_expected: true), ) let out = emit_node(node: node, merged: []) @@ -65,10 +65,10 @@ test fn witness_asserted_signoff_without_handback_evidence_refuses() -> Bool { test fn witness_merge_perturb_does_not_flip_box() -> Bool { let signed_node = sign( - rn: derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), + rn: derivable(identity: "rnfx_6JHS1TN9QPMY3YT77JFV", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: ""), s: signed(by: "operator", works: RedControlExecuted { witness_module: "test.fixture", witness_fn: "fixture_red_control", executed_on: "2026-07-28" }, scope_equivalent: HandbackDelivered { first_artifact: "fixture-handback", further_artifacts: [] }, as_expected: true), ) - let unsigned_node = derivable(id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: "") + let unsigned_node = derivable(identity: "rnfx_19FCXZK9ZJR91QKHYAFR", id: "signoff-witness", prs: [5491], title: "**signoff witness**", description: "") let signed_stays_checked = string_contains(s: emit_node(node: signed_node, merged: []), pattern: "[x] **signoff witness**") && string_contains(s: emit_node(node: signed_node, merged: [5491]), pattern: "[x] **signoff witness**") let unsigned_stays_unchecked = string_contains(s: emit_node(node: unsigned_node, merged: []), pattern: "[ ] **signoff witness**") diff --git a/dag/test/claim/roadmap_identity_witness_test.dag b/dag/test/claim/roadmap_identity_witness_test.dag new file mode 100644 index 00000000000..13128a8f873 --- /dev/null +++ b/dag/test/claim/roadmap_identity_witness_test.dag @@ -0,0 +1,103 @@ +module test.claim.roadmap_identity_witness_test + +import std.types { String, NonEmptyStr, Bool, Int, List } +import gunbc.roadmap_model { RoadmapNode, RoadmapNodeId, RoadmapNodeIdentity } +import gunbc.roadmap_authority { declared_roadmap_nodes } +import gunbc.roadmap_identity { + RoadmapIdentityRecord, + ActiveIdentity, + TombstonedIdentity, + RoadmapIdentityResolution, + IdentityResolved, + IdentityUnknown, + IdentityAmbiguous, + roadmap_identity_registry, + roadmap_active_identities, + roadmap_tombstoned_identities, + record_identity, + record_slug, + record_is_active, + resolve_slug, + identity_count, + distinct_identity_count, + distinct_active_slug_count, + active_identity_count, +} +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data identity_witness_scope_note: String = "WHAT THESE PROVE, AND THE ONE THING THEY CANNOT. They prove the registry is well formed here and now — identities unique across active and tombstoned rows, active slugs unique, the index total over declared nodes, resolution exactly-one in all three outcomes. They CANNOT prove permanence: an identity silently REPLACED between the base revision and this head produces a tree satisfying every claim below, because a snapshot cannot see history. Claiming otherwise would be the coverage-by-illusion this repository names, so the base-versus-head gate is declared owed on the carrier rather than implied here.\\n\\nThe rename claim is the discriminating one. Identity's whole purpose is surviving a slug change, and nothing else in this file would notice if identity silently tracked the slug — every uniqueness and totality claim holds just as well for a registry where identity IS the slug. Constructing a node, renaming its slug, and asserting the identity is unchanged is the only claim here that fails if the separation collapses." + +fn declared_identities() -> List { + declared_roadmap_nodes() |> map(n => n.identity as String) +} + +test fn every_declared_node_carries_an_identity() -> Bool { + all(declared_identities(), i => !(i == "")) + && count(declared_identities()) == count(declared_roadmap_nodes()) +} + +test fn identities_are_unique_across_the_registry() -> Bool { + distinct_identity_count() == identity_count() +} + +test fn active_slugs_are_unique() -> Bool { + distinct_active_slug_count() == active_identity_count() +} + +test fn index_is_total_over_declared_nodes() -> Bool { + active_identity_count() == count(declared_roadmap_nodes()) +} + +test fn resolution_is_exactly_one_not_first_pick() -> Bool { + all(roadmap_active_identities(), r => + match resolve_slug(slug: record_slug(r: r)) { + IdentityResolved { identity } => (identity as String) == record_identity(r: r) + IdentityUnknown { slug: _ } => false + IdentityAmbiguous { slug: _, matches: _ } => false + }) +} + +test fn an_unknown_slug_is_unknown_not_a_plausible_first_row() -> Bool { + match resolve_slug(slug: "no-such-roadmap-node") { + IdentityUnknown { slug } => slug == "no-such-roadmap-node" + IdentityResolved { identity: _ } => false + IdentityAmbiguous { slug: _, matches: _ } => false + } +} + +data rename_discriminator_note: String = "THE CLAIM THAT FAILS IF IDENTITY AND SLUG RECOLLAPSE. Every other claim in this file is equally true of a registry where identity is derived from the slug — uniqueness, totality and resolution all hold in that broken world. This one does not: it takes a declared node, changes ONLY its slug, and asserts the identity is byte-identical. That is the entire property the module exists for, since a rename is precisely the operation that used to destroy edges, receipts and dispatch joins by making the node a different node." + +fn renamed(n: RoadmapNode) -> RoadmapNode { + RoadmapNode { + identity: n.identity, + node: "renamed-slug-fixture" as RoadmapNodeId, + line: n.line, + carriers: n.carriers, + sizing: n.sizing, + execution: n.execution, + owner: n.owner, + signoff: n.signoff, + } +} + +test fn identity_survives_a_slug_rename() -> Bool { + match declared_roadmap_nodes() |> first { + Present { value: n } => + (renamed(n: n).identity as String) == (n.identity as String) + && !((renamed(n: n).node as String) == (n.node as String)) + Absent => false + } +} + +data tombstone_witness_note: String = "The tombstone roster is empty today and these claims are shaped so that stays honest rather than vacuous. The first asserts the registry equals active plus tombstoned — arithmetic that holds at zero and keeps holding when the first retirement lands. The second asserts every tombstoned record reports itself inactive, which is vacuously true now and becomes load-bearing the moment a row exists. Neither asserts a count, because pinning zero would red on the first legitimate retirement." + +test fn registry_is_active_plus_tombstoned() -> Bool { + identity_count() == active_identity_count() + count(roadmap_tombstoned_identities()) +} + +test fn tombstoned_records_are_not_active() -> Bool { + all(roadmap_tombstoned_identities(), r => !record_is_active(r: r)) + && all(roadmap_active_identities(), r => record_is_active(r: r)) +} diff --git a/dag/test/claim/roadmap_model_test.dag b/dag/test/claim/roadmap_model_test.dag index e514e8051c9..21e9bb42254 100644 --- a/dag/test/claim/roadmap_model_test.dag +++ b/dag/test/claim/roadmap_model_test.dag @@ -66,6 +66,7 @@ data existing_docs: List = [ fn node_with_carriers(cs: List) -> RoadmapNode { RoadmapNode { + identity: "rnfx_P8CGH3E7ERD8M55W9X49" as RoadmapNodeIdentity, node: child_node, line: AuthoredLine { done: false, content: "PR→checkbox status" }, carriers: cs, @@ -96,6 +97,7 @@ test fn witness_no_carrier_clean() -> Bool { test fn witness_node_status_drifts() -> Bool { let rn = RoadmapNode { + identity: "rnfx_WZ1MVE2JJZ2CCAWP2TVM" as RoadmapNodeIdentity, node: child_node, line: DerivableLine { prs: [5491], title: "stale", description: "" }, carriers: [], @@ -109,6 +111,7 @@ test fn witness_node_status_drifts() -> Bool { test fn witness_node_unsigned_box_clean() -> Bool { let rn = RoadmapNode { + identity: "rnfx_Q203PA6K5BSTTSCXR7JB" as RoadmapNodeIdentity, node: child_node, line: AuthoredLine { done: false, content: "partial evidence" }, carriers: [], diff --git a/dag/test/claim/roadmap_page_witness_test.dag b/dag/test/claim/roadmap_page_witness_test.dag index 0ad4fd02b48..8e90ae18f48 100644 --- a/dag/test/claim/roadmap_page_witness_test.dag +++ b/dag/test/claim/roadmap_page_witness_test.dag @@ -98,8 +98,8 @@ test fn witness_authority_shows_finish_line_progress() -> Bool { } fn depth_chain_html(fuel: Int) -> String { - let parent = gunbc.roadmap_authority.authored(id: "p", done: false, content: "parent-node") - let child = gunbc.roadmap_authority.authored(id: "c", done: false, content: "child-node-title") + let parent = gunbc.roadmap_authority.authored(identity: "rnfx_716835YJMPRW9JAC4H1Z", id: "p", done: false, content: "parent-node") + let child = gunbc.roadmap_authority.authored(identity: "rnfx_DY971T4PDGRTPY6WDHDR", id: "c", done: false, content: "child-node-title") let edge = RoadmapEdge { child: child.node, parent: parent.node } match try_serialize_html_source(node: nodes_ul(items: [parent], nodes: [parent, child], edges: [edge], merged: [], fuel: fuel)) { EmitOk { html: h } => h @@ -108,7 +108,7 @@ fn depth_chain_html(fuel: Int) -> String { } fn bold_title_html() -> String { - let rn = gunbc.roadmap_authority.authored(id: "bold1", done: false, content: "**bold** item") + let rn = gunbc.roadmap_authority.authored(identity: "rnfx_4JKJ1ZMAYSR30YJW8T8B", id: "bold1", done: false, content: "**bold** item") match try_serialize_html_source(node: nodes_ul(items: [rn], nodes: [rn], edges: [], merged: [], fuel: 0)) { EmitOk { html: h } => h EmitRejected { reason: _ } => "" @@ -149,8 +149,7 @@ fn dispatch_doc_html_for_with_merged(rn: RoadmapNode, merged: List) -> Stri } fn ready_dispatch_node() -> RoadmapNode { - gunbc.roadmap_authority.authored_wi( - id: "disp1", + gunbc.roadmap_authority.authored_wi(identity: "rnfx_7KPP33Y7P6BH9CBVKZ03", id: "disp1", done: false, content: "dispatch-me", intricacy: IntricacyLow, @@ -159,12 +158,13 @@ fn ready_dispatch_node() -> RoadmapNode { } fn unsized_open_dispatch_node() -> RoadmapNode { - gunbc.roadmap_authority.authored(id: "disp-unsized", done: false, content: "observe-only") + gunbc.roadmap_authority.authored(identity: "rnfx_324RFH775DGZZMVETPT6", id: "disp-unsized", done: false, content: "observe-only") } fn signed_off_dispatch_node() -> RoadmapNode { let base = ready_dispatch_node() RoadmapNode { + identity: "rnfx_08P5JSFSS1NH1YY20PWV" as RoadmapNodeIdentity, node: base.node, line: base.line, carriers: base.carriers, @@ -247,8 +247,7 @@ test fn witness_signed_off_node_doc_has_no_button() -> Bool { } test fn witness_merged_pr_is_review_evidence_not_acceptance() -> Bool { - let node = gunbc.roadmap_authority.derivable_wi( - id: "disp-review", + let node = gunbc.roadmap_authority.derivable_wi(identity: "rnfx_0RXWPCEK3N82T9871CXP", id: "disp-review", prs: [7301], title: "review-me", description: "", @@ -267,16 +266,13 @@ test fn witness_unsized_open_node_keeps_manual_dispatch() -> Bool { } fn blocked_dispatch_workspace_html() -> String { - let policy_blocker = gunbc.roadmap_authority.authored( - id: "disp-policy-blocker", + let policy_blocker = gunbc.roadmap_authority.authored(identity: "rnfx_ENDCMSJ7A7XTXK91KKZD", id: "disp-policy-blocker", done: false, content: "policy prerequisite") - let model_blocker = gunbc.roadmap_authority.authored( - id: "disp-model-blocker", + let model_blocker = gunbc.roadmap_authority.authored(identity: "rnfx_2R3ZJWEHE8BDYVGJ82Z9", id: "disp-model-blocker", done: false, content: "model prerequisite") - let blocked = gunbc.roadmap_authority.authored_wi( - id: "disp-blocked", + let blocked = gunbc.roadmap_authority.authored_wi(identity: "rnfx_VRBYQ6ZVRKNMCNVXFCG6", id: "disp-blocked", done: false, content: "blocked work", intricacy: IntricacyLow, @@ -313,7 +309,7 @@ test fn witness_upcoming_node_preserves_multi_parent_dependencies() -> Bool { } fn superseded_dispatch_node() -> RoadmapNode { - gunbc.roadmap_authority.superseded(id: "disp1", by: "successor-lane", content: "dispatch-me") + gunbc.roadmap_authority.superseded(identity: "rnfx_8MRNSMQ6RXCDT2FC8ZKY", id: "disp1", by: "successor-lane", content: "dispatch-me") } test fn witness_superseded_node_marked_no_button() -> Bool { @@ -335,7 +331,7 @@ test fn witness_ticket_rows_render_structured() -> Bool { } fn long_brief_fixture() -> RoadmapNode { - gunbc.roadmap_authority.ticket_row(id: "brief-overrun-fixture", done: false, t: TicketFields { + gunbc.roadmap_authority.ticket_row(identity: "rnfx_J87HN1XAW9JEHR4KTWC0", id: "brief-overrun-fixture", done: false, t: TicketFields { headline: "fixture", brief: "w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w w", displaced_cost: "", @@ -403,8 +399,7 @@ test fn witness_authority_leads_within_budget() -> Bool { fn overlong_lead_node() -> RoadmapNode { let chunk = "a lead with no separator so the whole line stands " - gunbc.roadmap_authority.authored( - id: "overlong-lead", + gunbc.roadmap_authority.authored(identity: "rnfx_FKEE5NQSZN982W3TF9SS", id: "overlong-lead", done: false, content: join([chunk, chunk, chunk, chunk, chunk, chunk, chunk], ""), ) diff --git a/dag/test/claim/roadmap_spawner_witness_test.dag b/dag/test/claim/roadmap_spawner_witness_test.dag index 9604c79875e..4cb30136e77 100644 --- a/dag/test/claim/roadmap_spawner_witness_test.dag +++ b/dag/test/claim/roadmap_spawner_witness_test.dag @@ -20,6 +20,7 @@ fn nid(s: String) -> RoadmapNodeId { s } fn sized_derivable(id: String, prs: List, title: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_AEFPP8RJQ523MPCZQ7WR" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: "" }, carriers: [], @@ -32,6 +33,7 @@ fn sized_derivable(id: String, prs: List, title: String) -> RoadmapNode { fn accepted_node(base: RoadmapNode) -> RoadmapNode { RoadmapNode { + identity: "rnfx_Z9KZSNBGMW86W36XPHHP" as RoadmapNodeIdentity, node: base.node, line: base.line, carriers: base.carriers, @@ -44,6 +46,7 @@ fn accepted_node(base: RoadmapNode) -> RoadmapNode { fn rejected_node(base: RoadmapNode) -> RoadmapNode { RoadmapNode { + identity: "rnfx_BDFRJM5H1F0VB3E58P5N" as RoadmapNodeIdentity, node: base.node, line: base.line, carriers: base.carriers, @@ -56,6 +59,7 @@ fn rejected_node(base: RoadmapNode) -> RoadmapNode { fn unsized_derivable(id: String, prs: List, title: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_NHF9FERTMRDJHGNW226Y" as RoadmapNodeIdentity, node: nid(s: id), line: DerivableLine { prs: prs, title: title, description: "" }, carriers: [], @@ -217,6 +221,7 @@ test fn witness_dispatch_json_partitions_ready_upcoming_done() -> Bool { fn superseded_node(id: String, by: String, content: String) -> RoadmapNode { RoadmapNode { + identity: "rnfx_3RQ5SZ48Q7R3RWGH84BY" as RoadmapNodeIdentity, node: nid(s: id), line: SupersededLine { by: by, content: content }, carriers: [], @@ -243,6 +248,7 @@ test fn witness_superseded_unblocks_child_and_never_dispatches() -> Bool { test fn witness_superseded_control_open_parent_still_gates() -> Bool { let node_open = RoadmapNode { + identity: "rnfx_DY0Y547BC2HK2V12KA6T" as RoadmapNodeIdentity, node: nid(s: "sp-old"), line: AuthoredLine { done: false, content: "Old Lane Row" }, carriers: [],