policy-nexus/build/adr/custodian-workplans-as-repo-artefacts/v1/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

298 lines
26 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="64b11785b683cf21ba2aca18e3b8f3301d6070e6a022df6efc722597a8547334">
<title>Workplans and Work Items Are Repository Artefacts</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-001</span> <span class="stat">accepted · accepted-1</span> <span>the-custodian</span> <span>reviewed 2026-02-28</span><span>generated from canonical source — do not edit</span></div><h1>Workplans and Work Items Are Repository Artefacts</h1><p class="sub">Source: <code>the-custodian · canon/architecture/adr-001-workplans-as-repo-artefacts.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</code></p><p class="sub">Review due: 2026-08-28</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="#workplan-file-convention"><span class="n">·</span>Workplan File Convention</a></li><li><a href="#rebuild-principle"><span class="n">·</span>Rebuild Principle</a></li><li><a href="#consequences"><span class="n">·</span>Consequences</a></li><li><a href="#alternatives-considered"><span class="n">·</span>Alternatives Considered</a></li><li><a href="#workplan-closure-protocol"><span class="n">·</span>Workplan Closure Protocol</a></li><li><a href="#related"><span class="n">·</span>Related</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p>Accepted.</p>
</section>
<section id="context"><h2>Context</h2>
<p>During early State Hub development (v0.1–v0.4), workstreams and tasks were created directly in the PostgreSQL database via MCP bootstrap tools (<code>create_workstream</code>, <code>create_task</code>). This made the database the <strong>origin</strong> of work items — not a cache or index. The pattern was convenient for rapid bootstrapping but is architecturally wrong for a system built on the values of auditability, reversibility, and local-first sovereignty.</p>
<p>The trigger for formalising this decision was the creation of the v0.5 workplan ("Dynamic Domains &amp; Multi-Repo") directly in the state-hub database without a corresponding file artefact in any repository.</p>
</section>
<section id="decision"><h2>Decision</h2>
<p><strong>Workplans and work items MUST originate as Markdown files in the repository that owns them.</strong> The Custodian State Hub indexes and caches those artefacts but is never their origin.</p>
<p>Formally: the state-hub must (theoretically, given sufficient compute and time) be able to <strong>rebuild its full representation</strong> of repositories, their workplans, tasks, decisions, and dependencies by reading only the files in the registered repositories. No information that matters for coordination should exist solely in the database.</p>
<h3>Corollaries</h3>
<ol><li><strong>Repository is authoritative.</strong> A workplan file is the canonical record. The state-hub database row is a materialized cache of that file.</li></ol>
<ol><li><strong>Database is disposable.</strong> Dropping and re-creating the database from registered repository files must produce an equivalent state. The database is an operational convenience, not a primary store.</li></ol>
<ol><li><strong>MCP bootstrap tools become index/sync tools.</strong> <code>create_workstream</code> and <code>create_task</code> are acceptable as convenience wrappers only if they write the file first and then register the row. Using them to write DB-only records violates this ADR.</li></ol>
<ol><li><strong>The rebuild principle implies a sync mechanism.</strong> There must be a defined path (<code>make sync-workplans</code> or equivalent) by which the state-hub reads workplan files from registered repositories and upserts its database state.</li></ol>
</section>
<section id="workplan-file-convention"><h2>Workplan File Convention</h2>
<p>Each workplan lives in a <code>workplans/</code> directory in the repository that owns the work. The owning repository is identified by domain.</p>
<h3>Location</h3>
<pre>&lt;repo-root&gt;/workplans/&lt;id&gt;-&lt;slug&gt;.md</pre>
<p>Examples:</p>
<ul><li><code>the-custodian/workplans/CUST-WP-0005-dynamic-domains.md</code></li><li><code>railiance/workplans/RAIL-WP-0001-three-phoenix.md</code></li></ul>
<h3>Frontmatter Schema</h3>
<pre>---
id: CUST-WP-0005 # human-readable workplan ID, unique per repo
type: workplan
title: &quot;State Hub v0.5 — Dynamic Domains &amp; Multi-Repo&quot;
domain: custodian # must match a registered domain slug
status: active # active | completed | archived
owner: custodian
topic_slug: custodian # maps to a state-hub Topic slug
created: &quot;2026-02-28&quot;
updated: &quot;2026-02-28&quot;
---</pre>
<h3>Task Items</h3>
<p>Tasks are embedded in the workplan file as headed sections. Each task section carries its own YAML block:</p>
<pre>## P1.1 — Create `domains` table + Alembic migration
</pre>
<p>id: CUST-WP-0005-T001 status: todo priority: high</p>
<pre>
Task description prose here.</pre>
<p>The state-hub parses these embedded task blocks during ingestion and upserts rows in the <code>tasks</code> table. The <code>id</code> field is the stable external key; the state-hub UUID is internal and opaque.</p>
<h3>Decision Items</h3>
<p>Decisions are separate files or embedded sections following the same pattern, using <code>type: decision</code> in frontmatter.</p>
</section>
<section id="rebuild-principle"><h2>Rebuild Principle</h2>
<p>The rebuild sequence for a clean state-hub:</p>
<ol><li><code>make migrate</code> — create schema</li><li><code>make seed-domains</code> — insert domain rows (domains.yaml in canon/)</li><li>For each registered repository: <code>make sync-workplans REPO=&lt;slug&gt;</code> — parse workplan files and upsert workstreams, tasks, decisions</li><li><code>make sync-progress</code> — replay progress events from episodic memory logs</li></ol>
<p>After step 4 the database must be functionally equivalent to the live state.</p>
</section>
<section id="consequences"><h2>Consequences</h2>
<h3>Immediate</h3>
<ul><li>The v0.5 and v0.3 workplans created DB-first in this session are <strong>legacy records</strong> that violate this ADR. Remediation: write the corresponding workplan files, then mark the DB rows as <code>source: db-legacy</code> until a sync mechanism can reconcile them.</li></ul>
<ul><li>The state-hub CLAUDE.md design-boundary note must be updated: the MCP bootstrap tools are permitted only as write-through tools (file + DB), never as DB-only tools.</li></ul>
<h3>Medium Term</h3>
<ul><li>A <code>make sync-workplans</code> command must be implemented as part of the managed-repos / contribution-tracking infrastructure (see v0.3 workplan).</li></ul>
<ul><li>The <code>managed_repos</code> table is the prerequisite: the state-hub must know which repositories to scan.</li></ul>
<ul><li>Workplan file format must be versioned and parsed by a dedicated loader (<code>state-hub/scripts/sync_workplans.py</code>).</li></ul>
<h3>Long Term</h3>
<ul><li>When the state-hub grows to cover multiple users or teams, this principle ensures that no coordination state can be lost by a database failure. Every repository is its own resilient shard of the coordination graph.</li></ul>
<ul><li>This is the foundation for the "transgenerational" property: workplans in git survive database migrations, cloud provider changes, and system rebuilds.</li></ul>
</section>
<section id="alternatives-considered"><h2>Alternatives Considered</h2>
<p><strong>Database-first with export:</strong> Create in DB, export to files on demand. Rejected: export is easily skipped and files become secondary/stale.</p>
<p><strong>Files-only, no database:</strong> Parse files on every query. Rejected: impractical at scale; the database is a necessary cache for cross-repo aggregation and real-time dashboard queries.</p>
<p><strong>Hybrid with explicit sync flag:</strong> Mark some records as "db-authoritative" and others as "file-authoritative." Rejected: introduces ambiguity about which records matter; violates the "single source of truth" principle.</p>
</section>
<section id="workplan-closure-protocol"><h2>Workplan Closure Protocol</h2>
<p>When a workplan is about to be marked <code>finished</code>, the responsible agent MUST perform a closure review before writing the status change. This prevents the stale-task accumulation that this ADR was designed to make detectable.</p>
<h3>Steps</h3>
<ol><li><strong>Query all non-done tasks</strong> in the workplan via <code>GET /tasks/?workplan_id=&lt;uuid&gt;</code> (legacy alias: <code>workstream_id</code>; filter for <code>todo</code>, <code>in_progress</code>, <code>blocked</code>).</li></ol>
<ol><li><strong>Classify each task</strong> into one of three outcomes:</li></ol>
<div class="scroll"><table><thead><tr><th>Outcome</th><th>Action</th></tr></thead><tbody><tr><td><strong>Done</strong> — work was completed, DB record just wasn't updated</td><td><code>PATCH /tasks/{id}/ {&quot;status&quot;: &quot;done&quot;}</code></td></tr><tr><td><strong>Cancelled</strong> — dropped, superseded, or out of scope</td><td><code>PATCH /tasks/{id}/ {&quot;status&quot;: &quot;cancelled&quot;, &quot;blocking_reason&quot;: &quot;&lt;why&gt;&quot;}</code></td></tr><tr><td><strong>Carry-forward</strong> — genuinely unfinished, belongs in the next run</td><td>Leave open; note in closure review; trigger new workplan</td></tr></tbody></table></div>
<ol><li><strong>Append a <code>## Closure Review</code> section</strong> to the workplan file:</li></ol>
<pre> ## Closure Review — YYYY-MM-DD
**Outcome:** All tasks completed / N tasks carried forward / N tasks dropped.
### Completed (DB updated)
- TASK-ID — title
### Cancelled (dropped)
| Task | Reason |
|------|--------|
| TASK-ID — title | Superseded by X |
### Carried forward
| Task | Target workplan |
|------|----------------|
| TASK-ID — title | CUST-WP-XXXX |</pre>
<ol><li><strong>If any tasks are carried forward</strong>: do not mark the workplan <code>finished</code> yet. Create the new workplan file (or amend an existing active one), then close the current workplan.</li></ol>
<ol><li><strong>Update the workplan frontmatter</strong> <code>status: finished</code> and <code>updated:</code> date.</li></ol>
<ol><li><strong>Mark the workplan <code>finished</code></strong> in the state hub via MCP or API (<code>update_workplan_status</code>).</li></ol>
<h3>Daily Stale-Task Cleanup</h3>
<p>As a safety net for cases where the closure review was skipped or incomplete, a cleanup script cancels any surviving open tasks in completed/archived workstreams:</p>
<pre>cd ~/the-custodian/state-hub
make cleanup-stale # run immediately
# or add to cron:
# 0 3 * * * cd ~/the-custodian/state-hub &amp;&amp; make cleanup-stale</pre>
<p>The script (<code>scripts/cleanup_stale_tasks.py</code>) emits a <code>cleanup</code> progress event recording which tasks were cancelled and in which workstreams. Tasks cancelled by the cleanup carry a <code>blocking_reason</code> noting they should be verified against the workplan file.</p>
<p>The closure review is the primary mechanism; the cleanup is the fallback. If the cleanup regularly cancels tasks, it signals that closure reviews are being skipped — that is the process failure to address, not just the stale tasks.</p>
</section>
<section id="related"><h2>Related</h2>
<ul><li>Custodian Constitution v0.1 §2 (Powers) — canon changes require review gate</li><li>ADR-000 (forthcoming) — overall Custodian architecture principles</li><li>State Hub v0.3 workplan — <code>sync_workplans.py</code> is a Phase 4 deliverable</li><li><code>canon/values/foundational_values_v0.1.md</code> — Local-first, Auditability, Reversibility</li></ul>
</section><footer><span>CUST-ADR-001 · accepted-1 · accepted</span><span>the-custodian · canon/architecture/adr-001-workplans-as-repo-artefacts.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</span></footer></main></div></div></html>