From 4b647dc32f3f57fc7b6c7a219fd00a4114d546e0 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 17 Oct 2026 03:39:27 +0200 Subject: [PATCH] Admit reviewed receiver evidence batch 1 --- .../conditional-T07-completion-overlay.json | 30 + .../owner-followthrough-r1/manifest.json | 53 ++ .../state-hub-followthrough.patch | 199 +++++ .../owner-followthrough-r1/validation.json | 21 + .../conditional-T07-completion-overlay.json | 30 + .../generated-copy-exact-diff.patch | 28 + .../owner-followthrough-r2/manifest.json | 61 ++ .../state-hub-followthrough.patch | 238 ++++++ .../owner-followthrough-r2/validation.json | 25 + .../owner-followthrough-r3/REVIEW.md | 17 + .../owner-followthrough-r3/manifest.json | 77 ++ .../state-hub-followthrough.patch | 650 +++++++++++++++ .../owner-followthrough-r3/validation.json | 17 + .../owner-followthrough-r4/REVIEW.md | 43 + .../owner-followthrough-r4/manifest.json | 77 ++ .../state-hub-followthrough.patch | 756 ++++++++++++++++++ .../owner-followthrough-r4/validation.json | 17 + .../owner-followthrough-r5/PUBLICATION.md | 31 + .../owner-followthrough-r5/REVIEW.md | 53 ++ .../STATE95-final-reviewed-overlay.md | 344 ++++++++ .../T09-completion-overlay.patch | 23 + .../apply_reviewed_overlay.py | 12 + .../canonical-validation.json | 13 + .../integrate_source.py | 42 + 24 files changed, 2857 insertions(+) create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/conditional-T07-completion-overlay.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/manifest.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/state-hub-followthrough.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/validation.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/conditional-T07-completion-overlay.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/generated-copy-exact-diff.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/manifest.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/state-hub-followthrough.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/validation.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/REVIEW.md create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/manifest.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/state-hub-followthrough.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/validation.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/REVIEW.md create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/manifest.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/state-hub-followthrough.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/validation.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/PUBLICATION.md create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/REVIEW.md create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/STATE95-final-reviewed-overlay.md create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/T09-completion-overlay.patch create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/apply_reviewed_overlay.py create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/canonical-validation.json create mode 100644 docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r5/integrate_source.py diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/conditional-T07-completion-overlay.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/conditional-T07-completion-overlay.json new file mode 100644 index 00000000..5085823a --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/conditional-T07-completion-overlay.json @@ -0,0 +1,30 @@ +{ + "schema": "statehub.conditional-T07-completion-overlay.v1", + "status": "prepared_not_applied_no_completion_claim", + "source_preimage": { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "sha256": "146caec8f37833d9cd0b509750e5131640725b5617f4b2eede989185e1869d5c" + }, + "guards": [ + "Actual normal cluster owner publication and central receiving status applied, with exact pushed commit receipt", + "RMGR-WP-0019-T06 actually done and source status/UUID projection verified", + "All three exact archived source SHA256 bytes remain equal to frozen manifest", + "All three historical parents/15 tasks full DB row hashes unchanged after normal receiving", + "No missing/unresolved/foreign-holder identity or retirement refusal hidden as success" + ], + "edits_after_guards_only": [ + { + "task_id": "STATE-WP-0095-T07", + "state_hub_task_id": "cfd1ea99-e75b-542e-a434-acefb62f3c7d", + "status": "done", + "evidence": "insert exact actual cluster commit/publication/receiving and preservation receipt refs at activation" + }, + { + "workplan_id": "STATE-WP-0095", + "frontmatter_status": "finished", + "only_if": "all eight retained task IDs done and no unresolved retained work remains" + } + ], + "preserve_all_IDs_UUIDs_history": true, + "normal_owner_commit_push_projection_required": true +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/manifest.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/manifest.json new file mode 100644 index 00000000..080b1005 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/manifest.json @@ -0,0 +1,53 @@ +{ + "schema": "statehub.source-owner-followthrough.v1", + "status": "isolated_source_candidate_not_applied", + "base_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", + "files": [ + { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "base_sha256": "ef10359cc5974283b5bdbfc602adf14e3c24362adf00640698d2937c6538483a", + "candidate_sha256": "146caec8f37833d9cd0b509750e5131640725b5617f4b2eede989185e1869d5c", + "action": "modify" + }, + { + "path": "Makefile", + "base_sha256": "5584ef94071044cca8215141ecd8aee8d6ae5cfe73e8f5719358c680eb8af0d4", + "candidate_sha256": "c5b70e8d4b8ae70380e0ade1c35f9e7fe80038de10080a4723b21ef07a98001e", + "action": "modify" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/Chart.yaml", + "base_sha256": "7fe02b390ffd2ba4ce6f82bd62df900826826401e31bdc2138dd5bf36b51a6ae", + "candidate_sha256": "61a989fcf277d05ffbececb7332ee3ca1d8f369c72f960ab1bc4dbc6815536b8", + "action": "modify" + }, + { + "path": "deploy/railiance/apps/helm/state-hub-values.yaml", + "base_sha256": "f7cc0e9968598f6ce0faa0d56f62d6817845c3570c18b6ac39ed6561b952b197", + "candidate_sha256": "b51296fc937450f4ecf0ae43243262a631b551389c0d09c8bfe58fa10e2f3ee9", + "action": "modify" + }, + { + "path": "docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json", + "base_sha256": null, + "candidate_sha256": "db6cd6931c5763ff390e54c6eb9430aad2c4c7c9f42dd6652f436e549e6f6297", + "action": "add" + } + ], + "patch_sha256": "sha256:3a2f8446262062a9183aff6f219fd6c300ed447624355cb778bb050cdb14a2b9", + "live_effects": false, + "new_runtime_image": false, + "T06": "done_actual_approved_verified_rollout", + "T07": "wait_until_actual_RMGR19T06_cluster_publication_and_preservation_return", + "T08": "done_source_readiness_preserved", + "generic_chart_default_tag": "empty_required_explicit_pin_unchanged", + "declared_production_image": "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "preserve_preexisting_dirty": [ + ".custodian-brief.md", + "WORK-RECORDS.md", + "docs/evidence/legacy-meter-weekly-review-20261014.json", + "docs/evidence/legacy-meter-weekly-review-20261015.json", + "docs/evidence/legacy-meter-weekly-review-20261016.json", + "docs/evidence/legacy-meter-weekly-review-20261017.json" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/state-hub-followthrough.patch b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/state-hub-followthrough.patch new file mode 100644 index 00000000..e44333ab --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/state-hub-followthrough.patch @@ -0,0 +1,199 @@ +--- a/workplans/STATE-WP-0095-historical-source-receiving.md ++++ b/workplans/STATE-WP-0095-historical-source-receiving.md +@@ -199,27 +199,37 @@ + + ```task + id: STATE-WP-0095-T06 +-status: wait ++status: done + priority: high + depends_on: [STATE-WP-0095-T05] +-needs_human: true +-intervention_note: "Review exact committed source, CI image and owner deployment proposal before rollout." ++needs_human: false ++intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention." + state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4" + ``` + +-Use the normal CI image and Helm owner path after exact revision/image pin, +-fresh headroom and reviewed dry-run/diff. API/MCP currently run main-7d99282; +-Helm revision75 is the observed rollback baseline, requiring freshness before +-execution. No deployment assent exists for this successor. Verify delivered +-module hash, health, both readiness checks and expected migrated schema +-c4d5e6f7a8b9; verify exact +-three parents/15 tasks and rejection behavior without committed probe changes. +-Only the verified return unblocks RMGR-WP-0019-T05. The full current image also carries inbox-feed migration c4d5e6f7a8b9, +-although this receiver patch adds none. Helm rollback leaves that migration in +-place; unrelated migration acceptance and old-image compatibility need normal +-owner evidence before rollout approval. Do not run destructive default local +-DB tests; use only exact disposable DB containment. Costs and monetary risk are +-unknown pending exact rollout proposal; no zero-cost or autonomy grant is inferred. ++Completed 2026-10-17 on the founder's approval of exact revision 4 of stable ++proposal STATE-WP-0095-T06-cluster-frozen-receiving-r1 (decision ++ e3798419-8aa9-5425-8c08-20df6bd82a19; approved digest ++ sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32). ++The ordinary owner Helm path delivered revision 76, runtime source 51bc70d and ++exact image manifest sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015. ++API/MCP Ready and primary/railiance01 health report expected schema ++c4d5e6f7a8b9; delivered historical module sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2 matches. ++All three parent and 15 task full DB row hashes/columns/UUIDs remained unchanged. ++Five actual native probes passed: preserve, actual foreign-parent holder move, ++nonterminal task, nonterminal parent and missing task. Every probe rolled back; ++a fresh transaction confirmed complete row restoration. The foreign-holder ++case precisely verified historical binding refusal, not the separate global ++UUID-holder creation branch. No committed probe writes were made. ++ ++Evidence: [verified receiving rollout](../docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json). ++This actual receiving return satisfies the prerequisite of RMGR-WP-0019-T05. ++Normal cluster publication and its immutable-source/row return remain T07; ++source readiness and this deployment do not count that later acceptance twice. ++The additive inbox migration is retained; Helm rollback does not downgrade ++schema or stop an active migration Job. Original failures and exact-UID recovery ++limits remain in the referenced approved packet. No TLS recovery/native private ++admission or new autonomy/cost grant is inferred. + + ## Verify ordinary cluster receiving and immutable historical rows + +@@ -256,6 +266,6 @@ + Command-fixture tests verify both requests and preserve other dry-run phases. + Evidence and original refusal remain in the Custodian cluster historical + publication packet, revisions/r2 and r3-readiness. This is completed source +-preparation; T06 remains the explicit native rollout gate. The runtime image ++preparation; T06 separately records the verified actual rollout. The runtime image + continues to be main-51bc70d; Makefile/workplan changes do not create a new + application image or native acceptance. +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ + RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub + RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub + RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml +-RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-0747b4d ++RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015 + RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform + RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests + # Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable. +--- a/deploy/railiance/apps/charts/state-hub/Chart.yaml ++++ b/deploy/railiance/apps/charts/state-hub/Chart.yaml +@@ -3,4 +3,4 @@ + description: State Hub API service for private Railiance operation + type: application + version: 0.1.0 +-appVersion: "main-5dd3df9" ++appVersion: "main-51bc70d" +--- a/deploy/railiance/apps/helm/state-hub-values.yaml ++++ b/deploy/railiance/apps/helm/state-hub-values.yaml +@@ -6,7 +6,7 @@ + + image: + repository: forgejo.coulomb.social/coulomb/state-hub +- tag: "main-0747b4d" ++ tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015" + + ingress: + enabled: false +--- /dev/null ++++ b/docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json +@@ -0,0 +1,102 @@ ++{ ++ "schema": "statehub.cluster-historical-receiving-delivered.v1", ++ "status": "verified", ++ "proposal_id": "STATE-WP-0095-T06-cluster-frozen-receiving-r1", ++ "approved_revision": 4, ++ "approved_digest_sha256": "sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32", ++ "approval_decision_id": "e3798419-8aa9-5425-8c08-20df6bd82a19", ++ "source_revision": "51bc70d1ff8f25450a8650d8e50caf053093a02e", ++ "readiness_source_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", ++ "helm_revision": 76, ++ "image_reference": "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", ++ "expected_schema": "c4d5e6f7a8b9", ++ "health": { ++ "status": "ok", ++ "instance_role": "primary", ++ "instance_label": "railiance01", ++ "schema": { ++ "status": "ok", ++ "applied": "c4d5e6f7a8b9", ++ "expected": "c4d5e6f7a8b9" ++ } ++ }, ++ "pods_ready": true, ++ "historical_module_sha256": "sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", ++ "historical_preservation_verified": true, ++ "parents": 3, ++ "tasks": 15, ++ "full_historical_rows_equal": true, ++ "native_rollback_probes": { ++ "foreign-holder": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "missing-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-parent": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "preserve": { ++ "full_historical_rows_equal": true, ++ "full_historical_rows_restored": true, ++ "native_outcome_status": "applied", ++ "rolled_back": true, ++ "status": "passed" ++ } ++ }, ++ "native_probe_commits": 0, ++ "fresh_transaction_full_rows_equal": true, ++ "source_receiving_gate_delivered": true, ++ "normal_cluster_publication_verified": false, ++ "registrar_admission_or_cluster_publication_not_inferred": true, ++ "authority_limits": [ ++ "Rollback probe SQL effects rolled back; foreign-holder case is historical-binding refusal, not separate global holder branch.", ++ "Actual historical native receiving is verified; owner-native TLS/private admission remains unresolved.", ++ "Migration schema remains additive; Helm rollback does not downgrade schema or stop an active hook Job." ++ ], ++ "evidence": [ ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/approval-decision.json", ++ "sha256": "sha256:b51554c004daa882f3709f67bdae7f2e939d611120383e61be04ec6969878134" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/helm-return.json", ++ "sha256": "sha256:61727deacf22d37da2d28cbaf04f5a3ddee0f9ff7571c77f1e85dba97963b572" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/delivered-runtime.json", ++ "sha256": "sha256:89681066e8fe139456163ebfafa3efe77ffae8534ea167219230b0a74367f129" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/native-refusal-probes.json", ++ "sha256": "sha256:673ccef965c820704dc310a1bf7832b583c30c3edfeb814528cf892555199172" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-full-row-hashes.json", ++ "sha256": "sha256:75cdd87eef79059ff3f484ed24ca69e285899cdf8d851cc4e472653c417eb0ee" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-full-row-hashes.json", ++ "sha256": "sha256:2156aad18f190384838c27e792706a156a7423391408afbc6107e6c28c5872d8" ++ } ++ ] ++} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/validation.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/validation.json new file mode 100644 index 00000000..1e94c920 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r1/validation.json @@ -0,0 +1,21 @@ +{ + "schema": "statehub-followthrough-source-validation.v1", + "patch_apply_check_exit": 0, + "direct_local_helm_render_exit": 0, + "all3runtime_images_match_promoted_digest": true, + "appVersion": "main-51bc70d", + "cluster_contacted": false, + "production_mutations": false, + "normal_make_render": { + "status": "refused_preexisting_canon_copy_drift", + "guard_not_bypassed": true, + "chart_copy_version": "1.0", + "canon_version": "1.1", + "differences": [ + "category publication", + "purpose history", + "purpose participation" + ], + "scope": "No classification copy edit/guard relaxation included in followthrough." + } +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/conditional-T07-completion-overlay.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/conditional-T07-completion-overlay.json new file mode 100644 index 00000000..cb3200df --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/conditional-T07-completion-overlay.json @@ -0,0 +1,30 @@ +{ + "schema": "statehub.conditional-T07-completion-overlay.v1", + "status": "prepared_not_applied_no_completion_claim", + "source_preimage": { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "sha256": "921716ab00993a79917ae5b4750e5b61ae22af4c4fd410b9ccf2be9ec54560df" + }, + "guards": [ + "Actual normal cluster owner publication and central receiving status applied, with exact pushed commit receipt", + "RMGR-WP-0019-T06 actually done and source status/UUID projection verified", + "All three exact archived source SHA256 bytes remain equal to frozen manifest", + "All three historical parents/15 tasks full DB row hashes unchanged after normal receiving", + "No missing/unresolved/foreign-holder identity or retirement refusal hidden as success" + ], + "edits_after_guards_only": [ + { + "task_id": "STATE-WP-0095-T07", + "state_hub_task_id": "cfd1ea99-e75b-542e-a434-acefb62f3c7d", + "status": "done", + "evidence": "insert exact actual cluster commit/publication/receiving and preservation receipt refs at activation" + }, + { + "workplan_id": "STATE-WP-0095", + "frontmatter_status": "finished", + "only_if": "all eight retained task IDs done and no unresolved retained work remains" + } + ], + "preserve_all_IDs_UUIDs_history": true, + "normal_owner_commit_push_projection_required": true +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/generated-copy-exact-diff.patch b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/generated-copy-exact-diff.patch new file mode 100644 index 00000000..6578d7b3 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/generated-copy-exact-diff.patch @@ -0,0 +1,28 @@ +--- prior-chart-copy ++++ generated-canon-copy +@@ -13,7 +13,7 @@ + # to match the standard version. CUST-WP-0050 T01. + + standard: "Repo Classification Standard" +-version: "1.0" ++version: "1.1" + canon_id: "canon-repo-classification" + + # category — exactly 1 required (§5) +@@ -24,6 +24,7 @@ + - tooling + - product + - business ++ - publication + + # domain / secondary_domains — primary exactly 1; secondaries 0..n (§6) + domains: +@@ -85,6 +86,8 @@ + - traceability + - documentation + - decision-support ++ - history ++ - participation + platform_and_operations: + - platform + - deployment diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/manifest.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/manifest.json new file mode 100644 index 00000000..c2d1f89a --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/manifest.json @@ -0,0 +1,61 @@ +{ + "schema": "statehub.source-owner-followthrough.v1", + "status": "isolated_six_path_source_refinement_not_applied", + "base_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", + "files": [ + { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "action": "modify", + "base_sha256": "ef10359cc5974283b5bdbfc602adf14e3c24362adf00640698d2937c6538483a", + "candidate_sha256": "921716ab00993a79917ae5b4750e5b61ae22af4c4fd410b9ccf2be9ec54560df" + }, + { + "path": "Makefile", + "action": "modify", + "base_sha256": "5584ef94071044cca8215141ecd8aee8d6ae5cfe73e8f5719358c680eb8af0d4", + "candidate_sha256": "c5b70e8d4b8ae70380e0ade1c35f9e7fe80038de10080a4723b21ef07a98001e" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/Chart.yaml", + "action": "modify", + "base_sha256": "7fe02b390ffd2ba4ce6f82bd62df900826826401e31bdc2138dd5bf36b51a6ae", + "candidate_sha256": "61a989fcf277d05ffbececb7332ee3ca1d8f369c72f960ab1bc4dbc6815536b8" + }, + { + "path": "deploy/railiance/apps/helm/state-hub-values.yaml", + "action": "modify", + "base_sha256": "f7cc0e9968598f6ce0faa0d56f62d6817845c3570c18b6ac39ed6561b952b197", + "candidate_sha256": "b51296fc937450f4ecf0ae43243262a631b551389c0d09c8bfe58fa10e2f3ee9" + }, + { + "path": "docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json", + "action": "add", + "base_sha256": null, + "candidate_sha256": "db6cd6931c5763ff390e54c6eb9430aad2c4c7c9f42dd6652f436e549e6f6297" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml", + "action": "modify", + "base_sha256": "9c36fbd5308b19695f9f05d86f8e315bbeccdd267575ce4e2fccf6fb2ea82e32", + "candidate_sha256": "f53c651bb4329c09c04c66f906e2462fe4339ab51ef0ec3da9f942c3016f5bed" + } + ], + "patch_sha256": "sha256:12ee2ab6f4cc45ff8cfcb4c03ef84a7e207eff13fdaa194a7281734f5cbc4021", + "live_effects": false, + "new_runtime_image": false, + "T06": "done_actual_approved_verified_rollout", + "T07": "wait_until_actual_RMGR19T06_cluster_publication_and_preservation_return", + "T08": "done_source_readiness_preserved", + "generic_chart_default_tag": "empty_required_explicit_pin_unchanged", + "declared_production_image": "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "preserve_preexisting_dirty": [ + ".custodian-brief.md", + "WORK-RECORDS.md", + "docs/evidence/legacy-meter-weekly-review-20261014.json", + "docs/evidence/legacy-meter-weekly-review-20261015.json", + "docs/evidence/legacy-meter-weekly-review-20261016.json", + "docs/evidence/legacy-meter-weekly-review-20261017.json" + ], + "revision": 2, + "source_repair": "Existing generator refreshes chart classification copy to unchanged authoritative canon; no live ConfigMap apply or new runtime image." +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/state-hub-followthrough.patch b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/state-hub-followthrough.patch new file mode 100644 index 00000000..0584675b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/state-hub-followthrough.patch @@ -0,0 +1,238 @@ +--- a/workplans/STATE-WP-0095-historical-source-receiving.md ++++ b/workplans/STATE-WP-0095-historical-source-receiving.md +@@ -199,27 +199,37 @@ + + ```task + id: STATE-WP-0095-T06 +-status: wait ++status: done + priority: high + depends_on: [STATE-WP-0095-T05] +-needs_human: true +-intervention_note: "Review exact committed source, CI image and owner deployment proposal before rollout." ++needs_human: false ++intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention." + state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4" + ``` + +-Use the normal CI image and Helm owner path after exact revision/image pin, +-fresh headroom and reviewed dry-run/diff. API/MCP currently run main-7d99282; +-Helm revision75 is the observed rollback baseline, requiring freshness before +-execution. No deployment assent exists for this successor. Verify delivered +-module hash, health, both readiness checks and expected migrated schema +-c4d5e6f7a8b9; verify exact +-three parents/15 tasks and rejection behavior without committed probe changes. +-Only the verified return unblocks RMGR-WP-0019-T05. The full current image also carries inbox-feed migration c4d5e6f7a8b9, +-although this receiver patch adds none. Helm rollback leaves that migration in +-place; unrelated migration acceptance and old-image compatibility need normal +-owner evidence before rollout approval. Do not run destructive default local +-DB tests; use only exact disposable DB containment. Costs and monetary risk are +-unknown pending exact rollout proposal; no zero-cost or autonomy grant is inferred. ++Completed 2026-10-17 on the founder's approval of exact revision 4 of stable ++proposal STATE-WP-0095-T06-cluster-frozen-receiving-r1 (decision ++ e3798419-8aa9-5425-8c08-20df6bd82a19; approved digest ++ sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32). ++The ordinary owner Helm path delivered revision 76, runtime source 51bc70d and ++exact image manifest sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015. ++API/MCP Ready and primary/railiance01 health report expected schema ++c4d5e6f7a8b9; delivered historical module sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2 matches. ++All three parent and 15 task full DB row hashes/columns/UUIDs remained unchanged. ++Five actual native probes passed: preserve, actual foreign-parent holder move, ++nonterminal task, nonterminal parent and missing task. Every probe rolled back; ++a fresh transaction confirmed complete row restoration. The foreign-holder ++case precisely verified historical binding refusal, not the separate global ++UUID-holder creation branch. No committed probe writes were made. ++ ++Evidence: [verified receiving rollout](../docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json). ++This actual receiving return satisfies the prerequisite of RMGR-WP-0019-T05. ++Normal cluster publication and its immutable-source/row return remain T07; ++source readiness and this deployment do not count that later acceptance twice. ++The additive inbox migration is retained; Helm rollback does not downgrade ++schema or stop an active migration Job. Original failures and exact-UID recovery ++limits remain in the referenced approved packet. No TLS recovery/native private ++admission or new autonomy/cost grant is inferred. + + ## Verify ordinary cluster receiving and immutable historical rows + +@@ -256,6 +266,17 @@ + Command-fixture tests verify both requests and preserve other dry-run phases. + Evidence and original refusal remain in the Custodian cluster historical + publication packet, revisions/r2 and r3-readiness. This is completed source +-preparation; T06 remains the explicit native rollout gate. The runtime image ++preparation; T06 separately records the verified actual rollout. The runtime image + continues to be main-51bc70d; Makefile/workplan changes do not create a new + application image or native acceptance. ++ ++ ++T08 source repair refinement, 2026-10-17: the required normal Make render ++correctly refused a pre-existing generated chart classification copy at v1.0 ++after authoritative canon reached v1.1. Regenerated only through the existing ++scripts/sync_classification_allowed.py, preserving canon and strict check. ++The exact source diff is version 1.0→1.1 plus publication/history/participation. ++Normal Make rendering and the focused classification check passed in an ++isolated owner-layout copy. The original refusal is retained in the Custodian ++owner-followthrough-r1 validation receipt. This is source repair only: no live ++ConfigMap apply, new runtime image, policy relaxation or T07 completion. +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ + RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub + RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub + RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml +-RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-0747b4d ++RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015 + RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform + RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests + # Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable. +--- a/deploy/railiance/apps/charts/state-hub/Chart.yaml ++++ b/deploy/railiance/apps/charts/state-hub/Chart.yaml +@@ -3,4 +3,4 @@ + description: State Hub API service for private Railiance operation + type: application + version: 0.1.0 +-appVersion: "main-5dd3df9" ++appVersion: "main-51bc70d" +--- a/deploy/railiance/apps/helm/state-hub-values.yaml ++++ b/deploy/railiance/apps/helm/state-hub-values.yaml +@@ -6,7 +6,7 @@ + + image: + repository: forgejo.coulomb.social/coulomb/state-hub +- tag: "main-0747b4d" ++ tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015" + + ingress: + enabled: false +--- /dev/null ++++ b/docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json +@@ -0,0 +1,102 @@ ++{ ++ "schema": "statehub.cluster-historical-receiving-delivered.v1", ++ "status": "verified", ++ "proposal_id": "STATE-WP-0095-T06-cluster-frozen-receiving-r1", ++ "approved_revision": 4, ++ "approved_digest_sha256": "sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32", ++ "approval_decision_id": "e3798419-8aa9-5425-8c08-20df6bd82a19", ++ "source_revision": "51bc70d1ff8f25450a8650d8e50caf053093a02e", ++ "readiness_source_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", ++ "helm_revision": 76, ++ "image_reference": "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", ++ "expected_schema": "c4d5e6f7a8b9", ++ "health": { ++ "status": "ok", ++ "instance_role": "primary", ++ "instance_label": "railiance01", ++ "schema": { ++ "status": "ok", ++ "applied": "c4d5e6f7a8b9", ++ "expected": "c4d5e6f7a8b9" ++ } ++ }, ++ "pods_ready": true, ++ "historical_module_sha256": "sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", ++ "historical_preservation_verified": true, ++ "parents": 3, ++ "tasks": 15, ++ "full_historical_rows_equal": true, ++ "native_rollback_probes": { ++ "foreign-holder": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "missing-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-parent": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "preserve": { ++ "full_historical_rows_equal": true, ++ "full_historical_rows_restored": true, ++ "native_outcome_status": "applied", ++ "rolled_back": true, ++ "status": "passed" ++ } ++ }, ++ "native_probe_commits": 0, ++ "fresh_transaction_full_rows_equal": true, ++ "source_receiving_gate_delivered": true, ++ "normal_cluster_publication_verified": false, ++ "registrar_admission_or_cluster_publication_not_inferred": true, ++ "authority_limits": [ ++ "Rollback probe SQL effects rolled back; foreign-holder case is historical-binding refusal, not separate global holder branch.", ++ "Actual historical native receiving is verified; owner-native TLS/private admission remains unresolved.", ++ "Migration schema remains additive; Helm rollback does not downgrade schema or stop an active hook Job." ++ ], ++ "evidence": [ ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/approval-decision.json", ++ "sha256": "sha256:b51554c004daa882f3709f67bdae7f2e939d611120383e61be04ec6969878134" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/helm-return.json", ++ "sha256": "sha256:61727deacf22d37da2d28cbaf04f5a3ddee0f9ff7571c77f1e85dba97963b572" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/delivered-runtime.json", ++ "sha256": "sha256:89681066e8fe139456163ebfafa3efe77ffae8534ea167219230b0a74367f129" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/native-refusal-probes.json", ++ "sha256": "sha256:673ccef965c820704dc310a1bf7832b583c30c3edfeb814528cf892555199172" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-full-row-hashes.json", ++ "sha256": "sha256:75cdd87eef79059ff3f484ed24ca69e285899cdf8d851cc4e472653c417eb0ee" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-full-row-hashes.json", ++ "sha256": "sha256:2156aad18f190384838c27e792706a156a7423391408afbc6107e6c28c5872d8" ++ } ++ ] ++} +--- a/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml ++++ b/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml +@@ -13,7 +13,7 @@ + # to match the standard version. CUST-WP-0050 T01. + + standard: "Repo Classification Standard" +-version: "1.0" ++version: "1.1" + canon_id: "canon-repo-classification" + + # category — exactly 1 required (§5) +@@ -24,6 +24,7 @@ + - tooling + - product + - business ++ - publication + + # domain / secondary_domains — primary exactly 1; secondaries 0..n (§6) + domains: +@@ -85,6 +86,8 @@ + - traceability + - documentation + - decision-support ++ - history ++ - participation + platform_and_operations: + - platform + - deployment diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/validation.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/validation.json new file mode 100644 index 00000000..220461db --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r2/validation.json @@ -0,0 +1,25 @@ +{ + "schema": "statehub-source-refinement-validation.v1", + "normal_owner_make_render_exit": 0, + "focused_classification_check_exit": 0, + "sync_script_sha256": "sha256:b1c06691843d15ac4e82c0eba59d566c754aa5163337ed4ee8644c3dc9283bcd", + "canon_sha256": "sha256:c0e0452bb08fe1700f2672b5b019da08c3d70737fa5b00bdfeab14ef32cb6477", + "exact_copy_diff": { + "removed": [ + "version: \"1.0\"" + ], + "added": [ + "version: \"1.1\"", + " - publication", + " - history", + " - participation" + ] + }, + "all3runtime_images_exact_promoted_digest": true, + "original_failed_render_receipt": "../owner-followthrough-r1/validation.json", + "canon_edited": false, + "guard_changed": false, + "cluster_contacted": false, + "new_runtime_image": false, + "canonical_mutations": false +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/REVIEW.md b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/REVIEW.md new file mode 100644 index 00000000..55f4a7fb --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/REVIEW.md @@ -0,0 +1,17 @@ +# Immutable ordinary-binding repair, source revision 3 + +Canonical base: 086d79a2736e34b916bd7de3c8a65e2ea38a3d64. The ten-path patch includes the previously reviewed six-path followthrough and four guard/test paths. Originals and the actual failed publication receipt remain untouched. + +The approved revision-4 receiving rollout preserved all 18 rows at its verification stage. A later ordinary Cluster publication changed exactly three parent full-row hashes; all 15 task hashes remained unchanged. The binding endpoint assigns a new backing sync timestamp unconditionally, providing a concrete mutation path. Hash-only snapshots do not establish the exact changed columns or supply original preimages. This patch does not restore them. + +Only the three existing task-bound railiance-cluster registry entries receive this guard. The CLI requires all three scan members, validates their exact source bytes and metadata, and omits valid immutable bindings before contacting any receiver. A refused batch becomes C-44 FAIL and returns a nonzero exit even in remote-all mode. The API independently validates all request metadata before any assignment, locks and refreshes parent/task rows, checks actual repo ownership and existing terminal required-bound tasks, and skips every setter for valid frozen rows. Additional children retain existing Forge receiving semantics; no new child-set invariant is introduced. The ordinary binding response retains updated/received and adds preserved. Frozen-only requests perform no commit or cache invalidation. Existing get_session uses an async-session context manager, which closes the read transaction and releases locks on exit. + +Validation: 40 isolated tests pass with --noconftest. Tests extract actual source functions/classes without importing owner app configuration or opening a DB. They verify all three exact sources, missing scan member/UUID, changed source and metadata, foreign names, complete mixed-batch refusal, API ownership and terminal-state refusal, no frozen setters/commit, ordinary compatibility, lock/refresh query intent and actual CLI exit criteria. The full tracked chart plus overlay passes normal Make render/classification check: eight objects, three unchanged digest-qualified images. No concurrency/native DB behavior is claimed by these source tests. + +T06 remains done for the approved delivery at its verification stage; T07 remains wait for failed whole-pipeline preservation. T09 is progress until source publication. T10 is wait/needs_human for a separate exact new runtime approval. New task UUIDs are intentionally unallocated pending root review and normal managed allocation. Preserve the original dirty brief/index/weekly evidence and index state. + +## Next runtime proposal preparation + +After reviewed source integration and normal publication, obtain normal CI status and resolve the resulting exact immutable image manifest. Inspect selected delivered source hashes, including historical_projection.py, workstreams.py and the installed closure; do not reuse the prior main-51bc70d runtime for the new API guard. Prepare digest-qualified packaged-chart reuse-values server dry-run, compare intended object identities/spec hashes, collect fresh release/headroom/schema and full-row hash baseline, then record a new scoped proposal and supervisor disposition. Current known prior baseline was Helm 76/schema c4d5e6f7a8b9; refresh rather than assume it remains current. CI revision, new image digest, final chart package hash and exact execution digest remain unresolved until publication. + +No new rollout is authorized by the prior revision-4 approval. A new rollout must retain migration closure and rollback limits: Helm rollback does not downgrade schema, and timeout/atomic does not necessarily stop an active hook Job. Cancellation may address only the exact owned trial Job UID/image after identity verification. Unknown migration recovery or connection/lock quiescence stops for attended owner handling; no credential grant or invented DB-session mapping. EUR cost and risk are unknown until reviewed; no bounded-outage assertion. New baseline preservation and rollback-only refusal probes prove the new stage, never restoration of the unavailable original parent preimages. T07/RMGR19T06 cannot close on source publication alone. diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/manifest.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/manifest.json new file mode 100644 index 00000000..32fbd9e7 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/manifest.json @@ -0,0 +1,77 @@ +{ + "schema": "statehub.source-owner-followthrough.v1", + "revision": 3, + "status": "isolated_source_candidate_not_published", + "base_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", + "files": [ + { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "action": "modify", + "base_sha256": "ef10359cc5974283b5bdbfc602adf14e3c24362adf00640698d2937c6538483a", + "candidate_sha256": "287001ec92d5a9b851b0d70e56747730a9bb150fcda09e97d6e6e9ddf39c7b5c" + }, + { + "path": "Makefile", + "action": "modify", + "base_sha256": "5584ef94071044cca8215141ecd8aee8d6ae5cfe73e8f5719358c680eb8af0d4", + "candidate_sha256": "c5b70e8d4b8ae70380e0ade1c35f9e7fe80038de10080a4723b21ef07a98001e" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/Chart.yaml", + "action": "modify", + "base_sha256": "7fe02b390ffd2ba4ce6f82bd62df900826826401e31bdc2138dd5bf36b51a6ae", + "candidate_sha256": "61a989fcf277d05ffbececb7332ee3ca1d8f369c72f960ab1bc4dbc6815536b8" + }, + { + "path": "deploy/railiance/apps/helm/state-hub-values.yaml", + "action": "modify", + "base_sha256": "f7cc0e9968598f6ce0faa0d56f62d6817845c3570c18b6ac39ed6561b952b197", + "candidate_sha256": "b51296fc937450f4ecf0ae43243262a631b551389c0d09c8bfe58fa10e2f3ee9" + }, + { + "path": "docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json", + "action": "add", + "base_sha256": null, + "candidate_sha256": "fb0b59ba3558e084fb4ae2c36c556f3cfbb2a889f6fd86d4e803c52f483b26fd" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml", + "action": "modify", + "base_sha256": "9c36fbd5308b19695f9f05d86f8e315bbeccdd267575ce4e2fccf6fb2ea82e32", + "candidate_sha256": "f53c651bb4329c09c04c66f906e2462fe4339ab51ef0ec3da9f942c3016f5bed" + }, + { + "path": "api/services/historical_projection.py", + "action": "modify", + "base_sha256": "e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", + "candidate_sha256": "e24e57c58f4aaa4ec9d9bf088009ac69006e53f75b9477ee53dc26c4e850b975" + }, + { + "path": "api/routers/workstreams.py", + "action": "modify", + "base_sha256": "61e7a0ab638b155e9e0b2a72fe8e0ddd2edf5bc6c2d3ca7f6c3ff51e8868910b", + "candidate_sha256": "9c9bf4376dd323b52109912bb3f615949e89ac02c8b88b4720f569f8565ad1d6" + }, + { + "path": "scripts/consistency_check.py", + "action": "modify", + "base_sha256": "1827921c79b476bee823c2ddf9896aeb83f3efca6548427e17b3244216bbc6b5", + "candidate_sha256": "ae96d6260f6c88d35bc2d3f22f2a6061367621fd0416387d25d8683c8e804e06" + }, + { + "path": "tests/test_frozen_binding_guard_isolated.py", + "action": "add", + "base_sha256": null, + "candidate_sha256": "78eb62178fdecf9b5f6c40d398f8c44e93ff740abaf86557ab51ceb708d2ed65" + } + ], + "patch_sha256": "sha256:e9bc2ba9f81b6290945b6de6e0a00ba4c995478b17da786605f5a0466e300b3d", + "T06": "done_verified_approved_rollout_at_stage", + "T07": "wait_actual_later_parent_row_preservation_failure", + "T08": "done_source_repair_pending_publication", + "T09": "progress_until_source_published", + "T10": "wait_needs_human_new_runtime", + "live_effects": false, + "original_parent_preimages": "unknown_do_not_restore_guesses", + "managed_UUIDs": "T09_T10_not_allocated_pending_review" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/state-hub-followthrough.patch b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/state-hub-followthrough.patch new file mode 100644 index 00000000..4611548e --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/state-hub-followthrough.patch @@ -0,0 +1,650 @@ +--- a/workplans/STATE-WP-0095-historical-source-receiving.md ++++ b/workplans/STATE-WP-0095-historical-source-receiving.md +@@ -199,27 +199,37 @@ + + ```task + id: STATE-WP-0095-T06 +-status: wait ++status: done + priority: high + depends_on: [STATE-WP-0095-T05] +-needs_human: true +-intervention_note: "Review exact committed source, CI image and owner deployment proposal before rollout." ++needs_human: false ++intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention." + state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4" + ``` + +-Use the normal CI image and Helm owner path after exact revision/image pin, +-fresh headroom and reviewed dry-run/diff. API/MCP currently run main-7d99282; +-Helm revision75 is the observed rollback baseline, requiring freshness before +-execution. No deployment assent exists for this successor. Verify delivered +-module hash, health, both readiness checks and expected migrated schema +-c4d5e6f7a8b9; verify exact +-three parents/15 tasks and rejection behavior without committed probe changes. +-Only the verified return unblocks RMGR-WP-0019-T05. The full current image also carries inbox-feed migration c4d5e6f7a8b9, +-although this receiver patch adds none. Helm rollback leaves that migration in +-place; unrelated migration acceptance and old-image compatibility need normal +-owner evidence before rollout approval. Do not run destructive default local +-DB tests; use only exact disposable DB containment. Costs and monetary risk are +-unknown pending exact rollout proposal; no zero-cost or autonomy grant is inferred. ++Completed 2026-10-17 on the founder's approval of exact revision 4 of stable ++proposal STATE-WP-0095-T06-cluster-frozen-receiving-r1 (decision ++ e3798419-8aa9-5425-8c08-20df6bd82a19; approved digest ++ sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32). ++The ordinary owner Helm path delivered revision 76, runtime source 51bc70d and ++exact image manifest sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015. ++API/MCP Ready and primary/railiance01 health report expected schema ++c4d5e6f7a8b9; delivered historical module sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2 matches. ++All three parent and 15 task full DB row hashes/columns/UUIDs remained unchanged. ++Five actual native probes passed: preserve, actual foreign-parent holder move, ++nonterminal task, nonterminal parent and missing task. Every probe rolled back; ++a fresh transaction confirmed complete row restoration. The foreign-holder ++case precisely verified historical binding refusal, not the separate global ++UUID-holder creation branch. No committed probe writes were made. ++ ++Evidence: [verified receiving rollout](../docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json). ++This actual receiving return satisfies the prerequisite of RMGR-WP-0019-T05. ++Normal cluster publication and its immutable-source/row return remain T07; ++source readiness and this deployment do not count that later acceptance twice. ++The additive inbox migration is retained; Helm rollback does not downgrade ++schema or stop an active migration Job. Original failures and exact-UID recovery ++limits remain in the referenced approved packet. No TLS recovery/native private ++admission or new autonomy/cost grant is inferred. + + ## Verify ordinary cluster receiving and immutable historical rows + +@@ -256,6 +266,70 @@ + Command-fixture tests verify both requests and preserve other dry-run phases. + Evidence and original refusal remain in the Custodian cluster historical + publication packet, revisions/r2 and r3-readiness. This is completed source +-preparation; T06 remains the explicit native rollout gate. The runtime image ++preparation; T06 separately records the verified actual rollout. The runtime image + continues to be main-51bc70d; Makefile/workplan changes do not create a new + application image or native acceptance. ++ ++ ++T08 source repair refinement, 2026-10-17: the required normal Make render ++correctly refused a pre-existing generated chart classification copy at v1.0 ++after authoritative canon reached v1.1. Regenerated only through the existing ++scripts/sync_classification_allowed.py, preserving canon and strict check. ++The exact source diff is version 1.0→1.1 plus publication/history/participation. ++Normal Make rendering and the focused classification check passed in an ++isolated owner-layout copy. The original refusal is retained in the Custodian ++owner-followthrough-r1 validation receipt. This is source repair only: no live ++ConfigMap apply, new runtime image, policy relaxation or T07 completion. ++ ++ ++T07 actual ordinary-publication return, 2026-10-17: cluster owner deep consistency ++and normal fast projection completed, exact archived source bytes and all 15 ++historical task full-row hashes remained unchanged. The later full-row check ++FAILED for exactly the three parent rows. The earlier approved T06 rollout had ++preserved all 18 rows at its verification stage; that success is not the later ++whole-pipeline acceptance. The original snapshots retain hashes only, so exact ++changed columns and original field preimages are not established. No guessed ++DB repair or claim that all 18 original row hashes are restored is authorized. ++T07 and RMGR-WP-0019-T06 remain waiting pending T09/T10 and a reviewed return. ++ ++## Guard immutable historical index bindings in CLI and receiving API ++ ++```task ++id: STATE-WP-0095-T09 ++status: progress ++priority: high ++depends_on: [STATE-WP-0095-T06] ++``` ++ ++Prepare and publish the narrowly scoped ordinary-binding repair for the three ++existing task-bound Cluster registry entries. The CLI verifies exact frozen ++source metadata/bytes and omits those bindings, including on old receivers. ++The API independently validates the complete batch before any mutation, checks ++actual repo ownership and the existing terminal parent/task closure, and ++preserves valid frozen rows without field or timestamp assignments. Invalid ++metadata, missing/foreign/nonterminal rows refuse the batch. Unknown ordinary ++bindings retain existing behavior; historical IDs, UUIDs and source bytes stay ++unchanged. Isolated regression tests and normal owner rendering are required. ++Mark done only after reviewed source publication and normal owner projection; ++source preparation does not establish delivered runtime or T07 acceptance. ++ ++## Deliver and verify the ordinary-binding guard successor ++ ++```task ++id: STATE-WP-0095-T10 ++status: wait ++priority: high ++depends_on: [STATE-WP-0095-T09] ++needs_human: true ++intervention_note: "New API runtime requires its own exact image, reviewed preflight and scoped rollout approval; prior revision-4 approval does not cover it." ++``` ++ ++After source publication, resolve normal CI status and immutable image identity, ++verify delivered guard source closure, prepare exact chart/reuse-values dry-run, ++full closure/headroom/schema baseline and conditional recovery. Retain additive ++migration and active-Job cancellation limitations; no Helm schema downgrade or ++bounded-outage claim. Obtain scoped approval before live rollout. Then verify ++new full-row baseline preservation and actual rollback-only refusal probes, ++and only then return to T07 ordinary-pipeline acceptance. This prevents future ++binding mutation; it does not reconstruct or restore original parent hashes. ++No production changes, new autonomy or credential authority are granted here. +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ + RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub + RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub + RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml +-RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-0747b4d ++RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015 + RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform + RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests + # Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable. +--- a/deploy/railiance/apps/charts/state-hub/Chart.yaml ++++ b/deploy/railiance/apps/charts/state-hub/Chart.yaml +@@ -3,4 +3,4 @@ + description: State Hub API service for private Railiance operation + type: application + version: 0.1.0 +-appVersion: "main-5dd3df9" ++appVersion: "main-51bc70d" +--- a/deploy/railiance/apps/helm/state-hub-values.yaml ++++ b/deploy/railiance/apps/helm/state-hub-values.yaml +@@ -6,7 +6,7 @@ + + image: + repository: forgejo.coulomb.social/coulomb/state-hub +- tag: "main-0747b4d" ++ tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015" + + ingress: + enabled: false +--- /dev/null ++++ b/docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json +@@ -0,0 +1,116 @@ ++{ ++ "schema": "statehub.cluster-historical-receiving-delivered.v1", ++ "status": "verified", ++ "proposal_id": "STATE-WP-0095-T06-cluster-frozen-receiving-r1", ++ "approved_revision": 4, ++ "approved_digest_sha256": "sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32", ++ "approval_decision_id": "e3798419-8aa9-5425-8c08-20df6bd82a19", ++ "source_revision": "51bc70d1ff8f25450a8650d8e50caf053093a02e", ++ "readiness_source_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", ++ "helm_revision": 76, ++ "image_reference": "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", ++ "expected_schema": "c4d5e6f7a8b9", ++ "health": { ++ "status": "ok", ++ "instance_role": "primary", ++ "instance_label": "railiance01", ++ "schema": { ++ "status": "ok", ++ "applied": "c4d5e6f7a8b9", ++ "expected": "c4d5e6f7a8b9" ++ } ++ }, ++ "pods_ready": true, ++ "historical_module_sha256": "sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", ++ "historical_preservation_verified": true, ++ "parents": 3, ++ "tasks": 15, ++ "full_historical_rows_equal": true, ++ "native_rollback_probes": { ++ "foreign-holder": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "missing-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-parent": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "preserve": { ++ "full_historical_rows_equal": true, ++ "full_historical_rows_restored": true, ++ "native_outcome_status": "applied", ++ "rolled_back": true, ++ "status": "passed" ++ } ++ }, ++ "native_probe_commits": 0, ++ "fresh_transaction_full_rows_equal": true, ++ "source_receiving_gate_delivered": true, ++ "normal_cluster_publication_verified": false, ++ "registrar_admission_or_cluster_publication_not_inferred": true, ++ "authority_limits": [ ++ "Rollback probe SQL effects rolled back; foreign-holder case is historical-binding refusal, not separate global holder branch.", ++ "Actual historical native receiving is verified; owner-native TLS/private admission remains unresolved.", ++ "Migration schema remains additive; Helm rollback does not downgrade schema or stop an active hook Job." ++ ], ++ "evidence": [ ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/approval-decision.json", ++ "sha256": "sha256:b51554c004daa882f3709f67bdae7f2e939d611120383e61be04ec6969878134" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/helm-return.json", ++ "sha256": "sha256:61727deacf22d37da2d28cbaf04f5a3ddee0f9ff7571c77f1e85dba97963b572" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/delivered-runtime.json", ++ "sha256": "sha256:89681066e8fe139456163ebfafa3efe77ffae8534ea167219230b0a74367f129" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/native-refusal-probes.json", ++ "sha256": "sha256:673ccef965c820704dc310a1bf7832b583c30c3edfeb814528cf892555199172" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-full-row-hashes.json", ++ "sha256": "sha256:75cdd87eef79059ff3f484ed24ca69e285899cdf8d851cc4e472653c417eb0ee" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-full-row-hashes.json", ++ "sha256": "sha256:2156aad18f190384838c27e792706a156a7423391408afbc6107e6c28c5872d8" ++ } ++ ], ++ "later_ordinary_cluster_publication": { ++ "status": "failed_full_parent_row_preservation", ++ "changed_parent_hashes": 3, ++ "unchanged_task_hashes": 15, ++ "exact_changed_columns": "unknown_hash_only_baseline", ++ "original_parent_preimages": "unavailable_no_guessed_restoration", ++ "receipt": "the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/post-publication-full-row-hashes-FAILED.json", ++ "T07": "wait", ++ "RMGR_WP_0019_T06": "wait", ++ "followup": [ ++ "STATE-WP-0095-T09", ++ "STATE-WP-0095-T10" ++ ] ++ } ++} +--- a/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml ++++ b/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml +@@ -13,7 +13,7 @@ + # to match the standard version. CUST-WP-0050 T01. + + standard: "Repo Classification Standard" +-version: "1.0" ++version: "1.1" + canon_id: "canon-repo-classification" + + # category — exactly 1 required (§5) +@@ -24,6 +24,7 @@ + - tooling + - product + - business ++ - publication + + # domain / secondary_domains — primary exactly 1; secondaries 0..n (§6) + domains: +@@ -85,6 +86,8 @@ + - traceability + - documentation + - decision-support ++ - history ++ - participation + platform_and_operations: + - platform + - deployment +--- a/api/services/historical_projection.py ++++ b/api/services/historical_projection.py +@@ -193,3 +193,46 @@ + raise ValueError("Historical source binding changed: " + entry["path"]) + result.append((entry, data.decode("utf-8"))) + return result ++ ++ ++# Only the three existing full-task Cluster bindings have immutable DB rows. ++# Other historical views retain their existing compatibility behavior. ++FROZEN_CLUSTER_BINDINGS = tuple( ++ entry for entry in BINDINGS ++ if entry["repo"] == "railiance-cluster" and "tasks" in entry ++) ++ ++ ++def frozen_cluster_binding(binding): ++ """Validate a claimed frozen binding; unrelated requests remain ordinary.""" ++ matches = [entry for entry in FROZEN_CLUSTER_BINDINGS ++ if str(binding["workplan_id"]) == entry["uuid"] ++ or (binding["repo_slug"] == "railiance-cluster" ++ and (binding["relative_path"] == entry["path"] ++ or binding["filename"] == Path(entry["path"]).name))] ++ if not matches: ++ return None ++ if len(matches) != 1: ++ raise ValueError("frozen historical binding is ambiguous") ++ entry = matches[0] ++ expected = { ++ "workplan_id": entry["uuid"], "repo_slug": entry["repo"], ++ "filename": Path(entry["path"]).name, "relative_path": entry["path"], ++ "archived": True, "status": "finished", ++ } ++ if any((str(binding[key]) if key == "workplan_id" else binding[key]) != value ++ for key, value in expected.items()): ++ raise ValueError("frozen historical binding metadata changed") ++ return entry ++ ++ ++def validate_frozen_cluster_rows(entry, parent, tasks, repo_id): ++ """Receiving requires the exact existing terminal closure, never repair.""" ++ if (parent is None or str(parent.id) != entry["uuid"] ++ or repo_id is None or parent.repo_id != repo_id ++ or parent.status not in {"done", "finished", "archived", "completed"}): ++ raise ValueError("frozen historical parent missing, foreign or nonterminal") ++ local = {str(task.id): task for task in tasks if task.workplan_id == parent.id} ++ if any(task["uuid"] not in local or local[task["uuid"]].status not in {"done", "cancel"} ++ for task in entry["tasks"]): ++ raise ValueError("frozen historical task missing, foreign or nonterminal") +--- a/api/routers/workstreams.py ++++ b/api/routers/workstreams.py +@@ -16,6 +16,8 @@ + from api.events import EventEnvelope, publish_event + from api.models.managed_repo import ManagedRepo + from api.models.workplan import Workplan ++from api.models.task import Task ++from api.services.historical_projection import frozen_cluster_binding, validate_frozen_cluster_rows + from api.schemas.workplan import ( + WorkplanBindingsSync, + WorkplanCreate, +@@ -435,12 +437,42 @@ + session: AsyncSession = Depends(get_session), + ) -> dict[str, int]: + """Upsert workstation workplan file bindings for remote API index fallback.""" ++ # Preflight the complete batch before assigning even an ordinary row. ++ try: ++ frozen = {entry.workplan_id: bound for entry in body.bindings ++ if (bound := frozen_cluster_binding(entry.model_dump())) is not None} ++ except ValueError as exc: ++ raise HTTPException(status_code=409, detail=str(exc)) from exc ++ requested_ids = {entry.workplan_id for entry in body.bindings} ++ rows = await session.execute( ++ select(Workplan).where(Workplan.id.in_(requested_ids)) ++ .order_by(Workplan.id).with_for_update().execution_options(populate_existing=True) ++ ) ++ workplans = {workplan.id: workplan for workplan in rows.scalars().all()} ++ if frozen: ++ repo_rows = await session.execute( ++ select(ManagedRepo).where(ManagedRepo.slug == "railiance-cluster") ++ ) ++ repo = repo_rows.scalar_one_or_none() ++ task_rows = await session.execute( ++ select(Task).where(Task.workplan_id.in_(frozen)) ++ .order_by(Task.id).with_for_update().execution_options(populate_existing=True) ++ ) ++ tasks = task_rows.scalars().all() ++ try: ++ for workplan_id, bound in frozen.items(): ++ validate_frozen_cluster_rows( ++ bound, workplans.get(workplan_id), tasks, repo.id if repo else None ++ ) ++ except ValueError as exc: ++ raise HTTPException(status_code=409, detail=str(exc)) from exc + synced_at = datetime.now(timezone.utc) + updated = 0 +- requested_ids = {entry.workplan_id for entry in body.bindings} +- rows = await session.execute(select(Workplan).where(Workplan.id.in_(requested_ids))) +- workplans = {workplan.id: workplan for workplan in rows.scalars().all()} ++ preserved = 0 + for entry in body.bindings: ++ if entry.workplan_id in frozen: ++ preserved += 1 ++ continue # No setters, including backing_synced_at/updated_at. + wp = workplans.get(entry.workplan_id) + if wp is None: + continue +@@ -449,9 +481,10 @@ + wp.backing_archived = entry.archived + wp.backing_synced_at = synced_at + updated += 1 +- await session.commit() +- _invalidate_workplan_index_cache() +- return {"updated": updated, "received": len(body.bindings)} ++ if updated: ++ await session.commit() ++ _invalidate_workplan_index_cache() ++ return {"updated": updated, "received": len(body.bindings), "preserved": preserved} + + + @router.post("/", status_code=status.HTTP_410_GONE) +--- a/scripts/consistency_check.py ++++ b/scripts/consistency_check.py +@@ -120,7 +120,9 @@ + resolve_topic_domain_slug, + ) + from api.services.lifecycle import should_activate_parent_for_active_tasks # noqa: E402 +-from api.services.historical_projection import BINDINGS as _HISTORICAL_BINDINGS # noqa: E402 ++from api.services.historical_projection import ( # noqa: E402 ++ BINDINGS as _HISTORICAL_BINDINGS, FROZEN_CLUSTER_BINDINGS, frozen_cluster_binding, ++) + from api.task_status import ( # noqa: E402 + CANONICAL_TASK_STATUSES, + OPEN_TASK_STATUSES, +@@ -318,7 +320,7 @@ + + # C-00 marks infrastructure/automation faults (API down, repo missing in DB). + # All other FAIL severities are assessment findings for follow-up. +-_AUTOMATION_ERROR_CHECKS: frozenset[str] = frozenset({"C-00"}) ++_AUTOMATION_ERROR_CHECKS: frozenset[str] = frozenset({"C-00", "C-44"}) + _NON_WORKPLAN_WORKPLAN_FILES: frozenset[str] = frozenset({"README.md"}) + + +@@ -2496,23 +2498,44 @@ + repo_dir: Path, + report: ConsistencyReport, + ) -> None: ++ if repo_slug == "railiance-cluster": ++ scanned_paths = {workplan_display_path(repo_dir, path) for path, _, _ in workplan_infos} ++ missing = [entry["path"] for entry in FROZEN_CLUSTER_BINDINGS ++ if entry["path"] not in scanned_paths] ++ if missing: ++ report.add(severity="FAIL", check_id="C-44", ++ message="Frozen binding batch refused: bound source missing from scan", ++ fixable=False) ++ return + bindings: list[dict[str, Any]] = [] + for wp_file, meta, _ in workplan_infos: + ws_id = str(meta.get("state_hub_workstream_id", "")).strip().strip('"') ++ archived = wp_file.parent.name == "archived" ++ file_status = normalise_workstream_status(str(meta.get("status", "")).strip()) ++ binding = { ++ "workplan_id": ws_id, "filename": wp_file.name, ++ "relative_path": workplan_display_path(repo_dir, wp_file), ++ "repo_slug": repo_slug, "archived": archived, ++ "status": file_status or None, ++ } ++ try: ++ frozen = frozen_cluster_binding(binding) ++ if frozen is not None: ++ # Verify exact source before omitting the immutable binding. ++ # This is safe even against a receiver predating the API guard. ++ from hashlib import sha256 ++ if (wp_file.is_symlink() or not wp_file.is_file() ++ or sha256(wp_file.read_bytes()).hexdigest() != frozen["sha256"]): ++ raise ValueError("frozen historical source missing or changed") ++ continue ++ except ValueError as exc: ++ report.add(severity="FAIL", check_id="C-44", ++ message=f"Frozen binding batch refused: {exc}", ++ file_path=str(wp_file), fixable=False) ++ return # Never send a partial batch after a frozen-source refusal. + if not ws_id: + continue +- archived = wp_file.parent.name == "archived" +- file_status = normalise_workstream_status(str(meta.get("status", "")).strip()) +- bindings.append( +- { +- "workplan_id": ws_id, +- "filename": wp_file.name, +- "relative_path": workplan_display_path(repo_dir, wp_file), +- "repo_slug": repo_slug, +- "archived": archived, +- "status": file_status or None, +- } +- ) ++ bindings.append(binding) + if not bindings: + return + result = _api_put(api_base, "/workplans/index/bindings", {"bindings": bindings}) +--- /dev/null ++++ b/tests/test_frozen_binding_guard_isolated.py +@@ -0,0 +1,143 @@ ++from pathlib import Path ++import ast, asyncio, importlib.util, types, uuid ++from dataclasses import dataclass, field ++from datetime import datetime, timezone ++import pytest ++ ++ROOT = Path(__file__).resolve().parents[1] ++spec=importlib.util.spec_from_file_location('historical',ROOT/'api/services/historical_projection.py') ++h=importlib.util.module_from_spec(spec);spec.loader.exec_module(h) ++B=h.FROZEN_CLUSTER_BINDINGS[0] ++def binding(**changes): ++ d=dict(workplan_id=uuid.UUID(B['uuid']),repo_slug=B['repo'],filename=Path(B['path']).name,relative_path=B['path'],archived=True,status='finished');d.update(changes);return d ++class Obj: ++ def __init__(self,**kw):self.__dict__.update(kw);self.writes=[] ++ def __setattr__(self,k,v): ++ if 'writes' in self.__dict__:self.writes.append(k) ++ object.__setattr__(self,k,v) ++class Entry(Obj): ++ def model_dump(self):return {k:v for k,v in self.__dict__.items() if k!='writes'} ++class Col: ++ def in_(self,x):return self ++ def __eq__(self,x):return self ++class Query: ++ def __init__(self):self.locked=False;self.refreshed=False;self.ordered=False ++ def where(self,*a):return self ++ def order_by(self,*a):self.ordered=True;return self ++ def with_for_update(self):self.locked=True;return self ++ def execution_options(self,**kw):self.refreshed=kw.get("populate_existing") is True;return self ++class Model: id=Col();slug=Col();workplan_id=Col() ++class Result: ++ def __init__(self,values):self.values=values ++ def scalars(self):return self ++ def all(self):return self.values ++ def scalar_one_or_none(self):return self.values[0] if self.values else None ++class Session: ++ def __init__(self,rows):self.results=iter(rows);self.commits=0;self.queries=[] ++ async def execute(self,q):self.queries.append(q);return Result(next(self.results)) ++ async def commit(self):self.commits+=1 ++class HTTPException(Exception): ++ def __init__(self,status_code,detail):self.status_code=status_code;super().__init__(detail) ++def extracted(path,name,extra): ++ tree=ast.parse((ROOT/path).read_text());node=next(n for n in tree.body if isinstance(n,(ast.FunctionDef,ast.AsyncFunctionDef)) and n.name==name) ++ node.decorator_list=[];node.returns=None ++ for a in node.args.args + node.args.kwonlyargs:a.annotation=None ++ node.args.defaults=[] ++ ns=dict(Path=Path,datetime=datetime,timezone=timezone,**extra) ++ exec(compile(ast.Module(body=[node],type_ignores=[]),str(path),'exec'),ns);return ns[name] ++def actual_report(): ++ tree=ast.parse((ROOT/'scripts/consistency_check.py').read_text()) ++ nodes=[n for n in tree.body if (isinstance(n,ast.ClassDef) and n.name in {'Issue','ConsistencyReport'}) or (isinstance(n,ast.AnnAssign) and isinstance(n.target,ast.Name) and n.target.id=='_AUTOMATION_ERROR_CHECKS')] ++ ns=dict(dataclass=dataclass,field=field,_AUTOMATION_ERROR_CHECKS=frozenset({'C-00','C-44'})) ++ exec(compile(ast.Module(body=nodes,type_ignores=[]),'actual_report','exec'),ns) ++ return ns['ConsistencyReport']('railiance-cluster','/unused') ++ ++def closure(): ++ repo=Obj(id=uuid.uuid4());parent=Obj(id=uuid.UUID(B['uuid']),repo_id=repo.id,status='finished') ++ tasks=[Obj(id=uuid.UUID(t['uuid']),workplan_id=parent.id,status='done') for t in B['tasks']] ++ return repo,parent,tasks ++async def run_api(entries,repo,parent,tasks,ordinary=None): ++ rows=([parent] if parent else [])+([ordinary] if ordinary else []) ++ session=Session([rows,[repo] if repo else [],tasks]) ++ fn=extracted('api/routers/workstreams.py','sync_workplan_bindings',dict(frozen_cluster_binding=h.frozen_cluster_binding,validate_frozen_cluster_rows=h.validate_frozen_cluster_rows,HTTPException=HTTPException,select=lambda *_:Query(),Workplan=Model,Task=Model,ManagedRepo=Model,_invalidate_workplan_index_cache=lambda:None)) ++ result=await fn(Obj(bindings=[Entry(**e) for e in entries]),session) ++ return result,session ++@pytest.mark.parametrize('change',[dict(repo_slug='foreign'),dict(workplan_id=uuid.uuid4()),dict(relative_path='workplans/other.md'),dict(filename='other.md'),dict(archived=False),dict(status='active')]) ++def test_metadata_refuses(change): ++ with pytest.raises(ValueError):h.frozen_cluster_binding(binding(**change)) ++def test_unrelated_binding_unchanged():assert h.frozen_cluster_binding(binding(workplan_id=uuid.uuid4(),filename='ordinary.md',relative_path='workplans/ordinary.md',repo_slug='other')) is None ++@pytest.mark.parametrize('fault',['parent_missing','foreign_repo','parent_active','task_missing','task_active','task_foreign','repo_missing']) ++def test_mixed_batch_refusal_precedes_all_writes(fault): ++ repo,parent,tasks=closure();ordinary=Obj(id=uuid.uuid4()) ++ if fault=='repo_missing':repo=None ++ if fault=='parent_missing':parent=None ++ if fault=='foreign_repo':parent.repo_id=uuid.uuid4() ++ if fault=='parent_active':parent.status='active' ++ if fault=='task_missing':tasks.pop() ++ if fault=='task_active':tasks[0].status='progress' ++ if fault=='task_foreign':tasks[0].workplan_id=uuid.uuid4() ++ if parent:parent.writes.clear() ++ for t in tasks:t.writes.clear() ++ normal=binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md') ++ with pytest.raises(HTTPException):asyncio.run(run_api([normal,binding()],repo,parent,tasks,ordinary)) ++ assert ordinary.writes==[] ++ if parent:assert parent.writes==[] ++ assert all(t.writes==[] for t in tasks) ++def test_exact_frozen_no_setters_or_commit(): ++ repo,parent,tasks=closure();result,session=asyncio.run(run_api([binding()],repo,parent,tasks)) ++ assert result==dict(updated=0,received=1,preserved=1);assert session.commits==0;assert parent.writes==[] ++ assert all(q.locked and q.refreshed and q.ordered for q in (session.queries[0],session.queries[2])) ++def test_mixed_valid_preserves_frozen_and_updates_ordinary(): ++ repo,parent,tasks=closure();ordinary=Obj(id=uuid.uuid4()) ++ normal=binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md') ++ result,session=asyncio.run(run_api([normal,binding()],repo,parent,tasks,ordinary)) ++ assert result==dict(updated=1,received=2,preserved=1);assert session.commits==1;assert parent.writes==[] ++ assert ordinary.writes==['backing_filename','backing_relative_path','backing_archived','backing_synced_at'] ++@pytest.mark.parametrize('tamper',['metadata','bytes','symlink','missing_scan','missing_uuid','valid']) ++@pytest.mark.parametrize("bound_index", range(3)) ++def test_cli_exact_source_omission_or_whole_batch_refusal(tmp_path,tamper,bound_index): ++ bound=h.FROZEN_CLUSTER_BINDINGS[bound_index] ++ import shutil ++ infos=[] ++ for entry in h.FROZEN_CLUSTER_BINDINGS: ++ target=tmp_path/entry['path'];target.parent.mkdir(parents=True,exist_ok=True) ++ shutil.copyfile(Path('/home/worsch/railiance-cluster')/entry['path'],target) ++ infos.append((target,dict(state_hub_workstream_id=entry['uuid'],status='finished'),'')) ++ path=tmp_path/bound['path'] ++ meta=dict(state_hub_workstream_id=bound['uuid'],status='finished') ++ if tamper=='metadata':meta['status']='active' ++ if tamper=='missing_uuid':meta.pop('state_hub_workstream_id') ++ if tamper=='bytes':path.write_text(path.read_text()+'\n') ++ if tamper=='symlink':path.unlink();path.symlink_to(Path('/home/worsch/railiance-cluster')/bound['path']) ++ ordinary=tmp_path/'workplans/ordinary.md';ordinary.write_text('ordinary') ++ if tamper=='missing_scan':infos.pop(bound_index) ++ else:infos[bound_index]=(path,meta,'') ++ calls=[];report=actual_report() ++ fn=extracted('scripts/consistency_check.py','_sync_workplan_bindings',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS,frozen_cluster_binding=h.frozen_cluster_binding,normalise_workstream_status=lambda s:s,workplan_display_path=lambda root,p:p.relative_to(root).as_posix(),_api_put=lambda *a:calls.append(a) or dict(updated=1,received=1))) ++ fn('http://unused','railiance-cluster',[(ordinary,dict(state_hub_workstream_id=str(uuid.uuid4()),status='active'),''),*infos],tmp_path,report) ++ if tamper=='valid':assert len(calls)==1 and len(calls[0][2]['bindings'])==1 ++ else:assert calls==[] and report.failures[0].check_id=='C-44' and report.automation_errors ++ ++ ++def test_api_tampered_mixed_batch_refuses_before_queries_or_assignments(): ++ repo,parent,tasks=closure();ordinary=Obj(id=uuid.uuid4()) ++ with pytest.raises(HTTPException): ++ asyncio.run(run_api([binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md'),binding(archived=False)],repo,parent,tasks,ordinary)) ++ assert ordinary.writes==[] and parent.writes==[] ++ ++ ++def test_foreign_repo_same_filename_path_is_ordinary(): ++ assert h.frozen_cluster_binding(binding(workplan_id=uuid.uuid4(),repo_slug='foreign')) is None ++ ++ ++@pytest.mark.parametrize('entry',h.FROZEN_CLUSTER_BINDINGS) ++def test_all_three_registry_bindings_are_guarded(entry): ++ good=dict(workplan_id=entry['uuid'],repo_slug=entry['repo'],filename=Path(entry['path']).name,relative_path=entry['path'],archived=True,status='finished') ++ assert h.frozen_cluster_binding(good) is entry ++ with pytest.raises(ValueError):h.frozen_cluster_binding(dict(good,status='active')) ++ ++ ++def test_real_cli_exit_criterion_is_nonzero_including_remote_all(): ++ report=actual_report();report.add(severity='FAIL',check_id='C-44',message='refused') ++ fn=extracted('scripts/consistency_check.py','consistency_exit_code',{}) ++ assert fn([report])==1 and fn([report],remote_all=True)==1 diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/validation.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/validation.json new file mode 100644 index 00000000..b6e64947 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r3/validation.json @@ -0,0 +1,17 @@ +{ + "isolated_regression_tests": 40, + "pytest_exit": 0, + "pytest_command": "python3 -m pytest --noconftest -q state-hub/tests/test_frozen_binding_guard_isolated.py", + "normal_make_render_exit": 0, + "render_complete_tracked_chart": true, + "rendered_objects": 8, + "runtime_images": [ + "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", + "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015" + ], + "classification_check_exit": 0, + "no_DB_or_cluster_access": true, + "no_canonical_mutations": true, + "original_r1_r2_receipts_preserved": true +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/REVIEW.md b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/REVIEW.md new file mode 100644 index 00000000..680fc1be --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/REVIEW.md @@ -0,0 +1,43 @@ +# Immutable ordinary-binding repair, source revision 4 + +Canonical base: 086d79a2736e34b916bd7de3c8a65e2ea38a3d64. The ten-path patch includes the previously reviewed six-path followthrough and four guard/test paths. Originals and the actual failed publication receipt remain untouched. + +The approved revision-4 receiving rollout preserved all 18 rows at its verification stage. A later ordinary Cluster publication changed exactly three parent full-row hashes; all 15 task hashes remained unchanged. The binding endpoint assigns a new backing sync timestamp unconditionally, providing a concrete mutation path. Hash-only snapshots do not establish the exact changed columns or supply original preimages. This patch does not restore them. + +Only the three existing task-bound railiance-cluster registry entries receive this guard. The CLI requires all three scan members, validates their exact source bytes and metadata, and omits valid immutable bindings before contacting any receiver. A refused batch becomes C-44 FAIL and returns a nonzero exit even in remote-all mode. The API independently validates all request metadata before any assignment, locks and refreshes parent/task rows, checks actual repo ownership and existing terminal required-bound tasks, and skips every setter for valid frozen rows. Additional children retain existing Forge receiving semantics; no new child-set invariant is introduced. The ordinary binding response retains updated/received and adds preserved. Frozen-only requests perform no commit or cache invalidation. Existing get_session uses an async-session context manager, which closes the read transaction and releases locks on exit. + +Validation: 75 isolated tests pass with --noconftest. Tests extract actual source functions/classes without importing owner app configuration or opening a DB. They verify all three exact sources, missing scan member/UUID, changed source and metadata, foreign names, complete mixed-batch refusal, API ownership and terminal-state refusal, no frozen setters/commit, ordinary compatibility, lock/refresh query intent and actual CLI exit criteria. The full tracked chart plus overlay passes normal Make render/classification check: eight objects, three unchanged digest-qualified images. No concurrency/native DB behavior is claimed by these source tests. + +T06 remains done for the approved delivery at its verification stage; T07 remains wait for failed whole-pipeline preservation. T09 is progress until source publication. T10 is wait/needs_human for a separate exact new runtime approval. New task UUIDs are intentionally unallocated pending root review and normal managed allocation. Preserve the original dirty brief/index/weekly evidence and index state. + +## Next runtime proposal preparation + +After reviewed source integration and normal publication, obtain normal CI status and resolve the resulting exact immutable image manifest. Inspect selected delivered source hashes, including historical_projection.py, workstreams.py and the installed closure; do not reuse the prior main-51bc70d runtime for the new API guard. Prepare digest-qualified packaged-chart reuse-values server dry-run, compare intended object identities/spec hashes, collect fresh release/headroom/schema and full-row hash baseline, then record a new scoped proposal and supervisor disposition. Current known prior baseline was Helm 76/schema c4d5e6f7a8b9; refresh rather than assume it remains current. CI revision, new image digest, final chart package hash and exact execution digest remain unresolved until publication. + +No new rollout is authorized by the prior revision-4 approval. A new rollout must retain migration closure and rollback limits: Helm rollback does not downgrade schema, and timeout/atomic does not necessarily stop an active hook Job. Cancellation may address only the exact owned trial Job UID/image after identity verification. Unknown migration recovery or connection/lock quiescence stops for attended owner handling; no credential grant or invented DB-session mapping. EUR cost and risk are unknown until reviewed; no bounded-outage assertion. New baseline preservation and rollback-only refusal probes prove the new stage, never restoration of the unavailable original parent preimages. T07/RMGR19T06 cannot close on source publication alone. + + +Revision 4 retains frozen revision 3 and adds central consistency repair-batch +refusal. The same helper checks exact registered parent/task UUIDs and source +paths before check_repo can issue an index binding PUT and again before +fix_repo can invoke reconcile, push, metadata registration or individual fixes. +A protected repair finding refuses the complete batch with C-44, including +when an ordinary fix appears first. Existing C-44 refusal is not duplicated. +This prevents generic C-04/C-10/C-22 and related fixes from modifying frozen +rows through other APIs. It does not disable ordinary checks or silently +accept source/DB drift: owner review and the waiting live records remain. +Tests invoke the actual fix engine with mixed fix lists for all three parents, +exercise bound task/source contexts, and verify unchanged ordinary behavior. +API preservation/refusal simulations now exercise all three parent closures. + + +The post-publication client regression can compare fresh CURRENT full-row hashes +before/after the fixed normal Cluster deep consistency and fast projection. +That is a separate stage from the preserved original failure; equal fresh +hashes do not restore or accept the unavailable original parent preimages. +For T10 prefer the exact originally approved b5c chart archive with only the +newly verified immutable guard image changed, after artifact identity and +server dry-run comparison. Do not infer authority to roll out the source chart's +v1.1 classification ConfigMap or other chart metadata changes. Source Make +pins continue to declare the actually delivered 51bc70d@c802 artifact until a +new exact runtime is approved and verified. diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/manifest.json b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/manifest.json new file mode 100644 index 00000000..8933cca7 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/manifest.json @@ -0,0 +1,77 @@ +{ + "schema": "statehub.source-owner-followthrough.v1", + "revision": 4, + "status": "isolated_source_candidate_not_published", + "base_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", + "files": [ + { + "path": "workplans/STATE-WP-0095-historical-source-receiving.md", + "action": "modify", + "base_sha256": "ef10359cc5974283b5bdbfc602adf14e3c24362adf00640698d2937c6538483a", + "candidate_sha256": "5671189ee87f2d4dff08979f6243e5eb7888b140955899642a74d569496b71c7" + }, + { + "path": "Makefile", + "action": "modify", + "base_sha256": "5584ef94071044cca8215141ecd8aee8d6ae5cfe73e8f5719358c680eb8af0d4", + "candidate_sha256": "c5b70e8d4b8ae70380e0ade1c35f9e7fe80038de10080a4723b21ef07a98001e" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/Chart.yaml", + "action": "modify", + "base_sha256": "7fe02b390ffd2ba4ce6f82bd62df900826826401e31bdc2138dd5bf36b51a6ae", + "candidate_sha256": "61a989fcf277d05ffbececb7332ee3ca1d8f369c72f960ab1bc4dbc6815536b8" + }, + { + "path": "deploy/railiance/apps/helm/state-hub-values.yaml", + "action": "modify", + "base_sha256": "f7cc0e9968598f6ce0faa0d56f62d6817845c3570c18b6ac39ed6561b952b197", + "candidate_sha256": "b51296fc937450f4ecf0ae43243262a631b551389c0d09c8bfe58fa10e2f3ee9" + }, + { + "path": "docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json", + "action": "add", + "base_sha256": null, + "candidate_sha256": "fb0b59ba3558e084fb4ae2c36c556f3cfbb2a889f6fd86d4e803c52f483b26fd" + }, + { + "path": "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml", + "action": "modify", + "base_sha256": "9c36fbd5308b19695f9f05d86f8e315bbeccdd267575ce4e2fccf6fb2ea82e32", + "candidate_sha256": "f53c651bb4329c09c04c66f906e2462fe4339ab51ef0ec3da9f942c3016f5bed" + }, + { + "path": "api/services/historical_projection.py", + "action": "modify", + "base_sha256": "e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", + "candidate_sha256": "e24e57c58f4aaa4ec9d9bf088009ac69006e53f75b9477ee53dc26c4e850b975" + }, + { + "path": "api/routers/workstreams.py", + "action": "modify", + "base_sha256": "61e7a0ab638b155e9e0b2a72fe8e0ddd2edf5bc6c2d3ca7f6c3ff51e8868910b", + "candidate_sha256": "9c9bf4376dd323b52109912bb3f615949e89ac02c8b88b4720f569f8565ad1d6" + }, + { + "path": "scripts/consistency_check.py", + "action": "modify", + "base_sha256": "1827921c79b476bee823c2ddf9896aeb83f3efca6548427e17b3244216bbc6b5", + "candidate_sha256": "c2dc6e1b930198ae8352de05573fc6d3b58709996a8499cec4e3f5217546c543" + }, + { + "path": "tests/test_frozen_binding_guard_isolated.py", + "action": "add", + "base_sha256": null, + "candidate_sha256": "17dfc4733dce5da4195556d1b0b87eca4783f8760b40c74220c331edfcef32f5" + } + ], + "patch_sha256": "sha256:c06bc0a3e667e9ae6407f60887ba8feddc66d19a2c21dfc86d1bbe3718361708", + "T06": "done_verified_approved_rollout_at_stage", + "T07": "wait_actual_later_parent_row_preservation_failure", + "T08": "done_source_repair_pending_publication", + "T09": "progress_until_source_published", + "T10": "wait_needs_human_new_runtime", + "live_effects": false, + "original_parent_preimages": "unknown_do_not_restore_guesses", + "managed_UUIDs": "T09_T10_not_allocated_pending_review" +} diff --git a/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/state-hub-followthrough.patch b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/state-hub-followthrough.patch new file mode 100644 index 00000000..99884afa --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r4/state-hub-followthrough.patch @@ -0,0 +1,756 @@ +--- a/workplans/STATE-WP-0095-historical-source-receiving.md ++++ b/workplans/STATE-WP-0095-historical-source-receiving.md +@@ -199,27 +199,37 @@ + + ```task + id: STATE-WP-0095-T06 +-status: wait ++status: done + priority: high + depends_on: [STATE-WP-0095-T05] +-needs_human: true +-intervention_note: "Review exact committed source, CI image and owner deployment proposal before rollout." ++needs_human: false ++intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention." + state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4" + ``` + +-Use the normal CI image and Helm owner path after exact revision/image pin, +-fresh headroom and reviewed dry-run/diff. API/MCP currently run main-7d99282; +-Helm revision75 is the observed rollback baseline, requiring freshness before +-execution. No deployment assent exists for this successor. Verify delivered +-module hash, health, both readiness checks and expected migrated schema +-c4d5e6f7a8b9; verify exact +-three parents/15 tasks and rejection behavior without committed probe changes. +-Only the verified return unblocks RMGR-WP-0019-T05. The full current image also carries inbox-feed migration c4d5e6f7a8b9, +-although this receiver patch adds none. Helm rollback leaves that migration in +-place; unrelated migration acceptance and old-image compatibility need normal +-owner evidence before rollout approval. Do not run destructive default local +-DB tests; use only exact disposable DB containment. Costs and monetary risk are +-unknown pending exact rollout proposal; no zero-cost or autonomy grant is inferred. ++Completed 2026-10-17 on the founder's approval of exact revision 4 of stable ++proposal STATE-WP-0095-T06-cluster-frozen-receiving-r1 (decision ++ e3798419-8aa9-5425-8c08-20df6bd82a19; approved digest ++ sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32). ++The ordinary owner Helm path delivered revision 76, runtime source 51bc70d and ++exact image manifest sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015. ++API/MCP Ready and primary/railiance01 health report expected schema ++c4d5e6f7a8b9; delivered historical module sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2 matches. ++All three parent and 15 task full DB row hashes/columns/UUIDs remained unchanged. ++Five actual native probes passed: preserve, actual foreign-parent holder move, ++nonterminal task, nonterminal parent and missing task. Every probe rolled back; ++a fresh transaction confirmed complete row restoration. The foreign-holder ++case precisely verified historical binding refusal, not the separate global ++UUID-holder creation branch. No committed probe writes were made. ++ ++Evidence: [verified receiving rollout](../docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json). ++This actual receiving return satisfies the prerequisite of RMGR-WP-0019-T05. ++Normal cluster publication and its immutable-source/row return remain T07; ++source readiness and this deployment do not count that later acceptance twice. ++The additive inbox migration is retained; Helm rollback does not downgrade ++schema or stop an active migration Job. Original failures and exact-UID recovery ++limits remain in the referenced approved packet. No TLS recovery/native private ++admission or new autonomy/cost grant is inferred. + + ## Verify ordinary cluster receiving and immutable historical rows + +@@ -256,6 +266,74 @@ + Command-fixture tests verify both requests and preserve other dry-run phases. + Evidence and original refusal remain in the Custodian cluster historical + publication packet, revisions/r2 and r3-readiness. This is completed source +-preparation; T06 remains the explicit native rollout gate. The runtime image ++preparation; T06 separately records the verified actual rollout. The runtime image + continues to be main-51bc70d; Makefile/workplan changes do not create a new + application image or native acceptance. ++ ++ ++T08 source repair refinement, 2026-10-17: the required normal Make render ++correctly refused a pre-existing generated chart classification copy at v1.0 ++after authoritative canon reached v1.1. Regenerated only through the existing ++scripts/sync_classification_allowed.py, preserving canon and strict check. ++The exact source diff is version 1.0→1.1 plus publication/history/participation. ++Normal Make rendering and the focused classification check passed in an ++isolated owner-layout copy. The original refusal is retained in the Custodian ++owner-followthrough-r1 validation receipt. This is source repair only: no live ++ConfigMap apply, new runtime image, policy relaxation or T07 completion. ++ ++ ++T07 actual ordinary-publication return, 2026-10-17: cluster owner deep consistency ++and normal fast projection completed, exact archived source bytes and all 15 ++historical task full-row hashes remained unchanged. The later full-row check ++FAILED for exactly the three parent rows. The earlier approved T06 rollout had ++preserved all 18 rows at its verification stage; that success is not the later ++whole-pipeline acceptance. The original snapshots retain hashes only, so exact ++changed columns and original field preimages are not established. No guessed ++DB repair or claim that all 18 original row hashes are restored is authorized. ++T07 and RMGR-WP-0019-T06 remain waiting pending T09/T10 and a reviewed return. ++ ++## Guard immutable historical index bindings in CLI and receiving API ++ ++```task ++id: STATE-WP-0095-T09 ++status: progress ++priority: high ++depends_on: [STATE-WP-0095-T06] ++``` ++ ++Prepare and publish the narrowly scoped ordinary-binding repair for the three ++existing task-bound Cluster registry entries. The CLI verifies exact frozen ++source metadata/bytes and omits those bindings, including on old receivers. ++The API independently validates the complete batch before any mutation, checks ++actual repo ownership and the existing terminal parent/task closure, and ++preserves valid frozen rows without field or timestamp assignments. Invalid ++metadata, missing/foreign/nonterminal rows refuse the batch. Unknown ordinary ++bindings retain existing behavior; historical IDs, UUIDs and source bytes stay ++unchanged. Isolated regression tests and normal owner rendering are required. ++Mark done only after reviewed source publication and normal owner projection; ++source preparation does not establish delivered runtime or T07 acceptance. ++Central check/fix engines also refuse the complete repair batch if any fixable ++context touches an exact registered parent/task UUID or frozen source path. ++This runs before index bindings and before reconcile/push/individual fixes; ++ordinary earlier fixes cannot partially execute before a later frozen refusal. ++ ++## Deliver and verify the ordinary-binding guard successor ++ ++```task ++id: STATE-WP-0095-T10 ++status: wait ++priority: high ++depends_on: [STATE-WP-0095-T09] ++needs_human: true ++intervention_note: "New API runtime requires its own exact image, reviewed preflight and scoped rollout approval; prior revision-4 approval does not cover it." ++``` ++ ++After source publication, resolve normal CI status and immutable image identity, ++verify delivered guard source closure, prepare exact chart/reuse-values dry-run, ++full closure/headroom/schema baseline and conditional recovery. Retain additive ++migration and active-Job cancellation limitations; no Helm schema downgrade or ++bounded-outage claim. Obtain scoped approval before live rollout. Then verify ++new full-row baseline preservation and actual rollback-only refusal probes, ++and only then return to T07 ordinary-pipeline acceptance. This prevents future ++binding mutation; it does not reconstruct or restore original parent hashes. ++No production changes, new autonomy or credential authority are granted here. +--- a/Makefile ++++ b/Makefile +@@ -9,7 +9,7 @@ + RAILIANCE_STATE_HUB_NAMESPACE ?= state-hub + RAILIANCE_STATE_HUB_CHART ?= deploy/railiance/apps/charts/state-hub + RAILIANCE_STATE_HUB_VALUES ?= deploy/railiance/apps/helm/state-hub-values.yaml +-RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-0747b4d ++RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015 + RAILIANCE_STATE_HUB_PLATFORM_DIR ?= deploy/railiance/platform + RAILIANCE_STATE_HUB_APP_MANIFESTS ?= deploy/railiance/apps/manifests + # Codex/WSL non-login shells may not source ~/.profile; keep uv discoverable. +--- a/deploy/railiance/apps/charts/state-hub/Chart.yaml ++++ b/deploy/railiance/apps/charts/state-hub/Chart.yaml +@@ -3,4 +3,4 @@ + description: State Hub API service for private Railiance operation + type: application + version: 0.1.0 +-appVersion: "main-5dd3df9" ++appVersion: "main-51bc70d" +--- a/deploy/railiance/apps/helm/state-hub-values.yaml ++++ b/deploy/railiance/apps/helm/state-hub-values.yaml +@@ -6,7 +6,7 @@ + + image: + repository: forgejo.coulomb.social/coulomb/state-hub +- tag: "main-0747b4d" ++ tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015" + + ingress: + enabled: false +--- /dev/null ++++ b/docs/evidence/2026-10-17-cluster-historical-receiving-rollout.json +@@ -0,0 +1,116 @@ ++{ ++ "schema": "statehub.cluster-historical-receiving-delivered.v1", ++ "status": "verified", ++ "proposal_id": "STATE-WP-0095-T06-cluster-frozen-receiving-r1", ++ "approved_revision": 4, ++ "approved_digest_sha256": "sha256:63a02e8296d126c0158b7eb2c4ce79c7eeb6f8d6f61298d473c1065c7557ba32", ++ "approval_decision_id": "e3798419-8aa9-5425-8c08-20df6bd82a19", ++ "source_revision": "51bc70d1ff8f25450a8650d8e50caf053093a02e", ++ "readiness_source_revision": "086d79a2736e34b916bd7de3c8a65e2ea38a3d64", ++ "helm_revision": 76, ++ "image_reference": "forgejo.coulomb.social/coulomb/state-hub:main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015", ++ "expected_schema": "c4d5e6f7a8b9", ++ "health": { ++ "status": "ok", ++ "instance_role": "primary", ++ "instance_label": "railiance01", ++ "schema": { ++ "status": "ok", ++ "applied": "c4d5e6f7a8b9", ++ "expected": "c4d5e6f7a8b9" ++ } ++ }, ++ "pods_ready": true, ++ "historical_module_sha256": "sha256:e3c4bf965764b32a6df5d61c71272ee22569df9a9ddae2a3fc3df97ec554e0b2", ++ "historical_preservation_verified": true, ++ "parents": 3, ++ "tasks": 15, ++ "full_historical_rows_equal": true, ++ "native_rollback_probes": { ++ "foreign-holder": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "missing-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-parent": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "nonterminal-task": { ++ "full_historical_rows_restored": true, ++ "historical_binding_refused": true, ++ "native_outcome_status": "refused", ++ "rolled_back": true, ++ "status": "passed" ++ }, ++ "preserve": { ++ "full_historical_rows_equal": true, ++ "full_historical_rows_restored": true, ++ "native_outcome_status": "applied", ++ "rolled_back": true, ++ "status": "passed" ++ } ++ }, ++ "native_probe_commits": 0, ++ "fresh_transaction_full_rows_equal": true, ++ "source_receiving_gate_delivered": true, ++ "normal_cluster_publication_verified": false, ++ "registrar_admission_or_cluster_publication_not_inferred": true, ++ "authority_limits": [ ++ "Rollback probe SQL effects rolled back; foreign-holder case is historical-binding refusal, not separate global holder branch.", ++ "Actual historical native receiving is verified; owner-native TLS/private admission remains unresolved.", ++ "Migration schema remains additive; Helm rollback does not downgrade schema or stop an active hook Job." ++ ], ++ "evidence": [ ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/approval-decision.json", ++ "sha256": "sha256:b51554c004daa882f3709f67bdae7f2e939d611120383e61be04ec6969878134" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/helm-return.json", ++ "sha256": "sha256:61727deacf22d37da2d28cbaf04f5a3ddee0f9ff7571c77f1e85dba97963b572" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/delivered-runtime.json", ++ "sha256": "sha256:89681066e8fe139456163ebfafa3efe77ffae8534ea167219230b0a74367f129" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/native-refusal-probes.json", ++ "sha256": "sha256:673ccef965c820704dc310a1bf7832b583c30c3edfeb814528cf892555199172" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/before-full-row-hashes.json", ++ "sha256": "sha256:75cdd87eef79059ff3f484ed24ca69e285899cdf8d851cc4e472653c417eb0ee" ++ }, ++ { ++ "path": "docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/after-full-row-hashes.json", ++ "sha256": "sha256:2156aad18f190384838c27e792706a156a7423391408afbc6107e6c28c5872d8" ++ } ++ ], ++ "later_ordinary_cluster_publication": { ++ "status": "failed_full_parent_row_preservation", ++ "changed_parent_hashes": 3, ++ "unchanged_task_hashes": 15, ++ "exact_changed_columns": "unknown_hash_only_baseline", ++ "original_parent_preimages": "unavailable_no_guessed_restoration", ++ "receipt": "the-custodian/docs/changes/netkingdom-target-2026-10-17/receiver-rollout-r4/post-publication-full-row-hashes-FAILED.json", ++ "T07": "wait", ++ "RMGR_WP_0019_T06": "wait", ++ "followup": [ ++ "STATE-WP-0095-T09", ++ "STATE-WP-0095-T10" ++ ] ++ } ++} +--- a/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml ++++ b/deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml +@@ -13,7 +13,7 @@ + # to match the standard version. CUST-WP-0050 T01. + + standard: "Repo Classification Standard" +-version: "1.0" ++version: "1.1" + canon_id: "canon-repo-classification" + + # category — exactly 1 required (§5) +@@ -24,6 +24,7 @@ + - tooling + - product + - business ++ - publication + + # domain / secondary_domains — primary exactly 1; secondaries 0..n (§6) + domains: +@@ -85,6 +86,8 @@ + - traceability + - documentation + - decision-support ++ - history ++ - participation + platform_and_operations: + - platform + - deployment +--- a/api/services/historical_projection.py ++++ b/api/services/historical_projection.py +@@ -193,3 +193,46 @@ + raise ValueError("Historical source binding changed: " + entry["path"]) + result.append((entry, data.decode("utf-8"))) + return result ++ ++ ++# Only the three existing full-task Cluster bindings have immutable DB rows. ++# Other historical views retain their existing compatibility behavior. ++FROZEN_CLUSTER_BINDINGS = tuple( ++ entry for entry in BINDINGS ++ if entry["repo"] == "railiance-cluster" and "tasks" in entry ++) ++ ++ ++def frozen_cluster_binding(binding): ++ """Validate a claimed frozen binding; unrelated requests remain ordinary.""" ++ matches = [entry for entry in FROZEN_CLUSTER_BINDINGS ++ if str(binding["workplan_id"]) == entry["uuid"] ++ or (binding["repo_slug"] == "railiance-cluster" ++ and (binding["relative_path"] == entry["path"] ++ or binding["filename"] == Path(entry["path"]).name))] ++ if not matches: ++ return None ++ if len(matches) != 1: ++ raise ValueError("frozen historical binding is ambiguous") ++ entry = matches[0] ++ expected = { ++ "workplan_id": entry["uuid"], "repo_slug": entry["repo"], ++ "filename": Path(entry["path"]).name, "relative_path": entry["path"], ++ "archived": True, "status": "finished", ++ } ++ if any((str(binding[key]) if key == "workplan_id" else binding[key]) != value ++ for key, value in expected.items()): ++ raise ValueError("frozen historical binding metadata changed") ++ return entry ++ ++ ++def validate_frozen_cluster_rows(entry, parent, tasks, repo_id): ++ """Receiving requires the exact existing terminal closure, never repair.""" ++ if (parent is None or str(parent.id) != entry["uuid"] ++ or repo_id is None or parent.repo_id != repo_id ++ or parent.status not in {"done", "finished", "archived", "completed"}): ++ raise ValueError("frozen historical parent missing, foreign or nonterminal") ++ local = {str(task.id): task for task in tasks if task.workplan_id == parent.id} ++ if any(task["uuid"] not in local or local[task["uuid"]].status not in {"done", "cancel"} ++ for task in entry["tasks"]): ++ raise ValueError("frozen historical task missing, foreign or nonterminal") +--- a/api/routers/workstreams.py ++++ b/api/routers/workstreams.py +@@ -16,6 +16,8 @@ + from api.events import EventEnvelope, publish_event + from api.models.managed_repo import ManagedRepo + from api.models.workplan import Workplan ++from api.models.task import Task ++from api.services.historical_projection import frozen_cluster_binding, validate_frozen_cluster_rows + from api.schemas.workplan import ( + WorkplanBindingsSync, + WorkplanCreate, +@@ -435,12 +437,42 @@ + session: AsyncSession = Depends(get_session), + ) -> dict[str, int]: + """Upsert workstation workplan file bindings for remote API index fallback.""" ++ # Preflight the complete batch before assigning even an ordinary row. ++ try: ++ frozen = {entry.workplan_id: bound for entry in body.bindings ++ if (bound := frozen_cluster_binding(entry.model_dump())) is not None} ++ except ValueError as exc: ++ raise HTTPException(status_code=409, detail=str(exc)) from exc ++ requested_ids = {entry.workplan_id for entry in body.bindings} ++ rows = await session.execute( ++ select(Workplan).where(Workplan.id.in_(requested_ids)) ++ .order_by(Workplan.id).with_for_update().execution_options(populate_existing=True) ++ ) ++ workplans = {workplan.id: workplan for workplan in rows.scalars().all()} ++ if frozen: ++ repo_rows = await session.execute( ++ select(ManagedRepo).where(ManagedRepo.slug == "railiance-cluster") ++ ) ++ repo = repo_rows.scalar_one_or_none() ++ task_rows = await session.execute( ++ select(Task).where(Task.workplan_id.in_(frozen)) ++ .order_by(Task.id).with_for_update().execution_options(populate_existing=True) ++ ) ++ tasks = task_rows.scalars().all() ++ try: ++ for workplan_id, bound in frozen.items(): ++ validate_frozen_cluster_rows( ++ bound, workplans.get(workplan_id), tasks, repo.id if repo else None ++ ) ++ except ValueError as exc: ++ raise HTTPException(status_code=409, detail=str(exc)) from exc + synced_at = datetime.now(timezone.utc) + updated = 0 +- requested_ids = {entry.workplan_id for entry in body.bindings} +- rows = await session.execute(select(Workplan).where(Workplan.id.in_(requested_ids))) +- workplans = {workplan.id: workplan for workplan in rows.scalars().all()} ++ preserved = 0 + for entry in body.bindings: ++ if entry.workplan_id in frozen: ++ preserved += 1 ++ continue # No setters, including backing_synced_at/updated_at. + wp = workplans.get(entry.workplan_id) + if wp is None: + continue +@@ -449,9 +481,10 @@ + wp.backing_archived = entry.archived + wp.backing_synced_at = synced_at + updated += 1 +- await session.commit() +- _invalidate_workplan_index_cache() +- return {"updated": updated, "received": len(body.bindings)} ++ if updated: ++ await session.commit() ++ _invalidate_workplan_index_cache() ++ return {"updated": updated, "received": len(body.bindings), "preserved": preserved} + + + @router.post("/", status_code=status.HTTP_410_GONE) +--- a/scripts/consistency_check.py ++++ b/scripts/consistency_check.py +@@ -120,7 +120,9 @@ + resolve_topic_domain_slug, + ) + from api.services.lifecycle import should_activate_parent_for_active_tasks # noqa: E402 +-from api.services.historical_projection import BINDINGS as _HISTORICAL_BINDINGS # noqa: E402 ++from api.services.historical_projection import ( # noqa: E402 ++ BINDINGS as _HISTORICAL_BINDINGS, FROZEN_CLUSTER_BINDINGS, frozen_cluster_binding, ++) + from api.task_status import ( # noqa: E402 + CANONICAL_TASK_STATUSES, + OPEN_TASK_STATUSES, +@@ -318,7 +320,7 @@ + + # C-00 marks infrastructure/automation faults (API down, repo missing in DB). + # All other FAIL severities are assessment findings for follow-up. +-_AUTOMATION_ERROR_CHECKS: frozenset[str] = frozenset({"C-00"}) ++_AUTOMATION_ERROR_CHECKS: frozenset[str] = frozenset({"C-00", "C-44"}) + _NON_WORKPLAN_WORKPLAN_FILES: frozenset[str] = frozenset({"README.md"}) + + +@@ -2484,6 +2486,8 @@ + _check_workplan_id_collisions(api_base, repo_slug, repo_dir, workplan_infos, report) + _check_scope_freshness(repo_dir, workplan_infos, report) + ++ if _refuse_frozen_fix_batch(report, repo_slug): ++ return report + _sync_workplan_bindings(api_base, repo_slug, workplan_infos, repo_dir, report) + + return report +@@ -2496,23 +2500,44 @@ + repo_dir: Path, + report: ConsistencyReport, + ) -> None: ++ if repo_slug == "railiance-cluster": ++ scanned_paths = {workplan_display_path(repo_dir, path) for path, _, _ in workplan_infos} ++ missing = [entry["path"] for entry in FROZEN_CLUSTER_BINDINGS ++ if entry["path"] not in scanned_paths] ++ if missing: ++ report.add(severity="FAIL", check_id="C-44", ++ message="Frozen binding batch refused: bound source missing from scan", ++ fixable=False) ++ return + bindings: list[dict[str, Any]] = [] + for wp_file, meta, _ in workplan_infos: + ws_id = str(meta.get("state_hub_workstream_id", "")).strip().strip('"') ++ archived = wp_file.parent.name == "archived" ++ file_status = normalise_workstream_status(str(meta.get("status", "")).strip()) ++ binding = { ++ "workplan_id": ws_id, "filename": wp_file.name, ++ "relative_path": workplan_display_path(repo_dir, wp_file), ++ "repo_slug": repo_slug, "archived": archived, ++ "status": file_status or None, ++ } ++ try: ++ frozen = frozen_cluster_binding(binding) ++ if frozen is not None: ++ # Verify exact source before omitting the immutable binding. ++ # This is safe even against a receiver predating the API guard. ++ from hashlib import sha256 ++ if (wp_file.is_symlink() or not wp_file.is_file() ++ or sha256(wp_file.read_bytes()).hexdigest() != frozen["sha256"]): ++ raise ValueError("frozen historical source missing or changed") ++ continue ++ except ValueError as exc: ++ report.add(severity="FAIL", check_id="C-44", ++ message=f"Frozen binding batch refused: {exc}", ++ file_path=str(wp_file), fixable=False) ++ return # Never send a partial batch after a frozen-source refusal. + if not ws_id: + continue +- archived = wp_file.parent.name == "archived" +- file_status = normalise_workstream_status(str(meta.get("status", "")).strip()) +- bindings.append( +- { +- "workplan_id": ws_id, +- "filename": wp_file.name, +- "relative_path": workplan_display_path(repo_dir, wp_file), +- "repo_slug": repo_slug, +- "archived": archived, +- "status": file_status or None, +- } +- ) ++ bindings.append(binding) + if not bindings: + return + result = _api_put(api_base, "/workplans/index/bindings", {"bindings": bindings}) +@@ -3610,6 +3635,44 @@ + # Fix engine + # --------------------------------------------------------------------------- + ++def _refuse_frozen_fix_batch(report: ConsistencyReport, repo_slug: str) -> bool: ++ """Refuse a complete repair batch touching the exact bound histories.""" ++ if any(issue.check_id == "C-44" and issue.severity == "FAIL" for issue in report.issues): ++ return True ++ protected_ids = {entry["uuid"] for entry in FROZEN_CLUSTER_BINDINGS} ++ protected_ids.update(task["uuid"] for entry in FROZEN_CLUSTER_BINDINGS ++ for task in entry["tasks"]) ++ paths = {entry["path"] for entry in FROZEN_CLUSTER_BINDINGS} ++ root = Path(report.repo_path).absolute() if report.repo_path else None ++ ++ def protected(value, key=""): ++ if isinstance(value, dict): ++ return any(protected(item, name) for name, item in value.items()) ++ if isinstance(value, (list, tuple)): ++ return any(protected(item, key) for item in value) ++ text = str(value) ++ if (key in {"id", "db_id", "ws_id", "task_id"} ++ or key.endswith(("_id", "_uuid"))): ++ if text in protected_ids: ++ return True ++ if key in {"file_path", "wp_file", "md_path", "path"} and text: ++ candidate = Path(text) ++ if candidate.is_absolute(): ++ return root is not None and any(candidate.absolute() == root / path for path in paths) ++ return repo_slug == "railiance-cluster" and candidate.as_posix() in paths ++ return False ++ ++ for issue in report.issues: ++ if issue.fixable and (protected(issue.db_id, "db_id") ++ or protected(issue.file_path, "file_path") ++ or protected(issue._fix_context)): ++ report.add(severity="FAIL", check_id="C-44", ++ message="Frozen historical repair batch refused; owner review required", ++ fixable=False) ++ return True ++ return False ++ ++ + def fix_repo( + api_base: str, + repo_slug: str, +@@ -3624,7 +3687,9 @@ + repo_path_override, + bootstrap_empty_projection=bootstrap_empty_projection, + ) +- if any(i.check_id in {"C-00", "C-36"} for i in report.failures): ++ if _refuse_frozen_fix_batch(report, repo_slug): ++ return report ++ if any(i.check_id in {"C-00", "C-36", "C-44"} for i in report.failures): + return report + + # RMGR-WP-0002: optional repo-manager reconcile proxy (index rebuild) for pilots +--- /dev/null ++++ b/tests/test_frozen_binding_guard_isolated.py +@@ -0,0 +1,180 @@ ++from pathlib import Path ++import ast, asyncio, importlib.util, types, uuid ++from dataclasses import dataclass, field ++from datetime import datetime, timezone ++import pytest ++ ++ROOT = Path(__file__).resolve().parents[1] ++spec=importlib.util.spec_from_file_location('historical',ROOT/'api/services/historical_projection.py') ++h=importlib.util.module_from_spec(spec);spec.loader.exec_module(h) ++B=h.FROZEN_CLUSTER_BINDINGS[0] ++def binding(_bound=B,**changes): ++ B=_bound ++ d=dict(workplan_id=uuid.UUID(B['uuid']),repo_slug=B['repo'],filename=Path(B['path']).name,relative_path=B['path'],archived=True,status='finished');d.update(changes);return d ++class Obj: ++ def __init__(self,**kw):self.__dict__.update(kw);self.writes=[] ++ def __setattr__(self,k,v): ++ if 'writes' in self.__dict__:self.writes.append(k) ++ object.__setattr__(self,k,v) ++class Entry(Obj): ++ def model_dump(self):return {k:v for k,v in self.__dict__.items() if k!='writes'} ++class Col: ++ def in_(self,x):return self ++ def __eq__(self,x):return self ++class Query: ++ def __init__(self):self.locked=False;self.refreshed=False;self.ordered=False ++ def where(self,*a):return self ++ def order_by(self,*a):self.ordered=True;return self ++ def with_for_update(self):self.locked=True;return self ++ def execution_options(self,**kw):self.refreshed=kw.get("populate_existing") is True;return self ++class Model: id=Col();slug=Col();workplan_id=Col() ++class Result: ++ def __init__(self,values):self.values=values ++ def scalars(self):return self ++ def all(self):return self.values ++ def scalar_one_or_none(self):return self.values[0] if self.values else None ++class Session: ++ def __init__(self,rows):self.results=iter(rows);self.commits=0;self.queries=[] ++ async def execute(self,q):self.queries.append(q);return Result(next(self.results)) ++ async def commit(self):self.commits+=1 ++class HTTPException(Exception): ++ def __init__(self,status_code,detail):self.status_code=status_code;super().__init__(detail) ++def extracted(path,name,extra): ++ tree=ast.parse((ROOT/path).read_text());node=next(n for n in tree.body if isinstance(n,(ast.FunctionDef,ast.AsyncFunctionDef)) and n.name==name) ++ node.decorator_list=[];node.returns=None ++ for a in node.args.args + node.args.kwonlyargs:a.annotation=None ++ node.args.defaults=[] ++ ns=dict(Path=Path,datetime=datetime,timezone=timezone,**extra) ++ exec(compile(ast.Module(body=[node],type_ignores=[]),str(path),'exec'),ns);return ns[name] ++def actual_report(): ++ tree=ast.parse((ROOT/'scripts/consistency_check.py').read_text()) ++ nodes=[n for n in tree.body if (isinstance(n,ast.ClassDef) and n.name in {'Issue','ConsistencyReport'}) or (isinstance(n,ast.AnnAssign) and isinstance(n.target,ast.Name) and n.target.id=='_AUTOMATION_ERROR_CHECKS')] ++ ns=dict(dataclass=dataclass,field=field,_AUTOMATION_ERROR_CHECKS=frozenset({'C-00','C-44'})) ++ exec(compile(ast.Module(body=nodes,type_ignores=[]),'actual_report','exec'),ns) ++ return ns['ConsistencyReport']('railiance-cluster','/unused') ++ ++def closure(B=B): ++ repo=Obj(id=uuid.uuid4());parent=Obj(id=uuid.UUID(B['uuid']),repo_id=repo.id,status='finished') ++ tasks=[Obj(id=uuid.UUID(t['uuid']),workplan_id=parent.id,status='done') for t in B['tasks']] ++ return repo,parent,tasks ++async def run_api(entries,repo,parent,tasks,ordinary=None): ++ rows=([parent] if parent else [])+([ordinary] if ordinary else []) ++ session=Session([rows,[repo] if repo else [],tasks]) ++ fn=extracted('api/routers/workstreams.py','sync_workplan_bindings',dict(frozen_cluster_binding=h.frozen_cluster_binding,validate_frozen_cluster_rows=h.validate_frozen_cluster_rows,HTTPException=HTTPException,select=lambda *_:Query(),Workplan=Model,Task=Model,ManagedRepo=Model,_invalidate_workplan_index_cache=lambda:None)) ++ result=await fn(Obj(bindings=[Entry(**e) for e in entries]),session) ++ return result,session ++@pytest.mark.parametrize('change',[dict(repo_slug='foreign'),dict(workplan_id=uuid.uuid4()),dict(relative_path='workplans/other.md'),dict(filename='other.md'),dict(archived=False),dict(status='active')]) ++def test_metadata_refuses(change): ++ with pytest.raises(ValueError):h.frozen_cluster_binding(binding(**change)) ++def test_unrelated_binding_unchanged():assert h.frozen_cluster_binding(binding(workplan_id=uuid.uuid4(),filename='ordinary.md',relative_path='workplans/ordinary.md',repo_slug='other')) is None ++@pytest.mark.parametrize('fault',['parent_missing','foreign_repo','parent_active','task_missing','task_active','task_foreign','repo_missing']) ++@pytest.mark.parametrize("bound",h.FROZEN_CLUSTER_BINDINGS) ++def test_mixed_batch_refusal_precedes_all_writes(fault,bound): ++ repo,parent,tasks=closure(bound);ordinary=Obj(id=uuid.uuid4()) ++ if fault=='repo_missing':repo=None ++ if fault=='parent_missing':parent=None ++ if fault=='foreign_repo':parent.repo_id=uuid.uuid4() ++ if fault=='parent_active':parent.status='active' ++ if fault=='task_missing':tasks.pop() ++ if fault=='task_active':tasks[0].status='progress' ++ if fault=='task_foreign':tasks[0].workplan_id=uuid.uuid4() ++ if parent:parent.writes.clear() ++ for t in tasks:t.writes.clear() ++ normal=binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md') ++ with pytest.raises(HTTPException):asyncio.run(run_api([normal,binding(_bound=bound)],repo,parent,tasks,ordinary)) ++ assert ordinary.writes==[] ++ if parent:assert parent.writes==[] ++ assert all(t.writes==[] for t in tasks) ++@pytest.mark.parametrize("bound",h.FROZEN_CLUSTER_BINDINGS) ++def test_exact_frozen_no_setters_or_commit(bound): ++ repo,parent,tasks=closure(bound);result,session=asyncio.run(run_api([binding(_bound=bound)],repo,parent,tasks)) ++ assert result==dict(updated=0,received=1,preserved=1);assert session.commits==0;assert parent.writes==[] ++ assert all(q.locked and q.refreshed and q.ordered for q in (session.queries[0],session.queries[2])) ++@pytest.mark.parametrize("bound",h.FROZEN_CLUSTER_BINDINGS) ++def test_mixed_valid_preserves_frozen_and_updates_ordinary(bound): ++ repo,parent,tasks=closure(bound);ordinary=Obj(id=uuid.uuid4()) ++ normal=binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md') ++ result,session=asyncio.run(run_api([normal,binding(_bound=bound)],repo,parent,tasks,ordinary)) ++ assert result==dict(updated=1,received=2,preserved=1);assert session.commits==1;assert parent.writes==[] ++ assert ordinary.writes==['backing_filename','backing_relative_path','backing_archived','backing_synced_at'] ++@pytest.mark.parametrize('tamper',['metadata','bytes','symlink','missing_scan','missing_uuid','valid']) ++@pytest.mark.parametrize("bound_index", range(3)) ++def test_cli_exact_source_omission_or_whole_batch_refusal(tmp_path,tamper,bound_index): ++ bound=h.FROZEN_CLUSTER_BINDINGS[bound_index] ++ import shutil ++ infos=[] ++ for entry in h.FROZEN_CLUSTER_BINDINGS: ++ target=tmp_path/entry['path'];target.parent.mkdir(parents=True,exist_ok=True) ++ shutil.copyfile(Path('/home/worsch/railiance-cluster')/entry['path'],target) ++ infos.append((target,dict(state_hub_workstream_id=entry['uuid'],status='finished'),'')) ++ path=tmp_path/bound['path'] ++ meta=dict(state_hub_workstream_id=bound['uuid'],status='finished') ++ if tamper=='metadata':meta['status']='active' ++ if tamper=='missing_uuid':meta.pop('state_hub_workstream_id') ++ if tamper=='bytes':path.write_text(path.read_text()+'\n') ++ if tamper=='symlink':path.unlink();path.symlink_to(Path('/home/worsch/railiance-cluster')/bound['path']) ++ ordinary=tmp_path/'workplans/ordinary.md';ordinary.write_text('ordinary') ++ if tamper=='missing_scan':infos.pop(bound_index) ++ else:infos[bound_index]=(path,meta,'') ++ calls=[];report=actual_report() ++ fn=extracted('scripts/consistency_check.py','_sync_workplan_bindings',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS,frozen_cluster_binding=h.frozen_cluster_binding,normalise_workstream_status=lambda s:s,workplan_display_path=lambda root,p:p.relative_to(root).as_posix(),_api_put=lambda *a:calls.append(a) or dict(updated=1,received=1))) ++ fn('http://unused','railiance-cluster',[(ordinary,dict(state_hub_workstream_id=str(uuid.uuid4()),status='active'),''),*infos],tmp_path,report) ++ if tamper=='valid':assert len(calls)==1 and len(calls[0][2]['bindings'])==1 ++ else:assert calls==[] and report.failures[0].check_id=='C-44' and report.automation_errors ++ ++ ++def test_api_tampered_mixed_batch_refuses_before_queries_or_assignments(): ++ repo,parent,tasks=closure();ordinary=Obj(id=uuid.uuid4()) ++ with pytest.raises(HTTPException): ++ asyncio.run(run_api([binding(workplan_id=ordinary.id,repo_slug='ordinary',filename='ordinary.md',relative_path='workplans/ordinary.md'),binding(archived=False)],repo,parent,tasks,ordinary)) ++ assert ordinary.writes==[] and parent.writes==[] ++ ++ ++def test_foreign_repo_same_filename_path_is_ordinary(): ++ assert h.frozen_cluster_binding(binding(workplan_id=uuid.uuid4(),repo_slug='foreign')) is None ++ ++ ++@pytest.mark.parametrize('entry',h.FROZEN_CLUSTER_BINDINGS) ++def test_all_three_registry_bindings_are_guarded(entry): ++ good=dict(workplan_id=entry['uuid'],repo_slug=entry['repo'],filename=Path(entry['path']).name,relative_path=entry['path'],archived=True,status='finished') ++ assert h.frozen_cluster_binding(good) is entry ++ with pytest.raises(ValueError):h.frozen_cluster_binding(dict(good,status='active')) ++ ++ ++def test_real_cli_exit_criterion_is_nonzero_including_remote_all(): ++ report=actual_report();report.add(severity='FAIL',check_id='C-44',message='refused') ++ fn=extracted('scripts/consistency_check.py','consistency_exit_code',{}) ++ assert fn([report])==1 and fn([report],remote_all=True)==1 ++ ++ ++@pytest.mark.parametrize('check,key,protected_kind',[ ++ ('C-04','ws_id','parent'),('C-10','task_id','task'),('C-22','task_id','task'), ++ ('C-09','db_id','parent'),('C-23','wp_file','path'), ++]) ++@pytest.mark.parametrize('bound',h.FROZEN_CLUSTER_BINDINGS) ++def test_complete_fix_batch_preflight_before_ordinary_writes(check,key,protected_kind,bound): ++ report=actual_report();report.repo_path='/tmp/fixture-cluster' ++ report.add(severity='WARN',check_id='C-05',message='ordinary first',fixable=True,_fix_context={'ws_id':str(uuid.uuid4()),'field':'title','value':'ordinary'}) ++ value=bound['uuid'] if protected_kind=='parent' else bound['tasks'][0]['uuid'] if protected_kind=='task' else str(Path(report.repo_path)/bound['path']) ++ report.add(severity='WARN',check_id=check,message='protected later',fixable=True,db_id=value if key=='db_id' else '',_fix_context={key:value}) ++ guard=extracted('scripts/consistency_check.py','_refuse_frozen_fix_batch',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS)) ++ assert guard(report,'railiance-cluster') and report.automation_errors ++ calls=[] ++ fn=extracted('scripts/consistency_check.py','fix_repo',dict(check_repo=lambda *a,**kw:report,_refuse_frozen_fix_batch=guard,_api_post=lambda *a:calls.append(a),_api_patch=lambda *a:calls.append(a))) ++ assert fn('http://unused','railiance-cluster',None,False,False) is report and calls==[] ++ ++def test_unrelated_fix_context_not_refused(): ++ report=actual_report();report.add(severity='WARN',check_id='C-05',message='ordinary',fixable=True,_fix_context={'ws_id':str(uuid.uuid4())}) ++ guard=extracted('scripts/consistency_check.py','_refuse_frozen_fix_batch',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS)) ++ assert not guard(report,'other') ++ ++ ++def test_check_repo_guard_precedes_binding_sync_and_does_not_duplicate(): ++ tree=ast.parse((ROOT/'scripts/consistency_check.py').read_text()) ++ node=next(n for n in tree.body if isinstance(n,ast.FunctionDef) and n.name=='check_repo') ++ guard_lines=[n.lineno for n in ast.walk(node) if isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=='_refuse_frozen_fix_batch'] ++ sync_lines=[n.lineno for n in ast.walk(node) if isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=='_sync_workplan_bindings'] ++ assert len(guard_lines)==len(sync_lines)==1 and guard_lines[0]