feat(projection): derive a repository's projection from the forge
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 23s

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
This commit is contained in:
tegwick 2026-08-25 23:34:25 +02:00
parent 6390b7bead
commit fd0d0d537b
3 changed files with 550 additions and 2 deletions

View file

@ -54,7 +54,7 @@ and should be extracted rather than rewritten.
```task
id: STATE-WP-0083-T01
status: todo
status: done
priority: high
```
@ -70,11 +70,22 @@ worthless precisely because nothing ever wrote it correctly.
Acceptance: deriving `the-custodian` twice from the same commit yields identical
output, and the commit is reported.
**Done (2026-08-25).** `api/services/forge_projection.py`. Central clones the
default branch from Forgejo and derives 69 workplans and 459 tasks from
`the-custodian` at commit `d5013ae`, identically across runs. Identifiers are
derived from the canonical record id in the `ADR-007` namespace, verified against
live records — so a forge-derived projection and a preliminary overlay agree on
identity without reconciliation. Eight tests, including that identifiers are
derived rather than inherited from whatever a file happens to carry.
A fresh shallow clone each time is deliberate: reusing a working copy is how a
projection ends up reflecting someone's local state instead of the forge.
## Report the difference before changing anything
```task
id: STATE-WP-0083-T02
status: todo
status: progress
priority: high
```
@ -90,6 +101,40 @@ clear — before anyone commits to clearing them.
Acceptance: a dry-run diff for a repository with known drift matches what
manual inspection shows.
**Workplan-level diff done; task-level blocked (2026-08-25).**
The first implementation matched hub records to derived ones *by UUID* and was
badly wrong: most hub records still carry pre-`ADR-007` random identifiers, so
nearly every record appeared simultaneously missing and stale. A reset built on
that comparison would have destroyed and recreated the whole projection. It now
matches on canonical record id, falling back to the backing file.
Workplan-level results are trustworthy and inspectable:
| Repository | missing | stale | differing |
|---|---|---|---|
| `whitehat-security` | 0 | 0 | 0 |
| `the-custodian` | 3 | 4 | 52 |
| `railiance-platform` | 18 | 25 | 2 |
`whitehat-security` reporting clean is the control: it was bootstrapped directly
from its files, so a forge derivation must agree with it exactly. The four stale
workplans on `the-custodian` were checked by hand and are real —
`CUST-WP-0023`/`0024` have no file in the repository at all, and
`state-hub-v0.1`/`v0.2` carry slugs that were never canonical ids. All four are
genuine hub-first records, the class `ADR-010` says to disposition.
**Blocked: hub tasks carry no canonical record id.** The task schema is
`id, workplan_id, title, status, priority, …` with nothing holding
`CUST-WP-0067-T01`. A file task and a hub task can therefore only be matched by
title, which is why the task diff reports 149 missing and 131 stale for
`the-custodian` where the workplan diff reports 3 and 4. That is the matching
failing, not drift.
Task-level reset must not be built on title matching — renaming a task heading
would silently destroy and recreate its record. `T03` is limited to workplans
until tasks carry their canonical id, which is `T06`.
## Apply the reset transactionally
```task
@ -144,3 +189,28 @@ replacement belongs with the provenance work in `T01`.
Acceptance: `CUST-WP-0068-T09` closes; no fingerprint input depends on a local
filesystem.
## Give hub tasks their canonical record id
```task
id: STATE-WP-0083-T06
status: todo
priority: high
```
Hub task rows hold no canonical identifier — only a title — so nothing reliably
connects `CUST-WP-0067-T01` in a file to its row. Every other record type has a
stable identity; tasks do not, and that gap is what stops the reset from covering
them.
Add the canonical id to the task record and populate it during derivation and
registration. Once present, task matching becomes identity-based like workplans,
and `T03` can extend to tasks safely.
Until then a task's identity is its title, which changes whenever someone edits a
heading. That is not a foundation for deletion.
Acceptance: task rows carry their canonical record id; the task diff for
`whitehat-security` reports clean; `the-custodian`'s task diff falls to something
manual inspection confirms.