Publish activity-core and ops-warden ADRs
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 55s

Harvest ops-warden's five ready records. Register ACT-ADR-001–005 after
publication metadata landed in activity-core. Markitect arc42-v1 and
the estate infospace are now the validation and retrieval path.
This commit is contained in:
tegwick 2026-08-19 00:17:13 +02:00
parent 64b47d73b9
commit 7d608c0d57
36 changed files with 5850 additions and 128 deletions

View file

@ -0,0 +1,247 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="41a3fb8b81bd521a5fa21af114975c54532df3ad">
<meta name="policy-source-digest" content="b9005e5f23dce53169e5f614ed461ce49e975266ff9a8fc01e2c6364fc5c91d3">
<title>Ops runs vs development work records — claim queue and plane split</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>ACT-ADR-005</span> <span class="stat">accepted · accepted-1</span> <span>activity-core</span> <span>reviewed 2026-08-03</span><span>generated from canonical source — do not edit</span></div><h1>Ops runs vs development work records — claim queue and plane split</h1><p class="sub">Source: <code>activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · 41a3fb8b81bd521a5fa21af114975c54532df3ad</code></p><p class="sub">Review due: 2027-02-03</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="#topology"><span class="n">·</span>Topology</a></li><li><a href="#implementation-stack"><span class="n">·</span>Implementation stack</a></li><li><a href="#references"><span class="n">·</span>References</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p><strong>Accepted</strong> (2026-08-03).</p>
</section>
<section id="context"><h2>Context</h2>
<p>The fleet has two healthy patterns that were forced into one vague “task” idea:</p>
<ol><li><strong>Development / coordination work</strong> — structured workplans and tasks as <strong>repo files</strong>, indexed by <strong>State Hub</strong> on <strong>coulombcore</strong> (<code>fix-consistency</code>, UUIDv7 write-back). This matches ADR-001 and <code>work-record-types_v0.1.md</code> and is how humans and coding agents ship product.</li></ol>
<ol><li><strong>Operations / scheduled automation</strong> — activity-core Temporal schedules (when / what / where), rein-aharness execution (how), completion events (idempotence). Instances of “todays FI brief” or “this prune fire” are <strong>ephemeral runs</strong>, not multi-day workplan bodies.</li></ol>
<p>Practice collapsed (2) into broken paths:</p>
<div class="scroll"><table><thead><tr><th>Path</th><th>Failure</th></tr></thead><tbody><tr><td>Emit <code>activity_task_spawn</code> progress only</td><td>Append-only; <strong>not claimable</strong>; schedule can “succeed” while nothing runs</td></tr><tr><td><code>ISSUE_SINK_TYPE=rest</code> → issue-core → Forgejo</td><td>Spam + 503s; issue-core INTENT forbids origin of fleet work</td></tr><tr><td>Host systemd timers dual-clocked with Temporal</td><td>Shadow scheduler; weak self-healing</td></tr></tbody></table></div>
<p>issue-cores <strong>correct</strong> role is a <strong>connector facade</strong> over external trackers (Forgejo, GitHub, Jira, …) so agents need not know each backend. It is <strong>not</strong> the origin of work records and <strong>not</strong> the default internal ops queue. <strong>Gitea is out of scope</strong> for this fleet; the self-hosted forge is <strong>Forgejo</strong>.</p>
<p>Canon already allows a DB-only exception for “runtime operations data (logs, metrics, run histories, token events)” (<code>work-record-types_v0.1.md</code>). This ADR names that exception for <strong>ops runs</strong>.</p>
</section>
<section id="decision"><h2>Decision</h2>
<h3>1. Two planes for “work,” one vocabulary for people</h3>
<div class="scroll"><table><thead><tr><th>Plane</th><th>Artefact</th><th>Home</th><th>Lifecycle</th></tr></thead><tbody><tr><td><strong>A — Development / coordination</strong></td><td>Work records (<code>workplan</code>, <code>task</code>, <code>intake</code>, …)</td><td>Repo files + State Hub (coulombcore)</td><td>File-backed, reviewable, multi-day</td></tr><tr><td><strong>B — Recurrence control</strong></td><td>Activity definitions + Temporal schedules</td><td>activity-core (railiance)</td><td>Git definitions; DB schedule state</td></tr><tr><td><strong>C — Ops execution</strong></td><td><strong><code>ops_run</code></strong> (runtime claim object)</td><td><strong>activity-core DB + claim API</strong></td><td>open → claimed → succeeded \</td><td>failed \</td><td>expired</td></tr><tr><td><strong>D — External collab</strong></td><td>Tracker issues</td><td><strong>issue-core</strong> optional projection to <strong>Forgejo</strong> (and others)</td><td>Never default for internal automation</td></tr></tbody></table></div>
<p>Coding agents orient on <strong>Plane A</strong> via State Hub. Automation orients on <strong>Planes B+C</strong>. Humans outside the fleet may see <strong>Plane D</strong> only when deliberately projected.</p>
<h3>2. activity-core owns the ops_run claim queue</h3>
<p>When a definition emits a TaskSpec for internal fleet work:</p>
<ol><li>Write <strong><code>ops_run</code></strong> row (durable, claimable) with idempotency key (e.g. <code>fi-daily:2026-08-04</code> for once-per-day briefs).</li><li>Optionally dual-write <code>activity_task_spawn</code> progress for existing observers.</li><li>Do <strong>not</strong> open a Forgejo issue.</li><li>Do <strong>not</strong> create a workplan task file for that days fire.</li></ol>
<p>Claim API (sketch; implement in ACTIVITY-WP-0026):</p>
<ul><li><code>POST /ops-runs/claim</code> — lease next open run matching labels / worker id</li><li><code>POST /ops-runs/{id}/complete</code> — succeeded + completion metadata</li><li><code>POST /ops-runs/{id}/fail</code> — failed + retry policy</li><li><code>GET /ops-runs?state=open</code> — operator visibility</li></ul>
<p>activity-core remains <strong>when / what / where</strong> only: it does <strong>not</strong> run domain LLM sessions or hold tenant git credentials.</p>
<h3>3. rein-aharness owns how (approach selection + execute)</h3>
<ul><li>Continuous <strong>claim loop</strong> (service / Deployment), not dual wall-clock timers as source of truth.</li><li>Host systemd timers become <strong>break-glass</strong> after cutover.</li><li><strong>Approach selection</strong> at claim time (minimal table v1):</li></ul>
<div class="scroll"><table><thead><tr><th>Labels / definition family</th><th>Approach</th></tr></thead><tbody><tr><td>mail / mail-intake</td><td>deterministic adapter (+ optional triage)</td></tr><tr><td>research-brief / fi-daily</td><td>structured llm-connect (<code>fi-research-brief</code>)</td></tr><tr><td>binky rhythm</td><td>structured llm-connect (<code>brief-daily</code>)</td></tr><tr><td>agent-session</td><td>persona + tool profile session</td></tr><tr><td>unknown</td><td>refuse; do not invent</td></tr></tbody></table></div>
<ul><li>Success posts <strong>domain completion event</strong> (e.g. <code>fi_daily_brief</code>) so resolvers set <code>due=false</code>, and closes the <code>ops_run</code>.</li></ul>
<h3>4. State Hub remains the work-record read model</h3>
<ul><li>Stays on <strong>coulombcore</strong>; railiance peers via edge relay / ops-bridge.</li><li>May <strong>project</strong> open/failed ops runs for fleet ops UI (read-only), but is <strong>not</strong> the claim authority.</li><li>Development workplans/tasks continue file + <code>fix-consistency</code> only.</li></ul>
<h3>5. issue-core is Forgejo (and multi-tracker) facade only</h3>
<ul><li><strong>Forgejo</strong> is the self-hosted forge; do not plan for <strong>Gitea</strong> as a product.</li><li>Optional <strong>project / link</strong> of an existing work-record UUID to an external issue when collaboration needs it.</li><li>Authenticated <code>POST /issues/</code> means “create/link <strong>external</strong> tracker work,” never “spawn fleet automation.”</li><li>Internal automation <strong>must not</strong> use issue-core as the ops claim queue unless a future design adds an <strong>internal-only</strong> backend with projection <strong>hard-off</strong> for automation labels — out of scope unless a later ADR says otherwise.</li></ul>
<h3>6. Promotion path ops → dev</h3>
<p>When an ops run discovers multi-day product work (e.g. “collect Kimi K3 under new quota”), the executor or human <strong>promotes</strong> an <code>intake</code> / workplan in the domain repo (Plane A). Ops runs never become permanent fake workplans.</p>
</section>
<section id="consequences"><h2>Consequences</h2>
<h3>Positive</h3>
<ul><li>Self-healing recurrence: schedule → claimable row → claim → complete/fail.</li><li>Aligns issue-core INTENT with practice; stops Forgejo spam path.</li><li>Keeps activity-core thin; keeps rein-aharness as sole session/credential shell.</li><li>Preserves the good dev loop (files + State Hub).</li></ul>
<h3>Negative / cost</h3>
<ul><li>New schema + API + migrator in activity-core.</li><li>rein-aharness must leave issue-core-only poll as primary for ops.</li><li>Temporary dual-write and dual timers until cutover proven.</li></ul>
<h3>Non-goals</h3>
<ul><li>Replacing workplans for development.</li><li>Running domain briefs inside activity-core workers.</li><li>Global <code>ISSUE_SINK_TYPE=rest</code> to Forgejo.</li><li>Gitea support or migration paths.</li></ul>
</section>
<section id="topology"><h2>Topology</h2>
<pre>coulombcore: State Hub + work-record registry
railiance: activity-core (Temporal + ops_run queue)
rein-aharness (claim loop + approach + llm-connect)
domain checkouts
forgejo: optional external collab via issue-core projection only</pre>
</section>
<section id="implementation-stack"><h2>Implementation stack</h2>
<div class="scroll"><table><thead><tr><th>Workplan</th><th>Owner</th><th>Role</th></tr></thead><tbody><tr><td>ACTIVITY-WP-0026</td><td>activity-core</td><td>ops_run schema, claim API, emit path, dual-write</td></tr><tr><td>REIN-A-0002</td><td>rein-aharness</td><td>claim loop, approach table, FI/Binky cutover</td></tr><tr><td>ISSUE-WP-0006</td><td>issue-core</td><td>Forgejo-only language; projection boundary; no ops queue</td></tr><tr><td>STATE-WP-0078</td><td>state-hub</td><td>Read projection of ops_run for ops UI (optional consume)</td></tr></tbody></table></div>
</section>
<section id="references"><h2>References</h2>
<ul><li><code>docs/recurring-automations-playbook.md</code></li><li><code>docs/task-emission-consumer-contract.md</code></li><li><code>docs/issue-core-emission-boundary.md</code></li><li>ACTIVITY-WP-0022, ACTIVITY-WP-0023 (G2)</li><li><code>the-custodian/canon/standards/work-record-types_v0.1.md</code></li><li><code>issue-core/INTENT.md</code> (work-record boundary)</li><li><code>state-hub/docs/cluster-operating-model.md</code> (coulombcore primary)</li></ul>
</section><footer><span>ACT-ADR-005 · accepted-1 · accepted</span><span>activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · 41a3fb8b81bd521a5fa21af114975c54532df3ad</span></footer></main></div></div></html>

