fix(consistency): preserve immutable historical binding rows
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 28s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
tegwick 2026-10-17 02:26:29 +02:00
parent 086d79a273
commit a25b62f75a
10 changed files with 586 additions and 42 deletions

View file

@ -9,7 +9,7 @@ RAILIANCE_STATE_HUB_RELEASE ?= state-hub
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.

View file

@ -16,6 +16,8 @@ from api.database import get_session
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 @@ async def sync_workplan_bindings(
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 @@ async def sync_workplan_bindings(
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)

View file

@ -193,3 +193,46 @@ def bound_sources(repo_root: Path, repo_slug: str):
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")

View file

@ -3,4 +3,4 @@ name: state-hub
description: State Hub API service for private Railiance operation
type: application
version: 0.1.0
appVersion: "main-5dd3df9"
appVersion: "main-51bc70d"

View file

@ -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 @@ categories:
- tooling
- product
- business
- publication
# domain / secondary_domains — primary exactly 1; secondaries 0..n (§6)
domains:
@ -85,6 +86,8 @@ capability_families:
- traceability
- documentation
- decision-support
- history
- participation
platform_and_operations:
- platform
- deployment

View file

@ -6,7 +6,7 @@ namespace:
image:
repository: forgejo.coulomb.social/coulomb/state-hub
tag: "main-0747b4d"
tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015"
ingress:
enabled: false

View file

@ -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"
]
}
}

View file

@ -120,7 +120,9 @@ from api.classification import ( # noqa: E402
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 @@ def iter_workplan_files(workplans_dir: Path, include_archived: bool = True) -> l
# 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 @@ def check_repo(
_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 @@ def _sync_workplan_bindings(
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('"')
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,
}
)
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
bindings.append(binding)
if not bindings:
return
result = _api_put(api_base, "/workplans/index/bindings", {"bindings": bindings})
@ -3610,6 +3635,45 @@ def _write_custodian_brief(api_base: str, repo_slug: str, repo_path: str) -> boo
# 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 {"db_id", "ws_id", "task_id", "from_workstream_id",
"from_workplan_id", "from_task_id", "desired_ws_id",
"desired_task_id", "desired_record_id", "state_hub_workstream_id", "state_hub_task_id"}:
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 +3688,9 @@ def fix_repo(
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

View file

@ -0,0 +1,200 @@
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]<sync_lines[0]
report=actual_report();report.add(severity='FAIL',check_id='C-44',message='already refused')
guard=extracted('scripts/consistency_check.py','_refuse_frozen_fix_batch',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS))
assert guard(report,'railiance-cluster') and len(report.issues)==1
@pytest.mark.parametrize('bound',h.FROZEN_CLUSTER_BINDINGS)
@pytest.mark.parametrize('key',['to_workstream_id','to_workplan_id','to_task_id'])
def test_ordinary_dependency_to_history_remains_allowed(bound,key):
report=actual_report();ordinary=str(uuid.uuid4())
target=bound['tasks'][0]['uuid'] if key=='to_task_id' else bound['uuid']
report.add(severity='WARN',check_id='C-20',message='ordinary inbound edge',fixable=True,db_id=ordinary,_fix_context={'from_workstream_id':ordinary,key:target,'relationship_type':'blocks'})
guard=extracted('scripts/consistency_check.py','_refuse_frozen_fix_batch',dict(FROZEN_CLUSTER_BINDINGS=h.FROZEN_CLUSTER_BINDINGS))
assert not guard(report,'railiance-cluster') and not report.automation_errors
@pytest.mark.parametrize('bound',h.FROZEN_CLUSTER_BINDINGS)
@pytest.mark.parametrize('key',['from_workstream_id','from_workplan_id','from_task_id','state_hub_workstream_id','state_hub_task_id','desired_record_id'])
def test_protected_dependency_origin_and_nested_binding_refused(bound,key):
report=actual_report();origin=bound['tasks'][0]['uuid'] if 'task' in key else bound['uuid']
context={key:origin,'to_workstream_id':str(uuid.uuid4())}
if key.startswith('state_hub'):context={'meta':context}
report.add(severity='WARN',check_id='C-20',message='protected origin',fixable=True,_fix_context=context)
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

View file

@ -199,27 +199,37 @@ not delivered receiving or a committed native trial.
```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,79 @@ Secret value change, policy relaxation or actual Secret write is introduced.
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: done
priority: high
depends_on: [STATE-WP-0095-T06]
state_hub_task_id: "8be8f13b-6352-5295-9755-de6fbfb13593"
```
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.
Reviewed source preparation and canonical regression proof completed on
2026-10-17: 102 isolated tests pass and normal Make render/classification
check passes (eight objects, three unchanged delivered runtime images).
This completion record accompanies the same source publication/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."
state_hub_task_id: "88585c69-d9b9-5e1f-a256-6416ff71e824"
```
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.