Five rows came back tombstoned but not retired — a zombie beside the correct
record derived from the same file. Two of my own changes combined to produce it.
Releasing the identifier destroys the evidence that made the row a re-key: the
tombstoned slug is no longer a work-record identifier, and a legacy row is not
UUID-derived, so both guards fall through to path matching — which matches the
very file the row was retired for and resurrects it.
And the release loop ran before matching, so it also stamped rows that were
about to derive again, leaving them unable to match their own file and never
un-retired.
Two changes. A retired row is now matched by UUID or by its own identifier, and
never by the path heuristics; a record that genuinely returns still un-retires,
because its identifier is unchanged. And the release exempts any row the forge
still derives.
746 pass.
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
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
Retiring llm-connect's five legacy rows failed on `duplicate key value violates
unique constraint "tasks_pkey"`, and the cause is that a bare `T01` is not an
identifier: it is unique within its workplan, not in the fleet. Unqualified,
uuid5("T01") is the same UUID for every workplan in the fleet that has one —
llm-connect's 91 task blocks derive 49 distinct UUIDs, so creating its
workplans inserts the same task primary key repeatedly in one flush.
Tasks are now qualified with their owning workplan before derivation, which is
the rule `task_record_id_backfill.qualify_task_id` already applies to stored
ids; the two must agree or the backfill and the projection disagree about what
a task is called. Already-qualified ids are untouched.
The create path's comment claimed it was "safe only because nothing exists to
mis-match against: this workplan is new to the hub". That was true of other
workplans and false within one: the collision was among the tasks it was
inserting itself.
The failed pass rolled back cleanly — llm-connect's five legacy rows are still
live and progress events are intact.
742 pass.
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
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 rows were being proposed for retirement that were correct all along.
`testdrive-jsui-publication` and `three-phoenix-ha-cluster` carry legacy title
slugs, but their UUIDs are exactly uuid5 of MARKITECT-WP-0002 and
RCLUSTER-WP-0007 — they are those records. `_identity_is_derived` short-circuits
on the UUID *version* before the slug rule ever runs, so both read as re-keys.
Acknowledging retirements for either repository would have retired a live
record on the strength of its slug.
The derived UUID is the strongest key available and was not used in matching at
all. It is now checked first: if a row's id equals uuid5 of a wanted
identifier, that row is that record, whatever its slug says. The slug and path
heuristics now only decide rows the UUID cannot identify.
This also corrects a claim I made from the retirement list alone: those files
derive normally. Nothing was wrong with them.
738 pass.
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
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 previous commit made retirement stamp the slug, but `stale` excludes rows
already retired — so nothing revisits them and the stamp never reaches the 32
rows retired earlier today: core-hub's two REPO-WP rows and the 30 ad-hoc
records. Each still holds an identifier no file derives, blocking whichever
repository legitimately owns it.
The reset now stamps any retired row whose identifier is still unreleased,
using that row's own retirement timestamp rather than today's, so the record
keeps saying when it was retired. Completing a decision already taken is not a
new retirement, so this runs without acknowledgement and is reported as
`released`, separately from `retired`.
Repairing it in the reset rather than by hand keeps the hub a read model and
makes the fix reproducible for any row that reaches this state again.
735 pass.
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
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
`slug` is unique across the whole table, so retiring a row by timestamp alone
leaves its identifier locked to a record nothing derives any more — and the
repository that legitimately owns it can never claim it. core-hub's inherited
REPO-WP-0001/0002 rows retired cleanly and repo-seed stayed refused with
"slug already belongs to another repository", pointing at two retired rows.
Retirement now stamps the slug `<slug>@retired-YYYYMMDD`. The row, its UUID and
its progress events are untouched, so history stays attached to the record it
happened under; only the human-facing identifier is freed. Re-retiring an
already-stamped row replaces the mark rather than stacking it, or the 100-char
column overflows after a few passes.
The UUIDs never collided here: core-hub's rows predate derived identity and
carry v4 UUIDs, while repo-seed derives v5 ones. The identifier was the only
thing in the way.
733 pass.
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
Updated by fix-consistency on 2026-08-28:
- update .custodian-brief.md for state-hub
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
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 previous commit tested UUID version as a proxy for "identity is derived".
It covers 19 of the 44 ad-hoc rows; the other 25 are legacy v4, so they kept
path matching and would have been updated in place — slug still
`adhoc-2026-07-02` while the file says `ADAPTIVE-WP-ADHOC-2026-07-02`. The
divergence would have persisted, and silently, for most of the records this
was meant to fix.
The distinction that actually matters is whether the row's slug is a
work-record identifier. A row whose slug is an identifier claims to be that
record, so a different derived identifier for the same file means two records.
A row whose slug is a title — `three-phoenix-ha-cluster` for RCLUSTER-WP-0007 —
never claimed one: those are hub-first rows from before ADR-001 where the
backing path is the only link there has ever been, and path matching is all
that holds them together.
730 pass.
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
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 ad-hoc requalification changed 30 identifiers while deliberately keeping
the filenames, per canon. The matcher's path fallback therefore matched every
old row to its new record and updated it in place — so the files said
CUST-WP-ADHOC-2026-08-25 while the hub still said adhoc-2026-08-25, with the
row's UUID still encoding the old identifier. Exactly the file/hub identity
divergence ADR-007 exists to prevent.
Path matching cannot distinguish a re-key from a rename when the path does not
change. Identity can: a derived row is UUIDv5 over the record id, so a changed
id is a different record — the old row retires and the new one is created.
Rows predating derived identity carry v4 UUIDs, where the identifier is a label
rather than the identity; those keep path matching, so rename detection still
works where it is the right answer.
The unmatched old row keeps its own slug as key, so it lands in `stale` and
becomes a retirement candidate rather than disappearing from the outcome.
720 pass.
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
`ADHOC-YYYY-MM-DD` is unique per date but not per repository, so any two repos
opening an ad-hoc on the same day collide. The 2026-08-26 fleet projection
reset refused 9 records for exactly this reason.
Canon (work-record-types_v0.1, CUST-WP-0066) settled the form as
`{PREFIX}-WP-ADHOC-YYYY-MM-DD`, filename unchanged, and grandfathered existing
ids on the condition they are never *silently* re-derived. This is the explicit
migration that clause allows for.
The hub id is derived from the record id, so a changed id is a different
record: stale state_hub_*_id fields are dropped and fix-consistency re-derives.
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 upgrade failed as `pre-upgrade hooks failed: timed out waiting for the
condition`. The migrate hook was in ImagePullBackOff: tag main-11f689d does not
exist. `.forgejo/workflows/image.yaml` has a `paths:` filter that excludes
deploy/**, so the chart-only commit was green in CI and built nothing — the two
green runs on it were host-smoke and container-smoke, not build-and-push.
470ece8 carries all the code; every commit after it touches only the chart and
generated docs.
scripts/verify_image_pin.py refuses a pin whose commit has no successful
build-and-push run, and says so in those terms rather than as a timeout five
minutes later. Verified against both the bad pin and the good one; treats an
unreachable forge as inconclusive rather than as failure.
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
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
MASON-WP-0003-T02 delivered the token on 2026-08-27, and three of its details
differ from the chart's placeholders: KV v2 puts `data/` in the read path, the
field is `FORGE_READ_TOKEN` rather than `token`, and the address matches every
existing ClusterSecretStore (`http://openbao.openbao.svc:8200`, mount
`platform`, auth mount `kubernetes`).
The projected token's audience is now optional and defaults to empty. A token
carrying an audience the auth role does not bind is rejected at TokenReview, and
the role's audience binding is not readable without a privileged session — the
four existing external-secrets roles use the API server audience, so that is the
default that can be verified to work. Setting `openbao` here narrows the token
to OpenBao alone and is worth doing once the role binds it.
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
Updated by fix-consistency on 2026-08-27:
- update .custodian-brief.md for state-hub
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
Nine repositories are invisible to derivation because central may not read
them. This adds the consuming half of the credential lane MASON-WP-0003 built.
The cluster has no agent injector and no secrets-store CSI driver, so the pod
authenticates to OpenBao with a projected ServiceAccount token (audience
`openbao`, not the API server) and reads the KV path itself. `forgeRead.*`
carries coordinates only; no credential is a chart value, an image layer, or a
Kubernetes Secret.
The credential reaches git through GIT_CONFIG_* setting http.extraHeader, not
through `-c` and not through userinfo in the clone URL — both of those put the
token in the process listing. It is redacted from ForgeDeriveError, which is
logged, stored in reset outcomes, and returned over the API.
Absent stays a supported state: with no credential, or with OpenBao
unreachable, resolution returns None and public derivation runs unchanged.
Raising would turn "nine repositories are unreadable" into "the pass failed",
which is what T01 exists to prevent.
Chart default is disabled. 717 pass.
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
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
Updated by fix-consistency on 2026-08-26:
- update .custodian-brief.md for state-hub
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
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
Updated by fix-consistency on 2026-08-26:
- update .custodian-brief.md for state-hub
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
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
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
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
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
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
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
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
Refs STATE-WP-0083-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
The reset raised IntegrityError on net-kingdom: its ADHOC-2026-08-23 derives to
an identifier another repository already holds — the collision CUST-WP-0066
documents, where two repositories created the same daily identifier on the same
day. Derivation is deterministic, so the clash is real rather than incidental.
It now checks, before creating anything, whether a derived identifier belongs to
another repository, and refuses naming both the record and the holder. A refusal
is something the caller can rule on; a constraint violation is a stack trace.
Acknowledging retirements deliberately does not authorise a collision. Those are
different decisions — one says the work is gone, the other says take an
identifier another repository owns — and conflating them would let a routine
acknowledgement smuggle an identity change through.
Refs STATE-WP-0083-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
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
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
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
A task id written as a bare "T01" is unique only inside its own workplan.
Stored as a canonical identifier it makes every workplan's first task share one
identity: 51 such ids were assigned to 148 rows on central before this was
caught, found because identified rows outnumbered distinct identities.
Short ids are now qualified as WORKPLAN-ID-T01. A short id in a file with no
workplan id in frontmatter is left unidentified — an identity that is not unique
is worse than none, which is the same rule the rest of this module already
follows.
The 136 affected rows on central have been cleared so the corrected backfill can
reassign them; the backfill never overwrites an existing identity, so they had to
be nulled rather than re-derived over.
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
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
The first implementation took local filesystem paths. Central has no workstation
checkout and must not depend on one: ADR-012 decision 1 makes the forge the
projection source, and a backfill reading someone's laptop would reintroduce the
exact coupling that ADR removes.
Surfaced concretely — central's postgres is not reachable from the workstation
(only the API tunnel, which is HTTP), so the local-path variant cannot reach the
database it needs to update, while the pod can clone the forge and already holds
the connection.
A repository that cannot be cloned contributes nothing rather than reducing what
the rest can identify.
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
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
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