policy-nexus/build/adr/custodian-workplan-identity/v1/revisions/accepted-1/index.html
tegwick 25dd59a7c6
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 48s
Publish estate ADRs, platform ADRs, and IAM Profile v0.3
T04 classifies the remaining corpus. Chapter 9 on the first-wave
arc42 stubs now matches what is published.
2026-08-19 01:09:41 +02:00

278 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="4039c9d1c08c92014ecc0a65dda63cc73ba187bb">
<meta name="policy-source-digest" content="69f463795bdf1769c11415e9c8afa170d6fe62f04a554f7573f269000b5c4b08">
<title>Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>CUST-ADR-007</span> <span class="stat">accepted · accepted-1</span> <span>the-custodian</span> <span>reviewed 2026-08-17</span><span>generated from canonical source — do not edit</span></div><h1>Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology</h1><p class="sub">Source: <code>the-custodian · canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</code></p><p class="sub">Review due: 2027-02-17</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#context"><span class="n">·</span>Context</a></li><li><a href="#decision"><span class="n">·</span>Decision</a></li><li><a href="#consequences"><span class="n">·</span>Consequences</a></li><li><a href="#migration-needs-a-separate-ruling"><span class="n">·</span>Migration — needs a separate ruling</a></li><li><a href="#references"><span class="n">·</span>References</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p>Accepted 2026-08-17. Identifier uniqueness, the registrar model, lifecycle protection, and worker topology are settled.</p>
<p><strong>Remediation of existing collisions (§ Migration) remains an open ruling.</strong> It is disruptive, touches six repositories, and no active work depends on it — all five duplicated identifiers are <code>finished</code>.</p>
</section>
<section id="context"><h2>Context</h2>
<p>On 2026-08-16, <code>STATE-WP-0080</code> was found registered <strong>twice, in two different databases</strong>, with two different workplan UUIDs and two disjoint sets of task UUIDs:</p>
<div class="scroll"><table><thead><tr><th>Registrar</th><th>Workplan UUID</th><th>Reachable at <code>127.0.0.1:8000</code></th></tr></thead><tbody><tr><td>Workstation hub (<code>make api</code>, local postgres)</td><td><code>03f38314</code></td><td>yes</td></tr><tr><td>A second instance, over a second database</td><td><code>bbfce36a</code></td><td><strong>404</strong></td></tr></tbody></table></div>
<p>The second instance is <em>not</em> identifiable from the commit author. <code>custodian-sync@railiance.local</code> is a hardcoded default git identity in <code>scripts/consistency_check.py:1899</code> (<code>GIT_SYNC_USER_NAME</code> / <code>GIT_SYNC_USER_EMAIL</code>), so every machine running <code>fix-consistency</code> commits under that name. The discriminator is the timezone: sync commits appear under both <code>+0000</code> and <code>+0200</code> (the workstation's offset), which is independent evidence of two machines writing to one repository.</p>
<p>Both write their IDs into the same git-tracked workplan file, so each sync overwrites the other's IDs and the file flip-flops on every round trip. The same commit (<code>ff909e1</code>, "renormalize lifecycle state [auto]") also promoted the workplan <code>proposed</code><code>active</code> without human review.</p>
<p>A fleet scan of 955 hub workplans (525 with parseable <code>PREFIX-WP-NNNN</code> identifiers) found this is not an isolated incident. Two distinct identity defects are live:</p>
<p><strong>Prefix reuse across repositories:</strong></p>
<pre>CUST-WP- → state-hub, the-custodian
RAIL-BS-WP- → railiance-bootstrap, railiance-cluster
RAILIANCE-WP- → railiance-apps, railiance-forge, railiance-platform, railiance-telemetry</pre>
<p><code>PRJ-WP-</code> is a latent fourth: <code>statehub register</code> derives it from the <code>prj-</code> flavor marker, so every project repository would collide (see <code>RMGR-WP-0004</code>).</p>
<p><strong>Running-number reuse:</strong></p>
<pre>CUST-WP-0000 the-custodian ×2
CUST-WP-0010 the-custodian ×2
CUST-WP-0045 the-custodian ×2
RAILIANCE-WP-0015 railiance-platform, railiance-apps
RAILIANCE-WP-0016 railiance-platform ×2, railiance-apps</pre>
<p><code>RAILIANCE-WP-0015</code> is actively cited in operational memory as the cnpg-backup gate in <code>railiance-apps</code>; a second record of the same name in <code>railiance-platform</code> makes that citation ambiguous.</p>
<p>The <code>C-26</code> consistency check already flags prefix nonconformance <em>within</em> a repo against a canonical prefix, but nothing enforces uniqueness <em>across</em> repos, and nothing prevents number reuse.</p>
</section>
<section id="decision"><h2>Decision</h2>
<p><strong>1. A workplan identifier is globally unique.</strong> <code>PREFIX-WP-NNNN</code> names exactly one workplan across the entire fleet, for all time.</p>
<div class="rule-quote"><p><strong>Amended 2026-08-17 by <code>ADR-011</code> decision 2.</strong> Uniqueness and forward-only allocation are <strong>namespace-scoped</strong>, not global; global identity is the pair <code>(namespace, identifier)</code>, written <code>PREFIX-WP-NNNN@namespace</code> when foreign. Global sequential allocation would require a central coordinator — the exact dependency federation must survive. Everything below holds unchanged <strong>within</strong> a namespace, which is where all current work sits.</p></div>
<ul><li>A workplan prefix is owned by exactly one repository. No two repositories may use the same prefix.</li><li>A running number is never reused within a prefix, including after a workplan is cancelled, archived, or deleted. Numbers are allocated forward only.</li><li>Prefixes are derived from the <em>project or repository identity</em>, never from a flavor marker or category (<code>PRJ-WP-</code> is invalid by construction).</li></ul>
<p><strong>2. Hub identifiers stored in repository files must be derivable, not database-local.</strong> The defect is structural: a database-local key is stored in a shared artifact, so each database overwrites the other's value on every sync. It also inverts ADR-001 — a file carrying a hub's private key is the file holding hub state.</p>
<p><em>Target state (C2).</em> <code>state_hub_workstream_id</code> and <code>state_hub_task_id</code> become <strong>deterministic</strong>: UUIDv5 derived from the workplan identifier. Per <code>ADR-011</code> decision 3 the derivation input is the pair <code>(namespace, identifier)</code>, not the identifier alone — deriving from the identifier alone would make two forks holding unrelated work under the same number compute the same UUID. Every instance computes the same value independently, writeback becomes idempotent, and any number of hub instances may coexist without coordination. The field shape is unchanged, so consumers keep working; only the provenance of the value changes.</p>
<p>This has a hard prerequisite: deterministic derivation from a <strong>non-unique</strong> identifier would manufacture collisions rather than remove them. Two project repos sharing <code>PRJ-WP-</code> would compute the same UUID for different workplans. Decision 1 must therefore be enforced before derivation ships.</p>
<p><em>Interim state (A).</em> Until derivation lands, exactly one instance writes hub identifiers into repository files. Other instances may read, project, and serve, but must not mint workplan or task UUIDs into git-tracked files.</p>
<div class="rule-quote"><p><strong>Corrected 2026-08-17, superseded by <code>ADR-010</code> decisions 13.</strong> This decision originally described workstation hubs as "development read replicas". That was wrong on both counts: the workstation instance was not a replica, and it was the <em>larger</em> of the two, holding 306 more workplans than the primary. The two instances were peer databases. <code>ADR-010</code> establishes the central hub as authoritative and local instances as rebuildable caches, which is what makes this interim rule coherent.</p></div>
<p>The interim is policy, enforced by discipline, and it has a real cost: registration requires connectivity to the registrar, so disconnected work cannot register. That cost is accepted only until C2 removes the need for it, at which point the number of hub instances becomes an availability choice rather than a correctness constraint.</p>
<p><em>Rejected.</em> Collapsing to a single shared database (workstation hubs pointing at the production database) would also make identifiers consistent by construction, but it eliminates offline capability — cutting directly against <code>STATE-WP-0068</code> (offline write buffer and edge relay) — and couples all local work to tunnel availability.</p>
<p><em>Ownership.</em> Both the interim guard and the derivation belong to <code>repo-manager</code> under decision <code>747011c6</code>, which already places file-backed record indexing and reconciliation there. Building either in <code>state-hub</code> would invest in a component being retired under <code>STATE-WP-0079</code>.</p>
<p>Migration scope for C2: <strong>758 workplan files</strong> across the fleet currently carry these fields.</p>
<p><strong>3. Lifecycle status is not automatically promoted.</strong> An automated normalization pass may report drift; it may not move a workplan from <code>proposed</code> to <code>active</code>. <code>proposed</code> means awaiting human review, and an automation that promotes it destroys the meaning of the review gate.</p>
<p><strong>4. Repository manipulation is performed by a worker agent in that repository.</strong> This is the default topology.</p>
<ul><li>A worker acting in repo X owns changes to repo X.</li><li>Multiple independent top-level workers inside a single repository are an <strong>exception</strong>, requiring an explicit reason, not a routine mode of operation.</li><li>Concurrent independent writers are what turned a two-registrar bug into repeated git divergence.</li></ul>
<p><strong>5. Project (<code>prj-</code>) repositories may act across their participating repositories.</strong> When work is governed by a project repo, its tasks may direct changes across every repository the project names, through the project's work agent, where that is more efficient than delegating.</p>
<p>This is a deliberate, scoped exception to decision 4: the project repo already owns cross-repo sequencing and its <code>SCOPE.md</code> names its participants, so its authority is declared rather than ad hoc. It does not license a worker in an arbitrary repository to reach into others.</p>
</section>
<section id="consequences"><h2>Consequences</h2>
<p><strong>Positive.</strong> Workplan identifiers become citable without qualification. Hub IDs stop flip-flopping in git. The <code>proposed</code> status regains meaning. Cross-repo authority becomes something a repository declares rather than something any session assumes.</p>
<p><strong>Negative.</strong> Existing collisions must be remediated (see below), which is disruptive. Workstation sessions lose the ability to register workplans directly and must route through the registrar or a worker in the owning repo. Prefix allocation needs a fleet-level registry, which is new machinery.</p>
<p><strong>Enforcement.</strong> Prefix ownership, uniqueness, and forward-only numbering are repository standards, so they belong to Repo Manager under decision <code>747011c6</code> (<code>RMGR-WP-0004</code>), not to a hub. Canon defines the rule; Repo Manager checks it.</p>
</section>
<section id="migration-needs-a-separate-ruling"><h2>Migration — needs a separate ruling</h2>
<p>Three prefixes and five identifiers are already colliding. Remediation options, in increasing cost:</p>
<ol><li><strong>Freeze and forward-fix.</strong> Accept existing collisions as historical, enforce uniqueness only for new workplans. Cheapest; leaves <code>RAILIANCE-WP-0015</code> permanently ambiguous.</li><li><strong>Renumber the live collisions only.</strong> Fix identifiers that are still cited or active; leave <code>finished</code>/<code>archived</code> duplicates alone.</li><li><strong>Full renaming.</strong> Give <code>railiance-apps</code>, <code>railiance-forge</code>, <code>railiance-platform</code>, <code>railiance-telemetry</code> distinct prefixes, likewise <code>railiance-bootstrap</code>/<code>railiance-cluster</code> and <code>state-hub</code>'s legacy <code>CUST-WP-</code> files. Touches six repositories and every inbound reference.</li></ol>
<p><strong>Ruled 2026-08-17: option 2.</strong> Renumber live collisions; leave <code>finished</code>/<code>archived</code> duplicates as historical record.</p>
<h3>The live renumber list is empty</h3>
<p>Verified against the fleet scan. All five duplicated identifiers are <code>finished</code>:</p>
<pre>CUST-WP-0000 the-custodian ×2 finished
CUST-WP-0010 the-custodian ×2 finished
CUST-WP-0045 the-custodian ×2 finished
RAILIANCE-WP-0015 apps, platform finished
RAILIANCE-WP-0016 apps, platform ×2 finished</pre>
<p>No workplan in a <code>proposed</code>, <code>ready</code>, <code>active</code>, <code>blocked</code>, or <code>backlog</code> state shares an identifier with another. <strong>Option 2 therefore requires no renumbering today.</strong> Historical duplicates stay, including the <code>RAILIANCE-WP-0015</code> ambiguity between <code>railiance-apps</code> and <code>railiance-platform</code>; citations of it must name the repository.</p>
<h3>The structural cause is not historical</h3>
<p>Option 2 governs <em>remediation</em>. It does not exempt anything from decision 1, which is accepted canon: one prefix, one repository, forward-only numbering.</p>
<p>Three shared prefixes are still in use across seven repositories, and each is a <strong>single number line being allocated from concurrently</strong>:</p>
<pre>RAIL-BS-WP- bootstrap 8, 9
cluster 7, 10, 11, 12, 13, 14
RAILIANCE-WP- platform 5, 8-17 (16 twice — an internal duplicate)
apps 15, 16 &lt;- already collided with platform
forge 2
telemetry 1
CUST-WP- the-custodian 50 plans
state-hub 4 legacy plans (canonical prefix is STATE-WP)</pre>
<p><code>RAIL-BS-</code> and <code>RAILIANCE-</code> are actively growing — <code>RAIL-BS-WP-0014</code> (<code>ready</code>), <code>RAILIANCE-WP-0002</code> (<code>ready</code>), <code>RAILIANCE-WP-0001</code> (<code>proposed</code>), all created 2026-08-11 or later. The <code>RAILIANCE-WP-0015</code>/<code>0016</code> collisions were not a historical accident; they are what concurrent allocation from a shared sequence produces, and it will recur at the next concurrent allocation.</p>
<p><code>CUST-</code> is dormant on the <code>state-hub</code> side — four legacy plans, one in <code>backlog</code> — and needs no split, only a prefix-ownership assertion.</p>
<h3>Prefix assignments</h3>
<p><code>RAIL-BS-WP-</code> is <strong>retired</strong> (2026-08-17). Neither repository keeps it: <code>railiance-cluster</code> adopts <code>RCLUSTER-WP-</code> for active and future plans; <code>railiance-bootstrap</code> adopts <code>RBS-WP-</code> for future plans. Finished and archived files keep <code>RAIL-BS-WP-</code> as historical record, consistent with option 2.</p>
<p>Migrating plans keep their running numbers — the prefix changes, the number does not. This preserves traceability and cannot violate forward-only allocation, because neither new prefix has prior history. <code>railiance-bootstrap</code> begins at <code>RBS-WP-0010</code>, above its historical maximum, leaving the lower range free should its finished plans ever be adopted into the new prefix.</p>
<p><code>RAILIANCE-WP-</code> should follow the same pattern — retired rather than awarded to one repository, since it names a family rather than a repository and so fails decision 1 for the same reason <code>PRJ-WP-</code> does. Assignment of the four successor prefixes is outstanding.</p>
<p>Execution of each rename belongs to a worker in the owning repository under decision 4. <code>RMGR-WP-0004-T09</code> records assignments and the numbering rule; it does not perform renames.</p>
<p><strong>Consequence.</strong> Prefix ownership must be assigned for all three shared prefixes before the next workplan is created in the affected repositories. This is forward conformance under decision 1, not migration, and is tracked as <code>RMGR-WP-0004-T09</code>. Renaming the <em>historical</em> files is explicitly not required — that would be option 3, which was rejected.</p>
</section>
<section id="references"><h2>References</h2>
<ul><li>Decision <code>747011c6</code> — repository standards belong to Repo Manager</li><li>ADR-001 — workplans originate as repo files; hub is a read model</li><li><code>RMGR-WP-0004</code> — repository standards conformance and governed scaffolding</li><li><code>STATE-WP-0080</code> — register scaffolding handoff</li><li>Fleet scan 2026-08-16: 955 hub workplans, 525 parseable identifiers, 3 reused prefixes, 5 reused identifiers</li></ul>
</section><footer><span>CUST-ADR-007 · accepted-1 · accepted</span><span>the-custodian · canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</span></footer></main></div></div></html>