View file

@ -0,0 +1,247 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="41a3fb8b81bd521a5fa21af114975c54532df3ad">
<meta name="policy-source-digest" content="b9005e5f23dce53169e5f614ed461ce49e975266ff9a8fc01e2c6364fc5c91d3">
<title>Ops runs vs development work records — claim queue and plane split</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>ACT-ADR-005</span> <span class="stat">accepted · accepted-1</span> <span>activity-core</span> <span>reviewed 2026-08-03</span><span>generated from canonical source — do not edit</span></div><h1>Ops runs vs development work records — claim queue and plane split</h1><p class="sub">Source: <code>activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · 41a3fb8b81bd521a5fa21af114975c54532df3ad</code></p><p class="sub">Review due: 2027-02-03</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="#topology"><span class="n">·</span>Topology</a></li><li><a href="#implementation-stack"><span class="n">·</span>Implementation stack</a></li><li><a href="#references"><span class="n">·</span>References</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p><strong>Accepted</strong> (2026-08-03).</p>
</section>
<section id="context"><h2>Context</h2>
<p>The fleet has two healthy patterns that were forced into one vague “task” idea:</p>
<ol><li><strong>Development / coordination work</strong> — structured workplans and tasks as <strong>repo files</strong>, indexed by <strong>State Hub</strong> on <strong>coulombcore</strong> (<code>fix-consistency</code>, UUIDv7 write-back). This matches ADR-001 and <code>work-record-types_v0.1.md</code> and is how humans and coding agents ship product.</li></ol>
<ol><li><strong>Operations / scheduled automation</strong> — activity-core Temporal schedules (when / what / where), rein-aharness execution (how), completion events (idempotence). Instances of “todays FI brief” or “this prune fire” are <strong>ephemeral runs</strong>, not multi-day workplan bodies.</li></ol>
<p>Practice collapsed (2) into broken paths:</p>
<div class="scroll"><table><thead><tr><th>Path</th><th>Failure</th></tr></thead><tbody><tr><td>Emit <code>activity_task_spawn</code> progress only</td><td>Append-only; <strong>not claimable</strong>; schedule can “succeed” while nothing runs</td></tr><tr><td><code>ISSUE_SINK_TYPE=rest</code> → issue-core → Forgejo</td><td>Spam + 503s; issue-core INTENT forbids origin of fleet work</td></tr><tr><td>Host systemd timers dual-clocked with Temporal</td><td>Shadow scheduler; weak self-healing</td></tr></tbody></table></div>
<p>issue-cores <strong>correct</strong> role is a <strong>connector facade</strong> over external trackers (Forgejo, GitHub, Jira, …) so agents need not know each backend. It is <strong>not</strong> the origin of work records and <strong>not</strong> the default internal ops queue. <strong>Gitea is out of scope</strong> for this fleet; the self-hosted forge is <strong>Forgejo</strong>.</p>
<p>Canon already allows a DB-only exception for “runtime operations data (logs, metrics, run histories, token events)” (<code>work-record-types_v0.1.md</code>). This ADR names that exception for <strong>ops runs</strong>.</p>
</section>
<section id="decision"><h2>Decision</h2>
<h3>1. Two planes for “work,” one vocabulary for people</h3>
<div class="scroll"><table><thead><tr><th>Plane</th><th>Artefact</th><th>Home</th><th>Lifecycle</th></tr></thead><tbody><tr><td><strong>A — Development / coordination</strong></td><td>Work records (<code>workplan</code>, <code>task</code>, <code>intake</code>, …)</td><td>Repo files + State Hub (coulombcore)</td><td>File-backed, reviewable, multi-day</td></tr><tr><td><strong>B — Recurrence control</strong></td><td>Activity definitions + Temporal schedules</td><td>activity-core (railiance)</td><td>Git definitions; DB schedule state</td></tr><tr><td><strong>C — Ops execution</strong></td><td><strong><code>ops_run</code></strong> (runtime claim object)</td><td><strong>activity-core DB + claim API</strong></td><td>open → claimed → succeeded \</td><td>failed \</td><td>expired</td></tr><tr><td><strong>D — External collab</strong></td><td>Tracker issues</td><td><strong>issue-core</strong> optional projection to <strong>Forgejo</strong> (and others)</td><td>Never default for internal automation</td></tr></tbody></table></div>
<p>Coding agents orient on <strong>Plane A</strong> via State Hub. Automation orients on <strong>Planes B+C</strong>. Humans outside the fleet may see <strong>Plane D</strong> only when deliberately projected.</p>
<h3>2. activity-core owns the ops_run claim queue</h3>
<p>When a definition emits a TaskSpec for internal fleet work:</p>
<ol><li>Write <strong><code>ops_run</code></strong> row (durable, claimable) with idempotency key (e.g. <code>fi-daily:2026-08-04</code> for once-per-day briefs).</li><li>Optionally dual-write <code>activity_task_spawn</code> progress for existing observers.</li><li>Do <strong>not</strong> open a Forgejo issue.</li><li>Do <strong>not</strong> create a workplan task file for that days fire.</li></ol>
<p>Claim API (sketch; implement in ACTIVITY-WP-0026):</p>
<ul><li><code>POST /ops-runs/claim</code> — lease next open run matching labels / worker id</li><li><code>POST /ops-runs/{id}/complete</code> — succeeded + completion metadata</li><li><code>POST /ops-runs/{id}/fail</code> — failed + retry policy</li><li><code>GET /ops-runs?state=open</code> — operator visibility</li></ul>
<p>activity-core remains <strong>when / what / where</strong> only: it does <strong>not</strong> run domain LLM sessions or hold tenant git credentials.</p>
<h3>3. rein-aharness owns how (approach selection + execute)</h3>
<ul><li>Continuous <strong>claim loop</strong> (service / Deployment), not dual wall-clock timers as source of truth.</li><li>Host systemd timers become <strong>break-glass</strong> after cutover.</li><li><strong>Approach selection</strong> at claim time (minimal table v1):</li></ul>
<div class="scroll"><table><thead><tr><th>Labels / definition family</th><th>Approach</th></tr></thead><tbody><tr><td>mail / mail-intake</td><td>deterministic adapter (+ optional triage)</td></tr><tr><td>research-brief / fi-daily</td><td>structured llm-connect (<code>fi-research-brief</code>)</td></tr><tr><td>binky rhythm</td><td>structured llm-connect (<code>brief-daily</code>)</td></tr><tr><td>agent-session</td><td>persona + tool profile session</td></tr><tr><td>unknown</td><td>refuse; do not invent</td></tr></tbody></table></div>
<ul><li>Success posts <strong>domain completion event</strong> (e.g. <code>fi_daily_brief</code>) so resolvers set <code>due=false</code>, and closes the <code>ops_run</code>.</li></ul>
<h3>4. State Hub remains the work-record read model</h3>
<ul><li>Stays on <strong>coulombcore</strong>; railiance peers via edge relay / ops-bridge.</li><li>May <strong>project</strong> open/failed ops runs for fleet ops UI (read-only), but is <strong>not</strong> the claim authority.</li><li>Development workplans/tasks continue file + <code>fix-consistency</code> only.</li></ul>
<h3>5. issue-core is Forgejo (and multi-tracker) facade only</h3>
<ul><li><strong>Forgejo</strong> is the self-hosted forge; do not plan for <strong>Gitea</strong> as a product.</li><li>Optional <strong>project / link</strong> of an existing work-record UUID to an external issue when collaboration needs it.</li><li>Authenticated <code>POST /issues/</code> means “create/link <strong>external</strong> tracker work,” never “spawn fleet automation.”</li><li>Internal automation <strong>must not</strong> use issue-core as the ops claim queue unless a future design adds an <strong>internal-only</strong> backend with projection <strong>hard-off</strong> for automation labels — out of scope unless a later ADR says otherwise.</li></ul>
<h3>6. Promotion path ops → dev</h3>
<p>When an ops run discovers multi-day product work (e.g. “collect Kimi K3 under new quota”), the executor or human <strong>promotes</strong> an <code>intake</code> / workplan in the domain repo (Plane A). Ops runs never become permanent fake workplans.</p>
</section>
<section id="consequences"><h2>Consequences</h2>
<h3>Positive</h3>
<ul><li>Self-healing recurrence: schedule → claimable row → claim → complete/fail.</li><li>Aligns issue-core INTENT with practice; stops Forgejo spam path.</li><li>Keeps activity-core thin; keeps rein-aharness as sole session/credential shell.</li><li>Preserves the good dev loop (files + State Hub).</li></ul>
<h3>Negative / cost</h3>
<ul><li>New schema + API + migrator in activity-core.</li><li>rein-aharness must leave issue-core-only poll as primary for ops.</li><li>Temporary dual-write and dual timers until cutover proven.</li></ul>
<h3>Non-goals</h3>
<ul><li>Replacing workplans for development.</li><li>Running domain briefs inside activity-core workers.</li><li>Global <code>ISSUE_SINK_TYPE=rest</code> to Forgejo.</li><li>Gitea support or migration paths.</li></ul>
</section>
<section id="topology"><h2>Topology</h2>
<pre>coulombcore: State Hub + work-record registry
railiance: activity-core (Temporal + ops_run queue)
rein-aharness (claim loop + approach + llm-connect)
domain checkouts
forgejo: optional external collab via issue-core projection only</pre>
</section>
<section id="implementation-stack"><h2>Implementation stack</h2>
<div class="scroll"><table><thead><tr><th>Workplan</th><th>Owner</th><th>Role</th></tr></thead><tbody><tr><td>ACTIVITY-WP-0026</td><td>activity-core</td><td>ops_run schema, claim API, emit path, dual-write</td></tr><tr><td>REIN-A-0002</td><td>rein-aharness</td><td>claim loop, approach table, FI/Binky cutover</td></tr><tr><td>ISSUE-WP-0006</td><td>issue-core</td><td>Forgejo-only language; projection boundary; no ops queue</td></tr><tr><td>STATE-WP-0078</td><td>state-hub</td><td>Read projection of ops_run for ops UI (optional consume)</td></tr></tbody></table></div>
</section>
<section id="references"><h2>References</h2>
<ul><li><code>docs/recurring-automations-playbook.md</code></li><li><code>docs/task-emission-consumer-contract.md</code></li><li><code>docs/issue-core-emission-boundary.md</code></li><li>ACTIVITY-WP-0022, ACTIVITY-WP-0023 (G2)</li><li><code>the-custodian/canon/standards/work-record-types_v0.1.md</code></li><li><code>issue-core/INTENT.md</code> (work-record boundary)</li><li><code>state-hub/docs/cluster-operating-model.md</code> (coulombcore primary)</li></ul>
</section><footer><span>ACT-ADR-005 · accepted-1 · accepted</span><span>activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · 41a3fb8b81bd521a5fa21af114975c54532df3ad</span></footer></main></div></div></html>