Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
A private repository failed derivation the same way a broken one did, so
"cannot read" and "does not exist" were indistinguishable from outside.
They authorise opposite things: only the second can justify retiring a
record.
- ForgeUnreadableError (a ForgeDeriveError, so old callers still catch it)
for permission-shaped clone failures, including Forgejo's 404 for an
unauthenticated private repo — indistinguishable here, and the safe
reading of an ambiguous answer cannot destroy a record.
- GIT_TERMINAL_PROMPT=0: an unattended pass must fail, not block on a
username prompt. Failing is what makes the case observable.
- DerivedProjection.retirement_eligible separates "no records found" from
"no records exist". A checkout with no workplans/ directory cannot
evidence an absence — the empty-clone path that would have proposed
every record in a repository for retirement.
- Retirement from an ineligible source is refused even when acknowledged.
- Fleet keeps unreadable out of the error bucket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
Takes the three tasks split out of ops-mason/MASON-WP-0003: mount the
forge read credential, teach derive_from_forge() to use it, and re-run
the fleet reset over the nine repositories that failed in
STATE-WP-0083-T04.
T01 (report unreadable as unreadable, never as retirement work) is
promoted ahead of the credential work: it does not depend on it, it
closes the empty-clone path that nearly retired vergabe-teilnahme, and
MASON-WP-0003-T01's breadth recommendation is contingent on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
51 records retired across the fleet; 106 of 121 repositories now noop. Zero
repositories hold a record that no longer derives.
26 real workplans were rescued from wrongful retirement — more than half the
number retired. vergabe-teilnahme (17) predated the convention entirely; nine
further files across five repositories carried no type field, three of them
created by this session's own renames.
Also records the RPF-WP collision this session introduced by numbering from
0001 without checking whether the target prefix was in use.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Second pass: 93 repositories noop with zero created, updated or retired,
demonstrating ADR-012 decision 7's requirement that reset twice produces the
same projection.
Slug refusal took errors from 12 to 9 — three repositories were erroring on slug
collisions, not just disaster-control. 60 records across 13 repositories no
longer derive from any file, which is the retirement work CUST-WP-0068-T09
waits on, now enumerated per repository.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
The identifier refusal checked id only. slug carries its own unique constraint
across the whole table, so two repositories can derive different identifiers
whose slugs still collide — which left disaster-control raising IntegrityError.
First fleet-wide pass over 121 repositories: 91 applied (737 updated, 8
created), 16 refused covering 64 records, 12 errored. 745 workplans now carry
the commit they derived from, satisfying ADR-012 decision 2 for the first time.
64 is the measured size of the stale-row problem CUST-WP-0068-T09 has waited on.
Eleven of the twelve errors are private repositories the pod cannot clone
anonymously — a real limit on "the forge is the projection source", since their
absence currently looks like an error rather than a policy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
ADR-012 decision 7 requires the fleet form to share the per-repository
implementation: the rarely-run wide operation must be the frequently-run narrow
one, or the wide one is trusted on the strength of never having been exercised.
Failure behaviour is the substance. A refusal does not stop the pass — aborting
on the first refusal means one unresolved repository blocks reconstruction
everywhere, which in practice means permanently. An error does not stop it
either. Each repository gets its own session so one failure cannot roll back
another's work, and only repositories that applied are committed.
Refs STATE-WP-0083-T04
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Eleven files declared types canon does not define while the hub held each as a
workplan. Eight had their type corrected; three lacking identifiers were given
one continuing their repository sequence, preserving hub identifiers.
The diff now proposes no retirements anywhere tested. Nothing in the reset's
logic changed: the retirements disappeared because the sources were repaired.
The reset had been reporting the truth about a fleet whose files could not be
read.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Returns refused naming ADHOC-2026-08-23 and its holder, instead of
IntegrityError. Records that a first check against the cache returned refused
via the retirement path and was nearly reported as proof — the output looked
like success while testing nothing of the sort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
False retirements are gone: kontextual-engine 1 to 0, the-custodian 4 to 2.
The four records reported as hub-first with no backing file all had files, by
three different mechanisms — two with unterminated frontmatter, two declaring
type: feature. Each time 'the tool did not find it' was reported as 'it does not
exist'. Ten files in workplans/ declare a non-workplan type and need
disposition.
The reset crashes on net-kingdom with an IntegrityError where it should refuse:
ADHOC-2026-08-23 derives to an identifier another repository holds, which is the
collision CUST-WP-0066 documented. A refusal is actionable; a constraint
violation is a stack trace.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Two shapes of one missing-newline defect: 16 with the closing delimiter fused
onto the last value, 2 with a value fused onto the following key. Only the
newline was inserted.
Forces a correction: state-hub-v0.1 and v0.2 were reported as hub-first records
with no backing file. They are CUST-WP-0000 and CUST-WP-0000b, which existed all
along and were unreadable — the claim came from a parser's silence rather than a
check against the directory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Found by reviewing the reset diff before applying: it proposed retiring
kont-wp-0015, a live correctly-registered workplan. Cause is a closing --- fused
onto the last frontmatter value, so the frontmatter never terminates and the
whole body is swallowed. Any parser selecting on type: workplan sees nothing, so
the file is invisible rather than invalid.
18 files across citation-evidence, infospace-bench, kontextual-engine,
net-kingdom and others. Running the reset before repairing them would retire
live work: the derivation omits the workplan, so the reset reads its correct hub
record as no longer deriving.
Blocks the first production reset.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Central: 5248 of 5974 tasks identified, 5213 distinct, 35 duplicates. The
pre-apply check was uniqueness rather than volume — all 136 pending updates were
verified to take identities held by nothing else, which is the lesson from the
first attempt where a clean dry run concealed 51 non-unique ids.
The 35 remaining duplicates predate this work: 18 from a workplan registered
twice, 12 from archived RAILIANCE-WP files left unrenamed by the active-only
migration decision, 4 from unqualified ad-hoc ids.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
5248 rows identified but only 5100 identities distinct: bare T01 task ids stored
as canonical gave every workplan's first task one identity, 51 ids across 148
rows. The backfill's conflict check could not see it — it detects one UUID
claimed by two ids, and this was the inverse — so it surfaced only because the
two counts did not reconcile.
Central now holds 5112 of 5974 identified, 5077 distinct. The remaining 34
duplicates are unqualified ad-hoc ids reused across repositories, the problem
CUST-WP-0066 closed for new records only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Every work-record type carried a stable identifier except tasks, whose rows held
only id, workplan_id, title, status and priority — nothing connecting a row to
CUST-WP-0067-T01 in the file it came from. Matching was therefore by title, so a
renamed heading looked like one task vanishing and another appearing, and the
forge-derived reset had to refuse to touch tasks at all.
Adds tasks.record_id (nullable: no migration can invent an identity for an
existing row) and a backfill that reads the pairing from the repository files,
where a task declares both its canonical id and its projection UUID. 5516 pairs
across 121 repositories with zero conflicts; 4456 of 6073 cache task rows
identified.
Diff and reset now key on record_id where present, falling back to a
title-prefixed key so an unidentified row stays visibly unidentified.
Unknown stays unknown: a row the files do not claim keeps no identity and the
reset keeps refusing to act on it, and an existing identity is never
overwritten — a mismatch is recorded as a conflict rather than resolved.
Refs STATE-WP-0083-T06
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Implements ADR-012 decision 7 as amended (STATE-WP-0083-T03). Creates what the
forge has and the hub lacks, updates what differs, retires what no longer
derives. It never deletes: hub-native records reference workplans with ON DELETE
RESTRICT, and destroying a progress event to tidy a derived projection would
lose hub-native truth to fix a derived-state problem.
Retirement is refused by default. A record that stops deriving may mean a
deliberately deleted file or a caller pointed at the wrong branch; only the
caller can say which.
Verified against live data and rolled back: whitehat-security applied 5 updates
with no retirements; the-custodian refused, naming the four hub-first records
confirmed by hand to have no backing file.
Tasks of existing workplans are deliberately untouched — hub tasks carry no
canonical identifier, so matching is by title and a renamed heading would
destroy and recreate a record. Tasks are created only alongside a new workplan,
where nothing exists to mis-match. Tracked as T06.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
First execution failed in a way that proved the design: the pre-upgrade hook
applied both migrations and deleted itself on success, then the k3s tunnel
dropped before the API could roll. Schema was briefly ahead of code — the safe
direction, and why additive migrations were the right shape.
Records two gaps it exposed: a successful migration leaves no trace because of
the hook delete policy, and bridge status reported a dropped tunnel as
connected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Adds projection_retired_at, projection_retired_reason and derived_from_commit.
Retirement cannot be expressed by deletion — hub-native records reference
workplans with RESTRICT — nor by `status`, since an archived workplan was closed
by its owner while a retired one is simply no longer derived by the forge. Those
are different facts and must not share a field.
Discovered while applying this: central's schema is two revisions behind the
code it runs. review_contracts does not exist there although its migration ships
in the serving image, and there is no migration mechanism at all — bare uvicorn
CMD, no chart-declared job. Recorded as STATE-WP-0083-T07, which now blocks T03.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Implements ADR-012 decisions 1 and 2 (STATE-WP-0083 T01, T02 partial). Central
clones the default branch from Forgejo and derives its own projection: 69
workplans and 459 tasks from the-custodian at d5013ae, identical across runs,
with the commit recorded as provenance.
Identifiers are derived in the ADR-007 namespace and verified against live
records, so a forge-derived projection and a preliminary overlay agree on
identity without reconciliation.
The diff first matched hub records by UUID and was badly wrong: most hub records
carry pre-ADR-007 random identifiers, so nearly everything appeared
simultaneously missing and stale, and a reset built on it would have destroyed
and recreated the entire projection. It now matches canonical record id, falling
back to the backing file. whitehat-security — bootstrapped straight from files —
now reports clean, which is the control.
Task-level comparison is deliberately not trusted: hub tasks carry no canonical
record id, only a title, so matching is by title. Recorded as T06; T03 is
limited to workplans until it lands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Implements ADR-012 decision 7. Placed in state-hub because central must do its
own reading: the pod can clone Forgejo anonymously, and driving this from Repo
Manager would push a workstation-computed projection into central, which
ADR-010 decision 5 forbids.
Also the only sanctioned removal path — the hub has no hard-delete for work
records by design.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
These workplans exist only in the retired local hub. Their random pre-ADR-007
identifiers are refused by C-06 as stale references, so they cannot be
registered. Deriving from the canonical record id takes no identity from
anything: central does not hold them and the old ids die with the cache.
Records central already holds were deliberately left untouched.
Refs CUST-WP-0068-T06
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
The file carried an identifier minted by the retired local hub while central
held a different record for the same file — ADR-010's same-filename,
different-UUID duplicate-registration class.
Central is authoritative (ADR-010 decision 1) and decision 6 says to mitigate
by changing the repository file rather than editing central, so the file adopts
central's id. The cache-side id dies with the cache.
Refs CUST-WP-0068
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Central now reads finished, matching the file. Its task projection is still the
pre-retarget draft with four open tasks, because central never saw the
2026-08-16 retarget and none of this file's task identifiers exist there.
Left deliberately: re-registering would add eight records alongside eight stale
ones. Duplicate task identity resolution belongs to CUST-WP-0068.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
The file carried 03f38314, minted by the retired local hub. Central holds a
different record for the same file (bbfce36a) — ADR-010's "same filename,
different UUID" duplicate-registration class.
Central is authoritative (ADR-010 decision 1), and decision 6 says to mitigate
by changing the repository file rather than editing central, so the file adopts
central's id. The cache-side id dies with the cache.
Refs CUST-WP-0068
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
The registrar is broken by workstation coupling rather than beside it. Inside
the pod /home/tegwick mounts read-only despite the chart setting no readOnly
and securityContext being empty, so the sweep dies before it can mint. That
is why 12 sync requests queued and the newest custodian-sync commits are July.
Eight tasks: restore the write path, give the pod its own clone volume and
service identity, run non-root, correct 72 repo records still pointing at
/home/worsch with gitea remote_urls, serve the dashboard from the cluster,
write the boundary rule down, and close out the registrar.
End state: workstation coding agents push to forgejo, cluster infrastructure
reads from forgejo, neither reads the other's disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The registrar is reachable and always was: state-hub-primary binds [::1]:8000
while the workstation API listens on 127.0.0.1:8000, so an IPv4 probe hits
the local hub and reads as a dead tunnel.
But the two databases have diverged by roughly a third -- 987 vs 649
workplans, 5710 vs 4037 tasks -- and the registrar runs pre-rename code. Nine
registrar sync requests from three agents have queued since 2026-08-20.
Re-checked the chain: RMGR-WP-0004-T08 is done, so RMGR-WP-0005-T03's stated
blocker has closed. Its substantive blocker has not -- ADR-007 Migration
option 2 permits historical identifier collisions, and UUIDv5 derivation from
a non-unique identifier manufactures collisions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First live cutover slice. Installed the shipped pilot config (writeback and
reconcile on, writeback_push off, scoped to repo-manager), reloaded the API
via its own --reload watcher rather than restarting the operator's process,
and confirmed reconcile routed through rmgr with a new mutation meter entry
(8 workplans, 53 tasks, 61 records).
Divergence check clean: the one-workplan delta against the hub is
RMGR-WP-0008, unregistered because this instance is not the identifier
registrar, not a dual-run divergence.
Slice sits at dual, not owner -- the hub stays authoritative until a full
sweep cycle shows zero divergence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Applied the A3 readiness check one level down. The State Hub adapter exposes
exactly three operations: rm_update_task_status, rm_reconcile, rm_scaffold.
No adapter for workplan CRUD, intake, decision, dependency or human-flag
records, and no repo-manager parser for them either.
A2 splits into A2a (task status + reconcile, ~6 items, receivable) and
A2b-A2e (~32 items, not receivable). A2a is the only slice in the plan
executable today -- roughly 6 of 425 inventory items.
Also records that dual-run is currently off entirely: no config file,
writeback and reconcile false, no pilot repos. The 23 metered mutations are
RMGR-WP-0002/0003 pilot residue. A2a's next step is operational, not code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The T02 slice plan marked all 167 repo-manager items receivable because
RMGR-WP-0001/0002/0003 are finished. That reads readiness off workplan
status, which is too coarse.
Verified against repo-manager/src/repo_manager/: those workplans delivered
work-record dual-run, repository observation, and scaffolding. parse/ holds
only workplan.py, and the source tree contains no reference to sbom,
repo_goal, contribution, technical_debt, or extension_point.
Only A1 (partial) and A2 are receivable -- 86 items, not 165. A3, A4 and A5
have no owner-side surface. A3 was recommended twice as the low-blast-radius
first cut; it is in fact the least ready slice in Wave A. Recommendation
withdrawn.
Critical path moves to repo-manager: a workplan there must build the
register, UI, and topic surfaces. Building them here is inadmissible under
policies/retirement-freeze.md. First executable slice is A2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
History is archived fleet-side, so the read surface has no remaining job.
- api/routers/suggestions.py: whole prefix 410s with a pointer to intake and
to the archive; the router drops from 176 lines to a stub
- mcp_server: the 6 suggestion tools removed outright rather than stubbed --
a retired tool that still appears in the tool list costs every agent
session context on every call, which is the opposite of retiring it
- write_idempotency: 5 /suggestions rules dropped
- dashboard: suggestions.md deleted, nav entry removed, reference.md and
wsjf-triage.md updated; docs/suggestions.md rewritten as archive pointer
- tests: two tests pinned the old read-live behaviour and now pin the
retirement contract instead
Tables stay: they are retire/archive in SHR-INV-0001 and are captured by the
final dump at T06.
Untouched, and worth knowing during cutover: ui-feedback.md / todo.md
'suggestions' are Shift+click dashboard feedback backed by technical_debt, a
different entity that shares the word. E3 (dashboard-meta) is that page; its
owner is state-hub-until-cutover so it retires at the T06 window, not now.
Full suite 612 passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Slice E1 was already half-retired: mutations 410 since CUST-WP-0061-T06,
reads held open only to keep the history reachable. That history is now
archived fleet-side, so the reads can go.
Records a dependency the inventory's 17-item count misses: the suggestions
dashboard page is live and referenced from index.md and wsjf-triage.md, so
E1's removal must sequence with the UI slices A4/B5 rather than ahead of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Retires the 15 workstream->workplan aliases that meet the retirement
standard: verified replacement, no manual hold, zero calls in a 7-day
window, and silence past the volume-scaled quiet threshold. Meter now reads
16 retired / 4 legacy with 0 outstanding candidates.
The 4 held are the three six-figure read paths and GET /tasks/?workstream_id
which still has traffic -- the set where a premature retirement would do the
most damage. They clear as their quiet counters run.
Journalled fleet-side in the-custodian/docs/retired-legacy-interfaces.md so
the record outlives this repo's archive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
States the four admissible reasons for a change to state-hub during
retirement -- preserve compatibility, enable extraction, reduce scope, fix
operational risk -- and the inadmissible case: establishing new permanent
ownership in the component being retired.
The operative test is 'where does this live after cutover?', not 'is this a
good change?' Most inadmissible changes are good changes; STATE-WP-0080's
retarget to RMGR-WP-0004 is the worked example.
Enforced by review rather than new automation: a freeze-checker built inside
State Hub would itself be new permanent automation here, so the policy would
violate itself. New tables/routes/MCP tools are the one hard stop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>