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
8.6 KiB
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)
- Founder authorization for this exact change and target commit, recorded as a
change proposal in
the-custodian/docs/changes/(copy the shape ofstate-hub-qualifier-rollout-2026-10-04.json;outcome: pending). - Identify the live revision and image. The historical-receiving proposal
(
main-5dd3df9) has no recorded outcome, so do not assume revision 71:
Recordhelm -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 currentPREV_REV,PREV_IMAGEand the live schema. Expected schema:a0b1c2d3e4f5. Stop if it is anything else. - Fresh CPU headroom for the surge (API 250m, MCP 10m, migrate job 50m) as in the previous rollout; stop on unrelated pending pods.
- The proof repository is
state-hubitself. Read its current hub edges so the post-check can tell receiving's rows from earlier ones:
and, inside the cluster DB (read-only):curl -s "http://10.43.68.154:8000/workplans/?repo=state-hub" > /tmp/edge-pre-workplans.jsonselect 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
- Push
main(operator; agents in this session did not push). The Forgejoimage.yamlworkflow builds on pushes touchingapi/**,migrations/**,scripts/**etc. and tagsmain-<short sha of the pushed head>. - Confirm the build-and-push run succeeded and that its source commit contains
c287ed8:git merge-base --is-ancestor c287ed8 <image source sha>. - Record
TAG=main-<sha7>and the image digest. Do not use a mutable tag. - Verify the receiving source digest in the image equals the tested source:
sha256sum api/services/forge_projection.pyat the image source commit vs.kubectl exec ... sha256sum /app/api/services/forge_projection.pyafter rollout.
2. Server-side dry run
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)
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 revisionNEW_REV.- API and MCP
1/1Ready; container image IDs match the recorded digest. alembic current=b3c4d5e6f7a8.\d workplan_dependenciesshowssource_owner,source_commit; index present; every pre-existing row hassource_owner IS NULL.GET /state/healthOK;GET /messages/?to_agent=state-hub&limit=1&offset=0answers 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.
SHA=$(git ls-remote <forge state-hub> refs/heads/main | cut -f1)- the exact forge head at receipt time (it must containc287ed8).- Receipt:
Expected at the time of writing (from the live C-20 report):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\"}"created_edgesincludesSTATE-WP-0095-T04 starts_after STATE-WP-0095-T03and, ifRMGR-WP-0019-T03resolves uniquely fleet-wide,STATE-WP-0095-T04 starts_after RMGR-WP-0019-T03, each withsource_owner = forge:state-hub,source_commit = $SHA;unresolved_edgeslistsSTATE-WP-0078 -> ACTIVITY-WP-0026(missing) unless that workplan has been received by then;external_edgeslists declarations already present as C-20/API rows;removed_edgesis empty (no owned rows exist before the first receipt);status: applied.
- Repeat the identical request (fresh idempotency, same
$SHA): expectedstatus: noop, zero created/removed edges;unresolved_edgesis reported again by design. - DB proof:
select source_owner, count(*) from workplan_dependencies group by 1- new rows only under
forge:state-hub; NULL-owner count unchanged.
- new rows only under
- Record evidence as
docs/evidence/<date>-dependency-edge-receiving-rollout.jsonin the shape of2026-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:
--atomicrestoresPREV_REVautomatically. - Post-upgrade verification failure:
helm -n state-hub rollback state-hub $PREV_REV --wait --timeout 3m. - Schema: do not downgrade.
b3c4d5e6f7a8is additive and nullable; the previous image ignores the columns (its ORM model does not map them, and inserts leave them NULL). Downgrade would dropsource_ownerand 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.