STATE-WP-0094: dependency-edge receiving rollout package and owner note
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 31s

Prepared, not executed: image, migration b3c4d5e6f7a8, Helm --reuse-values
--atomic, pre/post checks, normal Forge receive proof plus repeat noop, and
rollback without schema downgrade. Requires founder per-change authorization.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 692292@bnt-lap001
Assistant-Session: 89c7d8e8-e1b3-47eb-9411-a0ab227fca2f
This commit is contained in:
tegwick 2026-10-04 13:20:28 +02:00
parent c287ed8259
commit fad9b3f7dc
2 changed files with 182 additions and 0 deletions

View file

@ -0,0 +1,169 @@
# Rollout package: dependency-edge receiving (STATE-WP-0094-T05)
> **REQUIRES FOUNDER PER-CHANGE AUTHORIZATION.** This package is prepared, not
> executed. Nothing below has been run against railiance01. No image was
> promoted, no Helm command, migration, schedule or production write was
> performed while preparing it. The 2026-10-04 "Do the rollout" instruction
> covered the qualifier receiving change (revision 71) only and does not
> authorize this change.
Prepared 2026-10-04 from `main` at `c287ed8`. Shape mirrors the previous
authorized rollout (`docs/evidence/2026-10-04-receiving-rollout.json`,
`the-custodian/docs/changes/state-hub-qualifier-rollout-2026-10-04.json`).
## What ships
| Commit | Change | Runtime effect |
| --- | --- | --- |
| `fceb5f2` | Forge receiving derives `depends_on` edges and reconciles source-owned `workplan_dependencies` rows | New `created_edges`/`removed_edges`/`unresolved_edges`/`external_edges` in receipt outcomes; receiving writes dependency rows |
| `3b9a5c5` | `GET /messages/` accepts `offset` (stable `created_at,id` order); C-25 pages replies | Additive query parameter |
| `c287ed8` | Dry-run edge preview in `diff_against_hub`; classifier no longer collapses duplicate edge keys | Pure preview; reconcile uses the shared planner |
| `57f2751` | Historical projection / owner-return planner source (already in `5dd3df9`) | Ships only if the live image predates `5dd3df9` |
Migration: `b3c4d5e6f7a8` (revises `a0b1c2d3e4f5`, the schema recorded as live
in revision 71). Additive only: two nullable columns
`workplan_dependencies.source_owner` (varchar 160) and `source_commit`
(varchar 64), plus index `ix_workplan_dependencies_source_owner`. Existing rows
stay NULL, i.e. externally authored, so receiving never removes them.
Local verification (2026-10-04, scratch database on the local PostgreSQL):
`a0b1c2d3e4f5 -> b3c4d5e6f7a8 -> a0b1c2d3e4f5 -> b3c4d5e6f7a8` passed with a
pre-existing dependency row preserved through every step (owner stayed NULL).
`alembic check` reports long-standing model/migration drift on unrelated tables
only; nothing on `workplan_dependencies`. Full test suite: 1018 passed.
## 0. Preconditions (read-only)
1. Founder authorization for this exact change and target commit, recorded as a
change proposal in `the-custodian/docs/changes/` (copy the shape of
`state-hub-qualifier-rollout-2026-10-04.json`; `outcome: pending`).
2. Identify the live revision and image. The historical-receiving proposal
(`main-5dd3df9`) has no recorded outcome, so do not assume revision 71:
```bash
helm -n state-hub history state-hub --max 5
kubectl -n state-hub get deploy -o jsonpath='{range .items[*]}{.metadata.name}={.spec.template.spec.containers[0].image}{"\n"}{end}'
kubectl -n state-hub exec deploy/state-hub -- /app/.venv/bin/python -m alembic current
```
Record `PREV_REV`, `PREV_IMAGE` and the live schema. Expected schema:
`a0b1c2d3e4f5`. Stop if it is anything else.
3. Fresh CPU headroom for the surge (API 250m, MCP 10m, migrate job 50m) as in
the previous rollout; stop on unrelated pending pods.
4. The proof repository is `state-hub` itself. Read its current hub edges so
the post-check can tell receiving's rows from earlier ones:
```bash
curl -s "http://10.43.68.154:8000/workplans/?repo=state-hub" > /tmp/edge-pre-workplans.json
```
and, inside the cluster DB (read-only):
`select count(*), source_owner from workplan_dependencies group by 2;`
(column does not exist yet; before migration expect an error, after it all
NULL).
## 1. Image
1. Push `main` (operator; agents in this session did not push). The Forgejo
`image.yaml` workflow builds on pushes touching `api/**`, `migrations/**`,
`scripts/**` etc. and tags `main-<short sha of the pushed head>`.
2. Confirm the build-and-push run succeeded and that its source commit contains
`c287ed8`:
`git merge-base --is-ancestor c287ed8 <image source sha>`.
3. Record `TAG=main-<sha7>` and the image digest. Do not use a mutable tag.
4. Verify the receiving source digest in the image equals the tested source:
`sha256sum api/services/forge_projection.py` at the image source commit vs.
`kubectl exec ... sha256sum /app/api/services/forge_projection.py` after
rollout.
## 2. Server-side dry run
```bash
helm pull / copy chart: deploy/railiance/apps/charts/state-hub -> /tmp/cust-edge-rollout/chart
helm -n state-hub upgrade state-hub /tmp/cust-edge-rollout/chart \
--reuse-values --set image.tag=$TAG --dry-run=server
```
Expected diff vs. live: image tag, version labels and the consequent config
checksum only. Any other change stops the rollout.
## 3. Upgrade (the authorized action)
```bash
helm -n state-hub upgrade state-hub /tmp/cust-edge-rollout/chart \
--reuse-values --set image.tag=$TAG --atomic --timeout 3m
```
The chart's `pre-upgrade` hook job (`migrate-job.yaml`) runs
`alembic upgrade head`, applying `b3c4d5e6f7a8`. `--atomic` restores the prior
release if the hook or rollout fails.
## 4. Post-checks
- `helm -n state-hub status state-hub` = deployed, new revision `NEW_REV`.
- API and MCP `1/1` Ready; container image IDs match the recorded digest.
- `alembic current` = `b3c4d5e6f7a8`.
- `\d workplan_dependencies` shows `source_owner`, `source_commit`; index
present; every pre-existing row has `source_owner IS NULL`.
- `GET /state/health` OK; `GET /messages/?to_agent=state-hub&limit=1&offset=0`
answers 200.
- Receiving source digest in the container matches the tested file.
## 5. Post-deploy proof (normal immutable Forge receive)
Use the normal receiving endpoint only. No task PATCH, no direct consistency
repair, no C-20 `--fix` between the two receipts.
1. `SHA=$(git ls-remote <forge state-hub> refs/heads/main | cut -f1)` - the
exact forge head at receipt time (it must contain `c287ed8`).
2. Receipt:
```bash
curl -s -X POST http://10.43.68.154:8000/repos/state-hub/work-record-projection/reconcile \
-H 'Content-Type: application/json' -d "{\"expected_commit\":\"$SHA\"}"
```
Expected at the time of writing (from the live C-20 report):
- `created_edges` includes `STATE-WP-0095-T04 starts_after STATE-WP-0095-T03`
and, if `RMGR-WP-0019-T03` resolves uniquely fleet-wide,
`STATE-WP-0095-T04 starts_after RMGR-WP-0019-T03`, each with
`source_owner = forge:state-hub`, `source_commit = $SHA`;
- `unresolved_edges` lists `STATE-WP-0078 -> ACTIVITY-WP-0026` (`missing`)
unless that workplan has been received by then;
- `external_edges` lists declarations already present as C-20/API rows;
- `removed_edges` is empty (no owned rows exist before the first receipt);
- `status: applied`.
3. Repeat the identical request (fresh idempotency, same `$SHA`): expected
`status: noop`, zero created/removed edges; `unresolved_edges` is reported
again by design.
4. DB proof: `select source_owner, count(*) from workplan_dependencies group by 1`
- new rows only under `forge:state-hub`; NULL-owner count unchanged.
5. Record evidence as `docs/evidence/<date>-dependency-edge-receiving-rollout.json`
in the shape of `2026-10-04-receiving-rollout.json` (image, digest,
`helm_revision`, schema, both receipt outcomes, DB counts).
A failed receipt (e.g. a historical identifier collision as seen for
`the-custodian`) rolls back transactionally; record it as a failure, not as
edge-receiving proof.
## 6. Rollback
- Upgrade failure: `--atomic` restores `PREV_REV` automatically.
- Post-upgrade verification failure:
`helm -n state-hub rollback state-hub $PREV_REV --wait --timeout 3m`.
- **Schema: do not downgrade.** `b3c4d5e6f7a8` is additive and nullable; the
previous image ignores the columns (its ORM model does not map them, and
inserts leave them NULL). Downgrade would drop `source_owner` and so erase
the ownership marker of every receiving-created row, turning those rows into
"external" edges that later receipts could never remove. Retain schema and
data on rollback.
- Receiving-created rows under the previous image: they remain as ordinary
dependency edges. If they must go, delete only rows with
`source_owner = 'forge:<repo>'` - a separate authorized data change, not
part of rollback.
## Open risks
- Live revision/image is unconfirmed (historical-receiving outcome not
recorded); step 0.2 is mandatory.
- Pre-existing C-20/API edges are never adopted; a removed declaration does not
remove them.
- A checkout without `workplans/` is refused before edges are touched while
the hub still holds that repository's workplans, so owned edges are not
removed on an empty source; the dry-run preview does not model that refusal
and would list those removals (preview-only discrepancy).
- No scheduled freshness is part of this change; receiving remains manual.

View file

@ -219,3 +219,16 @@ nonterminal or future gates stop preparation even with prior successful pickup.
Missing safety metadata refuses planning. Thirty staged tests pass; the initial
13-test result remains historical preparation evidence, not complete safety
coverage. T03/T05 stay progress; no dispatch, schedule or source gate clearing.
2026-10-04 T05 follow-up (claude-state-hub): consistency report-only run at
HEAD and with the b2af3c1 checker produced identical findings (27 C-03 FAIL on
STATE-WP-0069/0070/0071/0073/0080 stale hub IDs, 11 WARN C-20/C-26/C-28/C-35);
none caused by fceb5f2/3b9a5c5. Migration b3c4d5e6f7a8 ran
a0b1c2d3e4f5 -> head -> a0b1c2d3e4f5 -> head on a scratch local PostgreSQL
database with a pre-existing edge row preserved (owner NULL). c287ed8 adds the
dry-run edge preview (create/remove/retain/unresolved/external) through the
shared pure planner and fixes duplicate-edge-key collapse in the classifier.
Rollout package prepared, not executed, and requires founder per-change
authorization: docs/rollout-dependency-edge-receiving.md. No deployment,
schedule or production write. T03/T05 status unchanged.