docs: preserve reviewed NetKingdom session artifacts
This commit is contained in:
parent
ccf5a95ce3
commit
a0d684116a
43 changed files with 21999 additions and 0 deletions
|
|
@ -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
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
.PHONY: start install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard dashboard-local sync check-primary primary-port-clear check check-local test test-python clean register-project register-codex-project register-mcp configure-codex bootstrap-env dev-hub edge-relay mcp-profile validate-adr add-domain rename-domain add-repo list-repos register-path register-from-classification register-from-classification-all cleanup-stale tunnels-up tunnels-status tunnels-check bridges install-hooks install-hooks-all gitea-inventory token-reconcile railiance-state-hub-render railiance-state-hub-client-dry-run railiance-state-hub-server-dry-run railiance-state-hub-headroom
|
||||
|
||||
COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env
|
||||
PYTHON ?= python3
|
||||
HELM ?= $(shell command -v helm 2>/dev/null || if [ -x "$$HOME/.local/bin/helm" ]; then printf "%s" "$$HOME/.local/bin/helm"; else printf "%s" "helm"; fi)
|
||||
KUBECTL ?= $(shell command -v kubectl 2>/dev/null || if [ -x "$$HOME/.local/bin/kubectl" ]; then printf "%s" "$$HOME/.local/bin/kubectl"; else printf "%s" "kubectl"; fi)
|
||||
|
||||
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-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.
|
||||
UV ?= $(shell command -v uv 2>/dev/null || if [ -x "$$HOME/.local/bin/uv" ]; then printf "%s" "$$HOME/.local/bin/uv"; else printf "%s" "uv"; fi)
|
||||
RMGR ?= $(shell command -v rmgr 2>/dev/null || if [ -x "$$HOME/repo-manager/.venv/bin/rmgr" ]; then printf "%s" "$$HOME/repo-manager/.venv/bin/rmgr"; else printf "%s" "rmgr"; fi)
|
||||
STATE_HUB_API_BASE ?= http://127.0.0.1:8000
|
||||
SYNC_PATH ?= .
|
||||
SYNC_PUSH ?= 1
|
||||
|
||||
start:
|
||||
@echo "# Normal production UI access (run in order)"
|
||||
@echo "make bridges # connect local :8000 to the railiance01 primary"
|
||||
@echo "make dashboard # verify the primary, then serve the UI on :3000"
|
||||
@echo "make sync # push file-backed records and reconcile the exact forge commit"
|
||||
@echo ""
|
||||
@echo "# Optional local MCP adapter"
|
||||
@echo "make mcp-http # local SSE adapter on :8001; not needed by the UI"
|
||||
@echo ""
|
||||
@echo "# Local fallback/development only"
|
||||
@echo "make api # local Postgres + API; conflicts with the production :8000 tunnel"
|
||||
@echo "make dashboard-local # dashboard against the deliberate local API"
|
||||
|
||||
install:
|
||||
$(UV) sync
|
||||
|
||||
dashboard/node_modules/.bin/observable: dashboard/package.json dashboard/package-lock.json
|
||||
cd dashboard && npm ci
|
||||
|
||||
dashboard-install: dashboard/node_modules/.bin/observable
|
||||
|
||||
dashboard-check: dashboard-install
|
||||
cd dashboard && npm run build
|
||||
|
||||
## Symlink the custodian CLI into ~/.local/bin so it's on PATH system-wide
|
||||
install-cli: install
|
||||
mkdir -p ~/.local/bin
|
||||
ln -sf "$(shell pwd)/.venv/bin/custodian" ~/.local/bin/custodian
|
||||
ln -sf "$(shell pwd)/.venv/bin/statehub" ~/.local/bin/statehub
|
||||
@echo "Installed: custodian → $$(readlink -f ~/.local/bin/custodian)"
|
||||
@echo "Installed: statehub → $$(readlink -f ~/.local/bin/statehub)"
|
||||
@echo "Make sure ~/.local/bin is on your PATH:"
|
||||
@echo " echo 'export PATH=\"\$$HOME/.local/bin:\$$PATH\"' >> ~/.bashrc && source ~/.bashrc"
|
||||
|
||||
db:
|
||||
$(COMPOSE) up -d postgres
|
||||
|
||||
db-tools:
|
||||
$(COMPOSE) --profile tools up -d
|
||||
|
||||
migrate:
|
||||
$(UV) run alembic upgrade head
|
||||
|
||||
seed:
|
||||
$(UV) run python scripts/seed.py
|
||||
|
||||
## Start (or restart) the MCP SSE server on :8001 — primary transport for Claude Code.
|
||||
## Remote clients (e.g. COULOMBCORE) connect via the ops-bridge tunnel (port 18001).
|
||||
## Registration: claude mcp add-json -s user state-hub '{"type":"sse","url":"http://127.0.0.1:8001/sse"}'
|
||||
mcp-http:
|
||||
@fuser -k 8001/tcp 2>/dev/null && echo "Stopped running MCP server" || true
|
||||
MCP_TRANSPORT=sse MCP_PORT=8001 $(UV) run python mcp_server/server.py
|
||||
|
||||
## Require the production identity before serving the normal dashboard. This
|
||||
## prevents an accidental local `make api` from presenting an empty fallback DB
|
||||
## as the live State Hub.
|
||||
check-primary:
|
||||
@health="$$(curl -fsS --max-time 5 http://127.0.0.1:8000/state/health 2>/dev/null)" || { \
|
||||
echo "ERROR: State Hub primary is not reachable on 127.0.0.1:8000." >&2; \
|
||||
echo "Run 'make bridges' first." >&2; \
|
||||
exit 1; \
|
||||
}; \
|
||||
identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \
|
||||
if [ "$$identity" != "primary/railiance01" ]; then \
|
||||
echo "ERROR: 127.0.0.1:8000 is '$$identity', not the primary/railiance01 State Hub." >&2; \
|
||||
echo "Stop the local API, then run 'make bridges'. Use 'make dashboard-local' only for intentional local development." >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
## Refuse to start the production tunnel when a non-primary API already owns
|
||||
## port 8000. With no listener, bridge is free to establish the tunnel.
|
||||
primary-port-clear:
|
||||
@if health="$$(curl -fsS --max-time 2 http://127.0.0.1:8000/state/health 2>/dev/null)"; then \
|
||||
identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \
|
||||
if [ "$$identity" != "primary/railiance01" ]; then \
|
||||
echo "ERROR: port 8000 is occupied by '$$identity'." >&2; \
|
||||
echo "Stop the local 'make api' process before starting the production tunnel." >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
dashboard: check-primary
|
||||
@fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true
|
||||
$(MAKE) dashboard-install
|
||||
cd dashboard && npm run dev
|
||||
|
||||
## Deliberate local-development dashboard; bypasses the production identity gate.
|
||||
dashboard-local:
|
||||
@echo "WARNING: serving the dashboard against the local/fallback API on :8000."
|
||||
@fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true
|
||||
$(MAKE) dashboard-install
|
||||
cd dashboard && npm run dev
|
||||
|
||||
## Fast work-record path. Repo Manager assigns only missing deterministic IDs,
|
||||
## verifies the primary/railliance01 bridge, and asks central to derive the
|
||||
## exact pushed Forgejo commit in one transactional request.
|
||||
sync:
|
||||
$(RMGR) sync --path "$(SYNC_PATH)" --api-base "$(STATE_HUB_API_BASE)" $(if $(filter 1 true yes,$(SYNC_PUSH)),--push,)
|
||||
|
||||
check: check-primary
|
||||
@echo "State Hub primary/railiance01 is healthy."
|
||||
|
||||
check-local:
|
||||
curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool
|
||||
|
||||
# CUST-WP-0067-T09. The chart ships a copy of the-custodian canon allowed-values
|
||||
# because a container has no checkout to read it from. Regenerate after any
|
||||
# canon vocabulary change; the -check target fails on drift so a release cannot
|
||||
# validate classification against a stale vocabulary.
|
||||
sync-classification-allowed:
|
||||
python3 scripts/sync_classification_allowed.py
|
||||
|
||||
check-classification-allowed:
|
||||
python3 scripts/sync_classification_allowed.py --check
|
||||
|
||||
# STATE-WP-0091: read-only surge-headroom check. Does not start Helm.
|
||||
# Live: make railiance-state-hub-headroom
|
||||
# Fixtures: make railiance-state-hub-headroom HEADROOM_FIXTURE=tests/fixtures/release_headroom/rev63-105m.json
|
||||
HEADROOM_FIXTURE ?=
|
||||
railiance-state-hub-headroom:
|
||||
@if [ -n "$(HEADROOM_FIXTURE)" ]; then \
|
||||
$(PYTHON) scripts/release_headroom_preflight.py --fixture "$(HEADROOM_FIXTURE)"; \
|
||||
else \
|
||||
$(PYTHON) scripts/release_headroom_preflight.py --kubectl "$(KUBECTL)"; \
|
||||
fi
|
||||
|
||||
railiance-state-hub-render: check-classification-allowed
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG)
|
||||
|
||||
railiance-state-hub-client-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"
|
||||
|
||||
railiance-state-hub-server-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(KUBECTL) apply --server-side --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
if $(KUBECTL) get namespace $(RAILIANCE_STATE_HUB_NAMESPACE) >/dev/null 2>&1; then \
|
||||
$(KUBECTL) apply --server-side --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \
|
||||
else \
|
||||
echo "Namespace $(RAILIANCE_STATE_HUB_NAMESPACE) does not exist; validating namespaced app manifests with client dry-run."; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \
|
||||
fi
|
||||
|
||||
test: test-python dashboard-check
|
||||
|
||||
test-python:
|
||||
TEST_DATABASE_URL=postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test \
|
||||
$(UV) run pytest -x -q
|
||||
|
||||
## Benchmark /state/summary revision cache (API must be running on :8000)
|
||||
benchmark-summary-cache:
|
||||
$(UV) run python scripts/benchmark_summary_cache.py
|
||||
|
||||
## ops-bridge managed tunnels
|
||||
## Requires ops-bridge: bridge is at /home/worsch/.local/bin/bridge
|
||||
tunnels-up: primary-port-clear
|
||||
bridge up
|
||||
|
||||
tunnels-status:
|
||||
bridge status
|
||||
|
||||
## End-to-end check: verifies SSH process alive + remote port listening on COULOMBCORE.
|
||||
## Exits non-zero if any tunnel is not fully operational.
|
||||
tunnels-check:
|
||||
bridge check
|
||||
|
||||
## Ensure all ops-bridge tunnels are up and healthy.
|
||||
## Brings up any stopped/stale tunnels, shows final status, exits non-zero if anything is still down.
|
||||
bridges: primary-port-clear
|
||||
@echo "==> Bringing up all tunnels..."
|
||||
bridge up
|
||||
@echo ""
|
||||
@echo "==> Tunnel status:"
|
||||
bridge status
|
||||
@echo ""
|
||||
@echo "==> Checking tunnel health..."
|
||||
bridge check
|
||||
|
||||
## Start (or restart) the LOCAL FALLBACK backend — db + migrate + uvicorn.
|
||||
## This replaces anything on :8000, including the production State Hub tunnel.
|
||||
api: db
|
||||
@echo "WARNING: starting the local fallback API; this is not the railiance01 primary."
|
||||
@echo "Waiting for postgres..."; \
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do \
|
||||
nc -z 127.0.0.1 5432 2>/dev/null && break; \
|
||||
sleep 1; \
|
||||
done
|
||||
$(MAKE) migrate
|
||||
@fuser -k 8000/tcp 2>/dev/null && echo "Stopped running API" || true
|
||||
$(UV) run uvicorn api.main:app --reload --reload-dir api --reload-dir mcp_server --reload-dir task_flow_engine --host 127.0.0.1 --port 8000
|
||||
|
||||
## Register a project (Claude Code): make register-project DOMAIN=railiance PROJECT_PATH=/home/worsch/railiance
|
||||
register-project:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-project DOMAIN=<domain> PROJECT_PATH=<path>"; exit 1)
|
||||
@test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)"
|
||||
|
||||
## Register a Codex project (AGENTS.md + HTTP API): make register-codex-project DOMAIN=capabilities PROJECT_PATH=/home/worsch/my-repo
|
||||
register-codex-project:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-codex-project DOMAIN=<domain> PROJECT_PATH=<path>"; exit 1)
|
||||
@test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)" --codex
|
||||
|
||||
## Register State Hub MCP for Claude Code. Optional: make register-mcp MCP_URL=http://127.0.0.1:18001/sse
|
||||
register-mcp:
|
||||
scripts/register-mcp.sh \
|
||||
$(if $(MCP_URL),--url "$(MCP_URL)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Configure Codex workspace-write networking for local State Hub access.
|
||||
configure-codex:
|
||||
scripts/configure-codex.sh \
|
||||
$(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(WITH_MCP),--with-mcp,) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Bootstrap a new operator/collaborator environment. Optional: make bootstrap-env ARGS="--install-missing"
|
||||
bootstrap-env:
|
||||
scripts/bootstrap-env.sh $(ARGS)
|
||||
|
||||
## Files-first local dev hub (CUST-WP-0054-T07): postgres + migrate + seed + API + repo registration.
|
||||
## Optional: WITH_EDGE=1 WITH_MCP=1 make dev-hub
|
||||
## Fleet tunnel when reachable: make dev-hub PROFILE=fleet
|
||||
dev-hub:
|
||||
bash scripts/dev_hub_up.sh $(if $(PROFILE),$(PROFILE),dev)
|
||||
|
||||
## Local edge relay for offline write buffering (STATE-WP-0068).
|
||||
edge-relay:
|
||||
@fuser -k 18080/tcp 2>/dev/null && echo "Stopped running edge relay" || true
|
||||
STATEHUB_UPSTREAM_URL=$${STATEHUB_UPSTREAM_URL:-http://127.0.0.1:8000} \
|
||||
$(UV) run uvicorn api.edge.relay:app --host 127.0.0.1 --port 18080
|
||||
|
||||
## Switch MCP/API profile: make mcp-profile PROFILE=dev|fleet|status
|
||||
mcp-profile:
|
||||
@test -n "$(PROFILE)" || (echo "ERROR: PROFILE is required (dev|fleet|status)."; exit 1)
|
||||
bash scripts/mcp_hub_profile.sh "$(PROFILE)"
|
||||
|
||||
## Add a second repo to an existing domain: make add-repo DOMAIN=railiance REPO_PATH=/home/worsch/railiance-infra
|
||||
add-repo:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1)
|
||||
@test -n "$(REPO_PATH)" || (echo "ERROR: REPO_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(REPO_PATH)" --additional
|
||||
|
||||
## Create a new domain: make add-domain DOMAIN=my_domain NAME="My Domain"
|
||||
add-domain:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required (slug)."; exit 1)
|
||||
@test -n "$(NAME)" || (echo "ERROR: NAME is required (display name)."; exit 1)
|
||||
curl -sf -X POST http://127.0.0.1:8000/domains/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"slug\": \"$(DOMAIN)\", \"name\": \"$(NAME)\"}" | python3 -m json.tool
|
||||
|
||||
## Rename a domain: make rename-domain DOMAIN=old_slug NEW_SLUG=new_slug NEW_NAME="New Name"
|
||||
rename-domain:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN (old slug) is required."; exit 1)
|
||||
@test -n "$(NEW_SLUG)" || (echo "ERROR: NEW_SLUG is required."; exit 1)
|
||||
@test -n "$(NEW_NAME)" || (echo "ERROR: NEW_NAME is required."; exit 1)
|
||||
curl -sf -X PATCH http://127.0.0.1:8000/domains/$(DOMAIN)/rename \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_slug\": \"$(NEW_SLUG)\", \"new_name\": \"$(NEW_NAME)\"}" | python3 -m json.tool
|
||||
|
||||
## Register this machine's local path for a repo: make register-path REPO=marki-docx PATH=/home/tegwick/marki-docx
|
||||
register-path:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make register-path REPO=<slug> PATH=<path>"; exit 1)
|
||||
@test -n "$(PATH)" || (echo "ERROR: PATH is required. Usage: make register-path REPO=<slug> PATH=<path>"; exit 1)
|
||||
curl -sf -X POST "http://127.0.0.1:8000/repos/$(REPO)/paths" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"host\": \"$$(hostname)\", \"path\": \"$(PATH)\"}" | python3 -m json.tool
|
||||
|
||||
## List repos for a domain: make list-repos DOMAIN=railiance
|
||||
list-repos:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1)
|
||||
curl -sf "http://127.0.0.1:8000/repos/?domain=$(DOMAIN)" | python3 -m json.tool
|
||||
|
||||
## Ingest SBOM data for a repo (all mechanisms: lockfiles + ansible + sbom-tools.yaml).
|
||||
## Auto-detect all sources: make ingest-sbom REPO=the-custodian REPO_PATH=/home/worsch/the-custodian
|
||||
## Single lockfile (explicit): make ingest-sbom REPO=the-custodian LOCKFILE=/path/to/uv.lock
|
||||
## Dry-run (no submit): make ingest-sbom REPO=the-custodian REPO_PATH=... DRY_RUN=1
|
||||
## Tip: run capture-tools first for repos with system-level tool dependencies.
|
||||
ingest-sbom:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_sbom.py --repo "$(REPO)" \
|
||||
$(if $(LOCKFILE),--lockfile "$(LOCKFILE)") \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Ingest capability declarations from SCOPE.md into the catalog.
|
||||
## Usage: make ingest-capabilities REPO=the-custodian [REPO_PATH=/home/worsch/the-custodian]
|
||||
## Or: make ingest-capabilities-all
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
ingest-capabilities:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_capabilities.py --repo "$(REPO)" \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
ingest-capabilities-all:
|
||||
$(UV) run python scripts/ingest_capabilities.py --all \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Check Repository Definition of Integrated (DoI) criteria for a repo.
|
||||
## Usage: make check-doi REPO=llm-connect
|
||||
## Or: make check-doi-all
|
||||
## Add JSON=1 for machine-readable output.
|
||||
check-doi:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/check_doi.py --repo "$(REPO)" $(if $(JSON),--json)
|
||||
|
||||
check-doi-all:
|
||||
$(UV) run python scripts/check_doi.py --all $(if $(JSON),--json)
|
||||
|
||||
## Ingest tpsc.yaml service declarations from a repo into the TPSC catalog.
|
||||
## Usage: make ingest-tpsc REPO=llm-connect
|
||||
## Or: make ingest-tpsc-all
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
ingest-tpsc:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_tpsc.py --repo "$(REPO)" \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
ingest-tpsc-all:
|
||||
$(UV) run python scripts/ingest_tpsc.py --all \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Run SBOM capture agent for a repo — generates/updates sbom-tools.yaml.
|
||||
## Usage: make capture-tools REPO=railiance-infra [REPO_PATH=/home/worsch/railiance-infra]
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
capture-tools:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/capture_sbom_tools.py --repo "$(REPO)" \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Check a repo for ADR-001 compliance: make validate-adr REPO=/path/to/repo [DOMAIN=custodian]
|
||||
validate-adr:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make validate-adr REPO=<path> [DOMAIN=<slug>]"; exit 1)
|
||||
$(UV) run python scripts/validate_repo_adr.py "$(REPO)" $(if $(DOMAIN),--domain "$(DOMAIN)",)
|
||||
|
||||
## Consistency exit contract:
|
||||
## - Direct scripts/consistency_check.py: 0 clean, 2 warnings-only, 1 failures.
|
||||
## - Agent/operator Make wrappers below normalize warning-only 2 to shell success
|
||||
## while preserving visible WARN output and keeping real failures non-zero.
|
||||
## Check a single repo for ADR-001 consistency: make check-consistency REPO=the-custodian [REPO_PATH=/override]
|
||||
## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures
|
||||
check-consistency:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make check-consistency REPO=<slug>"; exit 1)
|
||||
$(UV) run python scripts/consistency_check.py --repo "$(REPO)" \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check and auto-fix a single repo: make fix-consistency REPO=the-custodian [REPO_PATH=/override]
|
||||
## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures
|
||||
fix-consistency:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make fix-consistency REPO=<slug>"; exit 1)
|
||||
$(UV) run python scripts/consistency_check.py --repo "$(REPO)" --fix \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Normalize workplan frontmatter and task status literals in attached repos.
|
||||
## Usage: make normalize-attached-workplans REPO=artifact-store
|
||||
## make normalize-attached-workplans DIRTY=1
|
||||
normalize-attached-workplans:
|
||||
$(UV) run python scripts/normalize_attached_repo_workplans.py \
|
||||
$(if $(REPO),--repo "$(REPO)",) \
|
||||
$(if $(DIRTY),--dirty,) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
@test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO=<slug> or DIRTY=1"; exit 1)
|
||||
|
||||
## Regenerate AGENTS.md / CLAUDE.md / .claude/rules from templates.
|
||||
## Usage: make update-agent-instructions REPO=artifact-store
|
||||
## make update-agent-instructions DIRTY=1
|
||||
update-agent-instructions:
|
||||
$(UV) run python scripts/update_agent_instruction_files.py \
|
||||
$(if $(REPO),--repo "$(REPO)",) \
|
||||
$(if $(DIRTY),--dirty,)
|
||||
@test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO=<slug> or DIRTY=1"; exit 1)
|
||||
|
||||
## Reconcile measured token sources against State Hub.
|
||||
## Usage: make token-reconcile [SINCE=2026-05-19] [APPLY=1] [ZERO_FALLBACKS=1]
|
||||
token-reconcile:
|
||||
$(PYTHON) scripts/token_reconcile.py \
|
||||
$(if $(SINCE),--since "$(SINCE)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \
|
||||
$(if $(CLAUDE_HOME),--claude-home "$(CLAUDE_HOME)",) \
|
||||
$(if $(APPLY),--apply,) \
|
||||
$(if $(ZERO_FALLBACKS),--zero-superseded-fallbacks,)
|
||||
|
||||
## Pull then fix: single repo or all repos if REPO omitted
|
||||
## make fix-consistency-remote — smart pull+fix all repos that need it
|
||||
## make fix-consistency-remote REPO=slug — pull+fix one repo
|
||||
fix-consistency-remote:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
$(if $(REPO),--repo "$(REPO)",--all) \
|
||||
--remote \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(NO_WRITEBACK),--no-writeback,); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Infer repo slug from git remote URL and check: make check-consistency-here [REPO_PATH=/path/to/repo]
|
||||
## Omit REPO_PATH to use the Python script's CWD (i.e. pass an empty --here flag).
|
||||
check-consistency-here:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
--here $(if $(REPO_PATH),"$(REPO_PATH)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Infer repo slug from git remote URL and fix: make fix-consistency-here [REPO_PATH=/path/to/repo]
|
||||
fix-consistency-here:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
--here $(if $(REPO_PATH),"$(REPO_PATH)",) \
|
||||
--fix \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check all registered repos for ADR-001 consistency
|
||||
check-consistency-all:
|
||||
$(UV) run python scripts/consistency_check.py --all $(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check and auto-fix all registered repos
|
||||
fix-consistency-all:
|
||||
$(UV) run python scripts/consistency_check.py --all --fix $(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Cancel open tasks belonging to completed/archived workstreams.
|
||||
## Safe to run at any time; also suitable for a daily cron job.
|
||||
## Cron example: 0 3 * * * cd ~/state-hub && make cleanup-stale
|
||||
cleanup-stale:
|
||||
$(UV) run python scripts/cleanup_stale_tasks.py
|
||||
|
||||
## Install custodian post-commit sync hook into one repo: make install-hooks REPO=marki-docx
|
||||
install-hooks:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make install-hooks REPO=<slug>"; exit 1)
|
||||
bash scripts/install_hooks.sh --repo "$(REPO)"
|
||||
|
||||
## Install custodian post-commit sync hook into all active registered repos
|
||||
install-hooks-all:
|
||||
bash scripts/install_hooks.sh --all
|
||||
|
||||
## Remove custodian post-commit sync hook from one repo: make remove-hooks REPO=marki-docx
|
||||
remove-hooks:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make remove-hooks REPO=<slug>"; exit 1)
|
||||
bash scripts/install_hooks.sh --repo "$(REPO)" --remove
|
||||
|
||||
## Compare Gitea coulomb org repos against state-hub registered repos
|
||||
## Requires GITEA_TOKEN in env or .env: make gitea-inventory GITEA_TOKEN=<token>
|
||||
gitea-inventory:
|
||||
$(UV) run python scripts/gitea_inventory.py $(if $(JSON),--json)
|
||||
|
||||
## Register/update one repo from .repo-classification.yaml:
|
||||
## make register-from-classification REPO=state-hub
|
||||
## make register-from-classification PATH=/path/to/repo
|
||||
## Optional: DRY_RUN=1
|
||||
register-from-classification:
|
||||
@test -n "$(REPO)" -o -n "$(PATH)" || (echo "ERROR: REPO or PATH is required."; exit 1)
|
||||
$(UV) run python scripts/register_from_classification.py \
|
||||
$(if $(PATH),--repo-path "$(PATH)",--slug "$(REPO)") \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Bulk register/update all active repos with accessible local paths
|
||||
register-from-classification-all:
|
||||
$(UV) run python scripts/register_from_classification.py --bulk \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
clean:
|
||||
$(COMPOSE) down -v
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
import socket
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
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,
|
||||
WorkplanRead,
|
||||
WorkplanUpdate,
|
||||
)
|
||||
from api.services.lifecycle import transition_workplan_status
|
||||
from api.services.legacy_compat import retire_legacy_route
|
||||
from api.services.wait_kind import annotate_workplans
|
||||
from api.work_record_flavor import (
|
||||
RESIDUAL_FLAVOR,
|
||||
WORK_RECORD_FLAVORS,
|
||||
normalize_flavor,
|
||||
)
|
||||
from api.workplan_status import (
|
||||
is_supported_workplan_status,
|
||||
normalize_workplan_status,
|
||||
ready_review_status,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/workstreams", tags=["workstreams"])
|
||||
workplan_router = APIRouter(prefix="/workplans", tags=["workplans"])
|
||||
|
||||
_INDEX_CACHE: dict[str, Any] | None = None
|
||||
_INDEX_CACHE_AT: float = 0.0
|
||||
_INDEX_TTL = 30.0
|
||||
_INDEX_REFRESH_TASK: asyncio.Task | None = None
|
||||
_INDEX_LAST_ERROR: str | None = None
|
||||
|
||||
_COMPLETED_WORKPLAN_EVENT = "org.statehub.workplan.completed"
|
||||
|
||||
|
||||
def _repo_path(repo: ManagedRepo) -> Path | None:
|
||||
hostname = socket.gethostname()
|
||||
candidates = []
|
||||
host_paths = repo.host_paths or {}
|
||||
if host_paths.get(hostname):
|
||||
candidates.append(host_paths[hostname])
|
||||
if repo.local_path:
|
||||
candidates.append(repo.local_path)
|
||||
for raw in candidates:
|
||||
path = Path(raw).expanduser()
|
||||
if path.is_dir():
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def _frontmatter(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return {}
|
||||
if not text.startswith("---\n"):
|
||||
return {}
|
||||
end = text.find("\n---", 4)
|
||||
if end == -1:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return yaml.safe_load(text[4:end].strip()) or {}
|
||||
except yaml.YAMLError:
|
||||
return {}
|
||||
|
||||
|
||||
def _legacy_key(method: str, route: str) -> str:
|
||||
return f"rest_api:{method} {route}"
|
||||
|
||||
|
||||
async def _list_workplans(
|
||||
*,
|
||||
topic_id: uuid.UUID | None,
|
||||
repo_id: uuid.UUID | None,
|
||||
repo_goal_id: uuid.UUID | None,
|
||||
status_filter: str | None,
|
||||
owner: str | None,
|
||||
slug: str | None,
|
||||
flavor: str | None = None,
|
||||
include_residuals: bool = True,
|
||||
session: AsyncSession,
|
||||
) -> list[Workplan]:
|
||||
q = select(Workplan)
|
||||
if topic_id:
|
||||
q = q.where(Workplan.topic_id == topic_id)
|
||||
if repo_id:
|
||||
q = q.where(Workplan.repo_id == repo_id)
|
||||
if repo_goal_id:
|
||||
q = q.where(Workplan.repo_goal_id == repo_goal_id)
|
||||
if status_filter:
|
||||
normalised_status = normalize_workplan_status(status_filter)
|
||||
if not is_supported_workplan_status(status_filter):
|
||||
raise HTTPException(status_code=422, detail=f"Unsupported workplan status '{status_filter}'")
|
||||
q = q.where(Workplan.status == normalised_status)
|
||||
if owner:
|
||||
q = q.where(Workplan.owner == owner)
|
||||
if slug:
|
||||
q = q.where(Workplan.slug == slug)
|
||||
wanted = normalize_flavor(flavor)
|
||||
if wanted is not None:
|
||||
if wanted not in WORK_RECORD_FLAVORS:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=(
|
||||
f"Unknown work-record flavor {wanted!r}; "
|
||||
f"expected one of {', '.join(WORK_RECORD_FLAVORS)}"
|
||||
),
|
||||
)
|
||||
q = q.where(Workplan.flavor == wanted)
|
||||
elif not include_residuals:
|
||||
q = q.where(or_(Workplan.flavor.is_(None), Workplan.flavor != RESIDUAL_FLAVOR))
|
||||
q = q.order_by(
|
||||
Workplan.planning_priority.asc().nullslast(),
|
||||
Workplan.planning_order.asc().nullslast(),
|
||||
Workplan.updated_at.desc(),
|
||||
)
|
||||
result = await session.execute(q)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def _build_workplan_index(session: AsyncSession) -> dict[str, Any]:
|
||||
result = await session.execute(
|
||||
select(ManagedRepo).where(ManagedRepo.status == "active").order_by(ManagedRepo.slug)
|
||||
)
|
||||
index: dict[str, Any] = {}
|
||||
for repo in result.scalars().all():
|
||||
root = _repo_path(repo)
|
||||
if root is None:
|
||||
continue
|
||||
for directory, archived in (
|
||||
(root / "workplans", False),
|
||||
(root / "workplans" / "archived", True),
|
||||
):
|
||||
if not directory.is_dir():
|
||||
continue
|
||||
for path in sorted(directory.glob("*.md")):
|
||||
data = _frontmatter(path)
|
||||
workplan_id = data.get("state_hub_workstream_id") or data.get("state_hub_workplan_id")
|
||||
if not workplan_id:
|
||||
continue
|
||||
file_status = normalize_workplan_status(data.get("status", ""))
|
||||
review = (
|
||||
ready_review_status(
|
||||
root,
|
||||
data.get("reviewed_against_commit"),
|
||||
data.get("context_paths"),
|
||||
)
|
||||
if file_status == "ready"
|
||||
else None
|
||||
)
|
||||
index[str(workplan_id)] = {
|
||||
"filename": path.name,
|
||||
"relative_path": str(path.relative_to(root)),
|
||||
"repo_slug": repo.slug,
|
||||
"archived": archived,
|
||||
"status": file_status or None,
|
||||
"needs_review": bool(review and review.needs_review),
|
||||
"health_labels": ["needs_review"] if review and review.needs_review else [],
|
||||
}
|
||||
await _merge_db_backing_index(session, index)
|
||||
return {"workplans": index, "workstreams": index}
|
||||
|
||||
|
||||
async def _merge_db_backing_index(session: AsyncSession, index: dict[str, Any]) -> None:
|
||||
"""Fill index gaps from DB-backed file bindings synced by fix-consistency."""
|
||||
result = await session.execute(
|
||||
select(Workplan, ManagedRepo.slug)
|
||||
.join(ManagedRepo, Workplan.repo_id == ManagedRepo.id)
|
||||
.where(Workplan.backing_filename.isnot(None))
|
||||
)
|
||||
for wp, repo_slug in result.all():
|
||||
key = str(wp.id)
|
||||
if key in index:
|
||||
continue
|
||||
index[key] = {
|
||||
"filename": wp.backing_filename,
|
||||
"relative_path": wp.backing_relative_path,
|
||||
"repo_slug": repo_slug,
|
||||
"archived": bool(wp.backing_archived),
|
||||
"status": normalize_workplan_status(wp.status) if wp.status else None,
|
||||
"needs_review": False,
|
||||
"health_labels": [],
|
||||
}
|
||||
|
||||
|
||||
def _invalidate_workplan_index_cache() -> None:
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT
|
||||
_INDEX_CACHE = None
|
||||
_INDEX_CACHE_AT = 0.0
|
||||
|
||||
|
||||
def _index_with_meta(*, stale: bool, refresh_in_progress: bool) -> dict[str, Any]:
|
||||
age = time.monotonic() - _INDEX_CACHE_AT if _INDEX_CACHE_AT else None
|
||||
return {
|
||||
**(_INDEX_CACHE or {"workplans": {}, "workstreams": {}}),
|
||||
"_meta": {
|
||||
"generated_at": _INDEX_CACHE.get("_meta", {}).get("generated_at") if _INDEX_CACHE else None,
|
||||
"stale": stale,
|
||||
"cache_age_seconds": round(age, 3) if age is not None else None,
|
||||
"refresh_in_progress": refresh_in_progress,
|
||||
"last_error": _INDEX_LAST_ERROR,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _refresh_workplan_index_background() -> None:
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT, _INDEX_LAST_ERROR
|
||||
from api.database import async_session_factory
|
||||
|
||||
try:
|
||||
async with async_session_factory() as session:
|
||||
index = await _build_workplan_index(session)
|
||||
index["_meta"] = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"stale": False,
|
||||
"cache_age_seconds": 0.0,
|
||||
"refresh_in_progress": False,
|
||||
"last_error": None,
|
||||
}
|
||||
_INDEX_CACHE = index
|
||||
_INDEX_CACHE_AT = time.monotonic()
|
||||
_INDEX_LAST_ERROR = None
|
||||
except Exception as exc:
|
||||
_INDEX_LAST_ERROR = str(exc)
|
||||
|
||||
|
||||
def _ensure_index_refresh_started() -> None:
|
||||
global _INDEX_REFRESH_TASK
|
||||
if _INDEX_REFRESH_TASK is not None and not _INDEX_REFRESH_TASK.done():
|
||||
return
|
||||
_INDEX_REFRESH_TASK = asyncio.create_task(_refresh_workplan_index_background())
|
||||
|
||||
|
||||
async def _workplan_index(
|
||||
*,
|
||||
refresh: bool,
|
||||
session: AsyncSession,
|
||||
) -> dict[str, Any]:
|
||||
"""Map file-backed workplan ids to their local workplan filenames."""
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT, _INDEX_LAST_ERROR
|
||||
cache_age = time.monotonic() - _INDEX_CACHE_AT if _INDEX_CACHE_AT else None
|
||||
if not refresh and _INDEX_CACHE is not None and cache_age is not None and cache_age < _INDEX_TTL:
|
||||
refresh_running = _INDEX_REFRESH_TASK is not None and not _INDEX_REFRESH_TASK.done()
|
||||
return _index_with_meta(stale=False, refresh_in_progress=refresh_running)
|
||||
|
||||
if not refresh and _INDEX_CACHE is not None:
|
||||
_ensure_index_refresh_started()
|
||||
return _index_with_meta(stale=True, refresh_in_progress=True)
|
||||
|
||||
index = await _build_workplan_index(session)
|
||||
index["_meta"] = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"stale": False,
|
||||
"cache_age_seconds": 0.0,
|
||||
"refresh_in_progress": False,
|
||||
"last_error": None,
|
||||
}
|
||||
_INDEX_CACHE = index
|
||||
_INDEX_CACHE_AT = time.monotonic()
|
||||
_INDEX_LAST_ERROR = None
|
||||
return _INDEX_CACHE
|
||||
|
||||
|
||||
async def _create_workplan(
|
||||
*,
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = Workplan(**body.model_dump())
|
||||
session.add(wp)
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
return wp
|
||||
|
||||
|
||||
async def _get_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
return wp
|
||||
|
||||
|
||||
async def _update_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
status_update = update_data.pop("status", None)
|
||||
prev_status = wp.status
|
||||
for field, value in update_data.items():
|
||||
setattr(wp, field, value)
|
||||
if status_update is not None:
|
||||
transition_workplan_status(wp, status_update)
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
|
||||
if normalize_workplan_status(prev_status) != "finished" and wp.status == "finished":
|
||||
await _publish_completion_events(wp, session)
|
||||
|
||||
return wp
|
||||
|
||||
|
||||
async def _archive_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
transition_workplan_status(wp, "archived")
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
return wp
|
||||
|
||||
|
||||
async def _publish_completion_events(wp: Workplan, session: AsyncSession) -> None:
|
||||
workplan_envelope = EventEnvelope.new(
|
||||
_COMPLETED_WORKPLAN_EVENT,
|
||||
attributes={
|
||||
"workplan_id": str(wp.id),
|
||||
"slug": wp.slug,
|
||||
"title": wp.title,
|
||||
"topic_id": str(wp.topic_id) if wp.topic_id else None,
|
||||
"repo_id": str(wp.repo_id) if wp.repo_id else None,
|
||||
"repo_goal_id": str(wp.repo_goal_id) if wp.repo_goal_id else None,
|
||||
},
|
||||
)
|
||||
asyncio.create_task(publish_event(_COMPLETED_WORKPLAN_EVENT, workplan_envelope))
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_410_GONE)
|
||||
async def list_workstreams(
|
||||
request: Request,
|
||||
response: Response,
|
||||
topic_id: uuid.UUID | None = None,
|
||||
repo_id: uuid.UUID | None = None,
|
||||
repo_goal_id: uuid.UUID | None = None,
|
||||
status: str | None = None,
|
||||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
detail="Legacy GET /workstreams/ retired; use GET /workplans/",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/", response_model=list[WorkplanRead])
|
||||
async def list_workplans(
|
||||
topic_id: uuid.UUID | None = None,
|
||||
repo_id: uuid.UUID | None = None,
|
||||
repo_goal_id: uuid.UUID | None = None,
|
||||
status: str | None = None,
|
||||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
flavor: str | None = Query(None),
|
||||
include_residuals: bool = Query(True),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[Workplan]:
|
||||
workplans = await _list_workplans(
|
||||
topic_id=topic_id,
|
||||
repo_id=repo_id,
|
||||
repo_goal_id=repo_goal_id,
|
||||
status_filter=status,
|
||||
owner=owner,
|
||||
slug=slug,
|
||||
flavor=flavor,
|
||||
include_residuals=include_residuals,
|
||||
session=session,
|
||||
)
|
||||
return list(await annotate_workplans(session, workplans))
|
||||
|
||||
|
||||
@router.get("/workplan-index", status_code=status.HTTP_410_GONE)
|
||||
async def workplan_index(
|
||||
request: Request,
|
||||
response: Response,
|
||||
refresh: bool = Query(False, description="Force cache invalidation"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/workplan-index"),
|
||||
replacement_ref="/workplans/index",
|
||||
detail="Legacy GET /workstreams/workplan-index retired; use GET /workplans/index",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/index")
|
||||
async def workplan_index_preferred(
|
||||
refresh: bool = Query(False, description="Force cache invalidation"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> dict[str, Any]:
|
||||
return await _workplan_index(refresh=refresh, session=session)
|
||||
|
||||
|
||||
@workplan_router.put("/index/bindings")
|
||||
async def sync_workplan_bindings(
|
||||
body: WorkplanBindingsSync,
|
||||
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
|
||||
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
|
||||
wp.backing_filename = entry.filename
|
||||
wp.backing_relative_path = entry.relative_path
|
||||
wp.backing_archived = entry.archived
|
||||
wp.backing_synced_at = synced_at
|
||||
updated += 1
|
||||
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)
|
||||
async def create_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("POST", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
detail="Legacy POST /workstreams/ retired; use POST /workplans/",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.post("/", response_model=WorkplanRead, status_code=status.HTTP_201_CREATED)
|
||||
async def create_workplan(
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _create_workplan(body=body, session=session)
|
||||
|
||||
|
||||
@router.get("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def get_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy GET /workstreams/{workstream_id} retired; use GET /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def get_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
wp = await _get_workplan(workplan_id=workplan_id, session=session)
|
||||
await annotate_workplans(session, [wp])
|
||||
return wp
|
||||
|
||||
|
||||
@router.patch("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def update_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("PATCH", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy PATCH /workstreams/{workstream_id} retired; use PATCH /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.patch("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def update_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _update_workplan(workplan_id=workplan_id, body=body, session=session)
|
||||
|
||||
|
||||
@router.delete("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def archive_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("DELETE", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="DELETE /workplans/{workplan_id}",
|
||||
detail="Legacy DELETE /workstreams/{workstream_id} retired; use DELETE /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.delete("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def archive_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _archive_workplan(workplan_id=workplan_id, session=session)
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
"""Exact closed source bindings predating canonical Forge identity.
|
||||
|
||||
These are frozen historical source views, not aliases or UUID allocation rules.
|
||||
An altered/missing source requires explicit owner review; generic source UUIDs
|
||||
remain untrusted. Shared Activity views represent the one archived parent.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
import json
|
||||
|
||||
BINDINGS = json.loads(r'''[
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/archived/260707-custodian-WP-0003-event-bridge.md",
|
||||
"sha256": "83ea8bcb151e003419b73c59dbd028f4a1ce3d1120a82e4a128a772503ff3b76",
|
||||
"record_id": "custodian-WP-0003",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0001-temporal-backbone.md",
|
||||
"sha256": "020e2eac973f2daa41c1687f8e867da9d521f945547e6aa82c87993b3ff6fc64",
|
||||
"record_id": "custodian-WP-0001",
|
||||
"uuid": "e22516cc-4fdf-4dad-a928-a9e04a9fa7c7",
|
||||
"parent_record_id": "custodian-WP-0001"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0002-triggers-ops.md",
|
||||
"sha256": "d8cbc61631c01b6f11a03a63f2fb6275fc853ff7f7459ce65973ed1a233daa01",
|
||||
"record_id": "custodian-WP-0002",
|
||||
"uuid": "3a4f47d9-8bc1-434e-acb4-bed5d4dacda0",
|
||||
"parent_record_id": "custodian-WP-0002"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md",
|
||||
"sha256": "ffa6682e9ba3aaf559bd44203d561c90dea69bcf1b1e778209a7b209e6e5cdd3",
|
||||
"record_id": "custodian-WP-0003a",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md",
|
||||
"sha256": "4fc34e0f821e2df51e5c08c7ab34da35b65690381d01f0cfdf55d00c970b414c",
|
||||
"record_id": "custodian-WP-0003b",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003c-event-bridge-context-integration.md",
|
||||
"sha256": "b6cd62b26515e509670ca39736b6bcec9633beedd46c9bd8f7598c06bac7276b",
|
||||
"record_id": "custodian-WP-0003c",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0004-railiance-ops.md",
|
||||
"sha256": "20df02eea740e109c937130e7f8801a636fc0990ed8c6d2ced55659f8957adb1",
|
||||
"record_id": "custodian-WP-0004",
|
||||
"uuid": "759b1255-aa78-42b5-8fab-c5fcf168f5f4",
|
||||
"parent_record_id": "custodian-WP-0004"
|
||||
},
|
||||
{
|
||||
"repo": "the-custodian",
|
||||
"path": "workplans/ADHOC-2026-08-25-retired.md",
|
||||
"sha256": "344a1293dbb3a67a944eb34ca4a0337487d8f047226027a2107922346fbab2dd",
|
||||
"record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED",
|
||||
"uuid": "1c53d3db-6633-5e2d-987f-10706d73be9e",
|
||||
"parent_record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED"
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md",
|
||||
"sha256": "8d6f8533c119117686bc0d5bb9ed5349726aa6c3189ff98b53409c68ac685ca9",
|
||||
"record_id": "RAIL-BS-WP-0002",
|
||||
"uuid": "4c63dfc6-9eac-4e79-9f77-8f644ad7147d",
|
||||
"parent_record_id": "RAIL-BS-WP-0002",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "3f042630-eab0-4c6a-9167-e2b28ff20e40",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "e8510646-46ed-4697-a345-f3d3009eea78",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "dab2c07f-8aa0-4635-8df6-857e87e93fc5",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "5c3d40e4-239b-488e-9519-6f7a38d2325f",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T05",
|
||||
"uuid": "7f9e0e58-a130-467a-a2d0-b3f2564e496f",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md",
|
||||
"sha256": "4dc2f61e15adc3c5c81f9ecf8b933512275b95e8f829fac52ae7cde1385cea4a",
|
||||
"record_id": "RAIL-BS-WP-0003",
|
||||
"uuid": "7ee9ee22-1fae-4567-9194-8d70a9e0f45b",
|
||||
"parent_record_id": "RAIL-BS-WP-0003",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "6841c93a-f146-47eb-9f7c-8fa0e02c1bbc",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "ab166073-30a7-4702-a037-4091e8706e20",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "140da396-8e30-4f4d-b88c-c42c0cd46c01",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "d8a3ba40-fda0-4c1f-a9f1-ffcd621a5b3d",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md",
|
||||
"sha256": "04f787078232395bd2d43861de16c521848559fa286f06e1caa9cb779aa71ac3",
|
||||
"record_id": "RAIL-BS-WP-0004",
|
||||
"uuid": "7e8b0c20-51eb-40c9-9e3b-85dd380d7625",
|
||||
"parent_record_id": "RAIL-BS-WP-0004",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "4526a842-ea31-4874-9231-92ab556cfe7b",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "a6313e06-1976-46a7-8e31-df4eb2eca880",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "05d42a55-921f-4aa7-bb76-e8af9c7e0ac3",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "08233868-d522-4117-bc4e-6c0f52545665",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T05",
|
||||
"uuid": "2d5acff7-4a4e-4ddd-ad06-08237ad3dac8",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T06",
|
||||
"uuid": "f8e4a094-c367-40eb-b895-da17bc144b07",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
}
|
||||
]''')
|
||||
|
||||
|
||||
def bound_sources(repo_root: Path, repo_slug: str):
|
||||
entries = [entry for entry in BINDINGS if entry["repo"] == repo_slug]
|
||||
result = []
|
||||
for entry in entries:
|
||||
path = repo_root / entry["path"]
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("Historical source binding missing or symlinked: " + entry["path"])
|
||||
data = path.read_bytes()
|
||||
if sha256(data).hexdigest() != entry["sha256"]:
|
||||
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")
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: state-hub
|
||||
description: State Hub API service for private Railiance operation
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "main-51bc70d"
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
# GENERATED — do not edit.
|
||||
# Synced from the-custodian/canon/standards/repo-classification.allowed.yaml
|
||||
# by scripts/sync_classification_allowed.py (CUST-WP-0067-T09).
|
||||
# Canon is authoritative; this copy exists only so the container has one.
|
||||
# Machine-readable allowed-values for the Repo Classification Standard.
|
||||
#
|
||||
# Single source of truth for the standard's controlled vocabularies, derived
|
||||
# from canon/standards/repo-classification-standard_v1.0.md. Consumed by:
|
||||
# - the per-repo .repo-classification.yaml linter (tools/validate_repo_classification.py)
|
||||
# - the State Hub registration validator (CUST-WP-0050 T04)
|
||||
#
|
||||
# When the standard's vocabularies change, update this file and bump `version`
|
||||
# to match the standard version. CUST-WP-0050 T01.
|
||||
|
||||
standard: "Repo Classification Standard"
|
||||
version: "1.1"
|
||||
canon_id: "canon-repo-classification"
|
||||
|
||||
# category — exactly 1 required (§5)
|
||||
categories:
|
||||
- experimental
|
||||
- research
|
||||
- project
|
||||
- tooling
|
||||
- product
|
||||
- business
|
||||
- publication
|
||||
|
||||
# domain / secondary_domains — primary exactly 1; secondaries 0..n (§6)
|
||||
domains:
|
||||
- infotech
|
||||
- financials
|
||||
- communication
|
||||
- consumer
|
||||
- health
|
||||
- industrials
|
||||
- energy
|
||||
- utilities
|
||||
- materials
|
||||
- realestate
|
||||
- crypto
|
||||
- agents
|
||||
- space
|
||||
- government
|
||||
|
||||
# business_stake — 0..n; 2..6 recommended (§8)
|
||||
business_stake:
|
||||
- execution
|
||||
- intelligence
|
||||
- finance
|
||||
- legal
|
||||
- sales
|
||||
- experience
|
||||
- technology
|
||||
- operations
|
||||
- product
|
||||
- people
|
||||
- procurement
|
||||
- sustainability
|
||||
- automation
|
||||
|
||||
# business_mechanics — 0..n, optional (§9)
|
||||
business_mechanics:
|
||||
- intention
|
||||
- control
|
||||
- coordination
|
||||
- operation
|
||||
- adaptation
|
||||
|
||||
# capability_tags are intentionally OPEN-ENDED (§7): lowercase kebab-case, not
|
||||
# restricted to this set. The families below are the standard's recommended
|
||||
# canonical tags — used to warn on likely synonyms/typos, never to reject.
|
||||
capability_families:
|
||||
identity_and_access:
|
||||
- identity
|
||||
- authentication
|
||||
- authorization
|
||||
- access-control
|
||||
- user-management
|
||||
- tenancy
|
||||
knowledge_and_evidence:
|
||||
- knowledge
|
||||
- citations
|
||||
- evidence
|
||||
- source-management
|
||||
- traceability
|
||||
- documentation
|
||||
- decision-support
|
||||
- history
|
||||
- participation
|
||||
platform_and_operations:
|
||||
- platform
|
||||
- deployment
|
||||
- operations
|
||||
- observability
|
||||
- feature-control
|
||||
- configuration
|
||||
- orchestration
|
||||
market_and_coordination:
|
||||
- marketplace
|
||||
- pricing
|
||||
- monetization
|
||||
- reputation
|
||||
- challenges
|
||||
- bounties
|
||||
- collaboration
|
||||
- coordination
|
||||
product_and_lifecycle:
|
||||
- product-development
|
||||
- lifecycle
|
||||
governance_and_control:
|
||||
- governance
|
||||
- policy
|
||||
- compliance
|
||||
- risk
|
||||
- audit
|
||||
- control
|
||||
games_and_entertainment:
|
||||
- game
|
||||
- entertainment
|
||||
|
||||
# Validation guidance (advisory bounds the linter applies as warnings)
|
||||
guidance:
|
||||
secondary_domains_max: 3
|
||||
business_stake_recommended_min: 2
|
||||
business_stake_recommended_max: 6
|
||||
capability_tag_pattern: "^[a-z0-9]+(-[a-z0-9]+)*$"
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# Production values for the State Hub Railiance chart handoff.
|
||||
# Non-secret values only. DATABASE_URL comes from the Secret `state-hub-env`.
|
||||
|
||||
namespace:
|
||||
create: false
|
||||
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/state-hub
|
||||
tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015"
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# MCP layer on central (CUST-WP-0067-T08). Enabled together with the image tag
|
||||
# above: MCP_HOST landed in main-6c1262e, and enabling this on an older image
|
||||
# would deploy a pod that binds loopback and never becomes reachable.
|
||||
# ClusterIP only — no ingress, same reason the API's is disabled.
|
||||
mcp:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
# 2026-09-10: 1,078 observed 5-minute samples, p99 2.35m, peak 30.45m.
|
||||
# Preserve the 500m limit for bursts; free 40m for the API rollout surge.
|
||||
cpu: 10m
|
||||
|
||||
config:
|
||||
# This deployment is the authoritative hub. Callers verify this rather than
|
||||
# trusting that whatever answered on a port is central (CUST-WP-0067-T03).
|
||||
instanceRole: primary
|
||||
instanceLabel: railiance01
|
||||
sbomNexusUrl: "http://sbom-nexus.sbom-nexus.svc.cluster.local:8010"
|
||||
# Reversible T04 read cutover; set back to `legacy` to roll back.
|
||||
sbomNexusReadMode: nexus
|
||||
# Reversible T04 write cutover; set back to `legacy` to roll back future writes.
|
||||
sbomNexusWriteMode: nexus
|
||||
|
||||
resources:
|
||||
# Restored to the chart's 250m on 2026-09-21. The earlier 100m was a workaround
|
||||
# for a node with <250m unallocated; ~830m of Knative/Kourier requests were
|
||||
# released that day (the-custodian docs/kubernetes-change-gate-decision.md).
|
||||
# State Hub idles near 5m but bursts past 400m under concurrent agent load.
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
sweep:
|
||||
# RMGR-WP-0005-T11: disabled while railiance01 checkouts still target the
|
||||
# stale gitea-remote lineage. Re-enable only after the governed remote
|
||||
# reconciliation and registrar preflight are complete.
|
||||
enabled: false
|
||||
# Present in the live release but previously missing here, so deploying from
|
||||
# this file alone silently dropped it. Inert while enabled is false.
|
||||
hostname: 239.62.205.92.host.secureserver.net
|
||||
hostPath: /home/tegwick
|
||||
sshHostPath: /home/tegwick/.ssh
|
||||
|
||||
# Forge read credential (STATE-WP-0084-T02). Coordinates only — the token lives
|
||||
# in OpenBao at platform/workloads/state-hub/forge-derivation and reaches the
|
||||
# pod through Kubernetes auth, never through this file or a Secret.
|
||||
forgeRead:
|
||||
enabled: true
|
||||
|
||||
# RPF-WP-0035-T04: dedicated ESO signing key, API-only explicit reference.
|
||||
renamePreflight:
|
||||
enabled: true
|
||||
secretName: state-hub-rename-preflight
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sync the canon classification allowed-values into the chart (CUST-WP-0067-T09).
|
||||
|
||||
The State Hub validates repository classification against
|
||||
``the-custodian/canon/standards/repo-classification.allowed.yaml``. That file is
|
||||
canon and must stay authoritative, but a container has no the-custodian
|
||||
checkout, so a copy has to travel with the release.
|
||||
|
||||
A copy that nobody checks is a copy that silently drifts. This script owns the
|
||||
copy: ``--check`` fails when it diverges from canon, so the release can refuse
|
||||
to ship a stale vocabulary rather than validate against yesterday's rules.
|
||||
|
||||
Usage:
|
||||
python scripts/sync_classification_allowed.py # write the copy
|
||||
python scripts/sync_classification_allowed.py --check # verify, exit 1 on drift
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
CHART_COPY = (
|
||||
REPO_ROOT
|
||||
/ "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml"
|
||||
)
|
||||
CANON_CANDIDATES = (
|
||||
Path("/home/worsch/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
Path("/home/tegwick/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
REPO_ROOT.parent / "the-custodian/canon/standards/repo-classification.allowed.yaml",
|
||||
)
|
||||
|
||||
HEADER = (
|
||||
"# GENERATED — do not edit.\n"
|
||||
"# Synced from the-custodian/canon/standards/repo-classification.allowed.yaml\n"
|
||||
"# by scripts/sync_classification_allowed.py (CUST-WP-0067-T09).\n"
|
||||
"# Canon is authoritative; this copy exists only so the container has one.\n"
|
||||
)
|
||||
|
||||
|
||||
def find_canon() -> Path:
|
||||
for candidate in CANON_CANDIDATES:
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
raise SystemExit(
|
||||
"ERROR: canon allowed-values not found. Looked in:\n "
|
||||
+ "\n ".join(str(c) for c in CANON_CANDIDATES)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="Verify the chart copy matches canon; exit 1 on drift",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
canon = find_canon()
|
||||
desired = HEADER + canon.read_text(encoding="utf-8")
|
||||
|
||||
if args.check:
|
||||
if not CHART_COPY.is_file():
|
||||
print(f"DRIFT: {CHART_COPY} is missing; run without --check to create it")
|
||||
return 1
|
||||
current = CHART_COPY.read_text(encoding="utf-8")
|
||||
if current != desired:
|
||||
print(f"DRIFT: chart copy differs from canon ({canon})")
|
||||
sys.stdout.writelines(
|
||||
difflib.unified_diff(
|
||||
current.splitlines(keepends=True),
|
||||
desired.splitlines(keepends=True),
|
||||
fromfile="chart copy",
|
||||
tofile="canon",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(f"OK: chart copy matches canon ({canon})")
|
||||
return 0
|
||||
|
||||
CHART_COPY.parent.mkdir(parents=True, exist_ok=True)
|
||||
CHART_COPY.write_text(desired, encoding="utf-8")
|
||||
print(f"Synced {canon} -> {CHART_COPY}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -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]<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
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
.PHONY: start install install-cli dashboard-install dashboard-check db db-tools migrate seed api dashboard dashboard-local sync check-primary primary-port-clear check check-local test test-python clean register-project register-codex-project register-mcp configure-codex bootstrap-env dev-hub edge-relay mcp-profile validate-adr add-domain rename-domain add-repo list-repos register-path register-from-classification register-from-classification-all cleanup-stale tunnels-up tunnels-status tunnels-check bridges install-hooks install-hooks-all gitea-inventory token-reconcile railiance-state-hub-render railiance-state-hub-client-dry-run railiance-state-hub-server-dry-run railiance-state-hub-headroom
|
||||
|
||||
COMPOSE = docker compose -f infra/docker-compose.yml --env-file .env
|
||||
PYTHON ?= python3
|
||||
HELM ?= $(shell command -v helm 2>/dev/null || if [ -x "$$HOME/.local/bin/helm" ]; then printf "%s" "$$HOME/.local/bin/helm"; else printf "%s" "helm"; fi)
|
||||
KUBECTL ?= $(shell command -v kubectl 2>/dev/null || if [ -x "$$HOME/.local/bin/kubectl" ]; then printf "%s" "$$HOME/.local/bin/kubectl"; else printf "%s" "kubectl"; fi)
|
||||
|
||||
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-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.
|
||||
UV ?= $(shell command -v uv 2>/dev/null || if [ -x "$$HOME/.local/bin/uv" ]; then printf "%s" "$$HOME/.local/bin/uv"; else printf "%s" "uv"; fi)
|
||||
RMGR ?= $(shell command -v rmgr 2>/dev/null || if [ -x "$$HOME/repo-manager/.venv/bin/rmgr" ]; then printf "%s" "$$HOME/repo-manager/.venv/bin/rmgr"; else printf "%s" "rmgr"; fi)
|
||||
STATE_HUB_API_BASE ?= http://127.0.0.1:8000
|
||||
SYNC_PATH ?= .
|
||||
SYNC_PUSH ?= 1
|
||||
|
||||
start:
|
||||
@echo "# Normal production UI access (run in order)"
|
||||
@echo "make bridges # connect local :8000 to the railiance01 primary"
|
||||
@echo "make dashboard # verify the primary, then serve the UI on :3000"
|
||||
@echo "make sync # push file-backed records and reconcile the exact forge commit"
|
||||
@echo ""
|
||||
@echo "# Optional local MCP adapter"
|
||||
@echo "make mcp-http # local SSE adapter on :8001; not needed by the UI"
|
||||
@echo ""
|
||||
@echo "# Local fallback/development only"
|
||||
@echo "make api # local Postgres + API; conflicts with the production :8000 tunnel"
|
||||
@echo "make dashboard-local # dashboard against the deliberate local API"
|
||||
|
||||
install:
|
||||
$(UV) sync
|
||||
|
||||
dashboard/node_modules/.bin/observable: dashboard/package.json dashboard/package-lock.json
|
||||
cd dashboard && npm ci
|
||||
|
||||
dashboard-install: dashboard/node_modules/.bin/observable
|
||||
|
||||
dashboard-check: dashboard-install
|
||||
cd dashboard && npm run build
|
||||
|
||||
## Symlink the custodian CLI into ~/.local/bin so it's on PATH system-wide
|
||||
install-cli: install
|
||||
mkdir -p ~/.local/bin
|
||||
ln -sf "$(shell pwd)/.venv/bin/custodian" ~/.local/bin/custodian
|
||||
ln -sf "$(shell pwd)/.venv/bin/statehub" ~/.local/bin/statehub
|
||||
@echo "Installed: custodian → $$(readlink -f ~/.local/bin/custodian)"
|
||||
@echo "Installed: statehub → $$(readlink -f ~/.local/bin/statehub)"
|
||||
@echo "Make sure ~/.local/bin is on your PATH:"
|
||||
@echo " echo 'export PATH=\"\$$HOME/.local/bin:\$$PATH\"' >> ~/.bashrc && source ~/.bashrc"
|
||||
|
||||
db:
|
||||
$(COMPOSE) up -d postgres
|
||||
|
||||
db-tools:
|
||||
$(COMPOSE) --profile tools up -d
|
||||
|
||||
migrate:
|
||||
$(UV) run alembic upgrade head
|
||||
|
||||
seed:
|
||||
$(UV) run python scripts/seed.py
|
||||
|
||||
## Start (or restart) the MCP SSE server on :8001 — primary transport for Claude Code.
|
||||
## Remote clients (e.g. COULOMBCORE) connect via the ops-bridge tunnel (port 18001).
|
||||
## Registration: claude mcp add-json -s user state-hub '{"type":"sse","url":"http://127.0.0.1:8001/sse"}'
|
||||
mcp-http:
|
||||
@fuser -k 8001/tcp 2>/dev/null && echo "Stopped running MCP server" || true
|
||||
MCP_TRANSPORT=sse MCP_PORT=8001 $(UV) run python mcp_server/server.py
|
||||
|
||||
## Require the production identity before serving the normal dashboard. This
|
||||
## prevents an accidental local `make api` from presenting an empty fallback DB
|
||||
## as the live State Hub.
|
||||
check-primary:
|
||||
@health="$$(curl -fsS --max-time 5 http://127.0.0.1:8000/state/health 2>/dev/null)" || { \
|
||||
echo "ERROR: State Hub primary is not reachable on 127.0.0.1:8000." >&2; \
|
||||
echo "Run 'make bridges' first." >&2; \
|
||||
exit 1; \
|
||||
}; \
|
||||
identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \
|
||||
if [ "$$identity" != "primary/railiance01" ]; then \
|
||||
echo "ERROR: 127.0.0.1:8000 is '$$identity', not the primary/railiance01 State Hub." >&2; \
|
||||
echo "Stop the local API, then run 'make bridges'. Use 'make dashboard-local' only for intentional local development." >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
## Refuse to start the production tunnel when a non-primary API already owns
|
||||
## port 8000. With no listener, bridge is free to establish the tunnel.
|
||||
primary-port-clear:
|
||||
@if health="$$(curl -fsS --max-time 2 http://127.0.0.1:8000/state/health 2>/dev/null)"; then \
|
||||
identity="$$(printf '%s' "$$health" | $(PYTHON) -c 'import json, sys; d=json.load(sys.stdin); print("{}/{}".format(d.get("instance_role", ""), d.get("instance_label", "")))')"; \
|
||||
if [ "$$identity" != "primary/railiance01" ]; then \
|
||||
echo "ERROR: port 8000 is occupied by '$$identity'." >&2; \
|
||||
echo "Stop the local 'make api' process before starting the production tunnel." >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
dashboard: check-primary
|
||||
@fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true
|
||||
$(MAKE) dashboard-install
|
||||
cd dashboard && npm run dev
|
||||
|
||||
## Deliberate local-development dashboard; bypasses the production identity gate.
|
||||
dashboard-local:
|
||||
@echo "WARNING: serving the dashboard against the local/fallback API on :8000."
|
||||
@fuser -k 3000/tcp 2>/dev/null && echo "Stopped running dashboard" || true
|
||||
$(MAKE) dashboard-install
|
||||
cd dashboard && npm run dev
|
||||
|
||||
## Fast work-record path. Repo Manager assigns only missing deterministic IDs,
|
||||
## verifies the primary/railliance01 bridge, and asks central to derive the
|
||||
## exact pushed Forgejo commit in one transactional request.
|
||||
sync:
|
||||
$(RMGR) sync --path "$(SYNC_PATH)" --api-base "$(STATE_HUB_API_BASE)" $(if $(filter 1 true yes,$(SYNC_PUSH)),--push,)
|
||||
|
||||
check: check-primary
|
||||
@echo "State Hub primary/railiance01 is healthy."
|
||||
|
||||
check-local:
|
||||
curl -sf http://127.0.0.1:8000/state/health | python3 -m json.tool
|
||||
|
||||
# CUST-WP-0067-T09. The chart ships a copy of the-custodian canon allowed-values
|
||||
# because a container has no checkout to read it from. Regenerate after any
|
||||
# canon vocabulary change; the -check target fails on drift so a release cannot
|
||||
# validate classification against a stale vocabulary.
|
||||
sync-classification-allowed:
|
||||
python3 scripts/sync_classification_allowed.py
|
||||
|
||||
check-classification-allowed:
|
||||
python3 scripts/sync_classification_allowed.py --check
|
||||
|
||||
# STATE-WP-0091: read-only surge-headroom check. Does not start Helm.
|
||||
# Live: make railiance-state-hub-headroom
|
||||
# Fixtures: make railiance-state-hub-headroom HEADROOM_FIXTURE=tests/fixtures/release_headroom/rev63-105m.json
|
||||
HEADROOM_FIXTURE ?=
|
||||
railiance-state-hub-headroom:
|
||||
@if [ -n "$(HEADROOM_FIXTURE)" ]; then \
|
||||
$(PYTHON) scripts/release_headroom_preflight.py --fixture "$(HEADROOM_FIXTURE)"; \
|
||||
else \
|
||||
$(PYTHON) scripts/release_headroom_preflight.py --kubectl "$(KUBECTL)"; \
|
||||
fi
|
||||
|
||||
railiance-state-hub-render: check-classification-allowed
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG)
|
||||
|
||||
railiance-state-hub-client-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"
|
||||
|
||||
railiance-state-hub-server-dry-run:
|
||||
@set -e; \
|
||||
tmpdir="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmpdir"' EXIT; \
|
||||
$(HELM) template $(RAILIANCE_STATE_HUB_RELEASE) $(RAILIANCE_STATE_HUB_CHART) \
|
||||
--namespace $(RAILIANCE_STATE_HUB_NAMESPACE) \
|
||||
-f $(RAILIANCE_STATE_HUB_VALUES) \
|
||||
--set image.tag=$(RAILIANCE_STATE_HUB_IMAGE_TAG) > "$$tmpdir/state-hub.yaml"; \
|
||||
$(KUBECTL) apply --server-side --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-credentials.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-cluster.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_PLATFORM_DIR)/state-hub-db-networkpolicies.yaml; \
|
||||
$(KUBECTL) apply --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
if $(KUBECTL) get namespace $(RAILIANCE_STATE_HUB_NAMESPACE) >/dev/null 2>&1; then \
|
||||
$(KUBECTL) apply --server-side --dry-run=server -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=server -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \
|
||||
else \
|
||||
echo "Namespace $(RAILIANCE_STATE_HUB_NAMESPACE) does not exist; validating namespaced app manifests with client dry-run."; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-namespace.yaml; \
|
||||
$(KUBECTL) apply --dry-run=client -f $(RAILIANCE_STATE_HUB_APP_MANIFESTS)/state-hub-env.secret.sops.yaml.template; \
|
||||
$(KUBECTL) apply --dry-run=client -n $(RAILIANCE_STATE_HUB_NAMESPACE) -f "$$tmpdir/state-hub.yaml"; \
|
||||
fi
|
||||
|
||||
test: test-python dashboard-check
|
||||
|
||||
test-python:
|
||||
TEST_DATABASE_URL=postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test \
|
||||
$(UV) run pytest -x -q
|
||||
|
||||
## Benchmark /state/summary revision cache (API must be running on :8000)
|
||||
benchmark-summary-cache:
|
||||
$(UV) run python scripts/benchmark_summary_cache.py
|
||||
|
||||
## ops-bridge managed tunnels
|
||||
## Requires ops-bridge: bridge is at /home/worsch/.local/bin/bridge
|
||||
tunnels-up: primary-port-clear
|
||||
bridge up
|
||||
|
||||
tunnels-status:
|
||||
bridge status
|
||||
|
||||
## End-to-end check: verifies SSH process alive + remote port listening on COULOMBCORE.
|
||||
## Exits non-zero if any tunnel is not fully operational.
|
||||
tunnels-check:
|
||||
bridge check
|
||||
|
||||
## Ensure all ops-bridge tunnels are up and healthy.
|
||||
## Brings up any stopped/stale tunnels, shows final status, exits non-zero if anything is still down.
|
||||
bridges: primary-port-clear
|
||||
@echo "==> Bringing up all tunnels..."
|
||||
bridge up
|
||||
@echo ""
|
||||
@echo "==> Tunnel status:"
|
||||
bridge status
|
||||
@echo ""
|
||||
@echo "==> Checking tunnel health..."
|
||||
bridge check
|
||||
|
||||
## Start (or restart) the LOCAL FALLBACK backend — db + migrate + uvicorn.
|
||||
## This replaces anything on :8000, including the production State Hub tunnel.
|
||||
api: db
|
||||
@echo "WARNING: starting the local fallback API; this is not the railiance01 primary."
|
||||
@echo "Waiting for postgres..."; \
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do \
|
||||
nc -z 127.0.0.1 5432 2>/dev/null && break; \
|
||||
sleep 1; \
|
||||
done
|
||||
$(MAKE) migrate
|
||||
@fuser -k 8000/tcp 2>/dev/null && echo "Stopped running API" || true
|
||||
$(UV) run uvicorn api.main:app --reload --reload-dir api --reload-dir mcp_server --reload-dir task_flow_engine --host 127.0.0.1 --port 8000
|
||||
|
||||
## Register a project (Claude Code): make register-project DOMAIN=railiance PROJECT_PATH=/home/worsch/railiance
|
||||
register-project:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-project DOMAIN=<domain> PROJECT_PATH=<path>"; exit 1)
|
||||
@test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)"
|
||||
|
||||
## Register a Codex project (AGENTS.md + HTTP API): make register-codex-project DOMAIN=capabilities PROJECT_PATH=/home/worsch/my-repo
|
||||
register-codex-project:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required. Usage: make register-codex-project DOMAIN=<domain> PROJECT_PATH=<path>"; exit 1)
|
||||
@test -n "$(PROJECT_PATH)" || (echo "ERROR: PROJECT_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(PROJECT_PATH)" --codex
|
||||
|
||||
## Register State Hub MCP for Claude Code. Optional: make register-mcp MCP_URL=http://127.0.0.1:18001/sse
|
||||
register-mcp:
|
||||
scripts/register-mcp.sh \
|
||||
$(if $(MCP_URL),--url "$(MCP_URL)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Configure Codex workspace-write networking for local State Hub access.
|
||||
configure-codex:
|
||||
scripts/configure-codex.sh \
|
||||
$(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(WITH_MCP),--with-mcp,) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Bootstrap a new operator/collaborator environment. Optional: make bootstrap-env ARGS="--install-missing"
|
||||
bootstrap-env:
|
||||
scripts/bootstrap-env.sh $(ARGS)
|
||||
|
||||
## Files-first local dev hub (CUST-WP-0054-T07): postgres + migrate + seed + API + repo registration.
|
||||
## Optional: WITH_EDGE=1 WITH_MCP=1 make dev-hub
|
||||
## Fleet tunnel when reachable: make dev-hub PROFILE=fleet
|
||||
dev-hub:
|
||||
bash scripts/dev_hub_up.sh $(if $(PROFILE),$(PROFILE),dev)
|
||||
|
||||
## Local edge relay for offline write buffering (STATE-WP-0068).
|
||||
edge-relay:
|
||||
@fuser -k 18080/tcp 2>/dev/null && echo "Stopped running edge relay" || true
|
||||
STATEHUB_UPSTREAM_URL=$${STATEHUB_UPSTREAM_URL:-http://127.0.0.1:8000} \
|
||||
$(UV) run uvicorn api.edge.relay:app --host 127.0.0.1 --port 18080
|
||||
|
||||
## Switch MCP/API profile: make mcp-profile PROFILE=dev|fleet|status
|
||||
mcp-profile:
|
||||
@test -n "$(PROFILE)" || (echo "ERROR: PROFILE is required (dev|fleet|status)."; exit 1)
|
||||
bash scripts/mcp_hub_profile.sh "$(PROFILE)"
|
||||
|
||||
## Add a second repo to an existing domain: make add-repo DOMAIN=railiance REPO_PATH=/home/worsch/railiance-infra
|
||||
add-repo:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1)
|
||||
@test -n "$(REPO_PATH)" || (echo "ERROR: REPO_PATH is required."; exit 1)
|
||||
scripts/register_project.sh "$(DOMAIN)" "$(REPO_PATH)" --additional
|
||||
|
||||
## Create a new domain: make add-domain DOMAIN=my_domain NAME="My Domain"
|
||||
add-domain:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required (slug)."; exit 1)
|
||||
@test -n "$(NAME)" || (echo "ERROR: NAME is required (display name)."; exit 1)
|
||||
curl -sf -X POST http://127.0.0.1:8000/domains/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"slug\": \"$(DOMAIN)\", \"name\": \"$(NAME)\"}" | python3 -m json.tool
|
||||
|
||||
## Rename a domain: make rename-domain DOMAIN=old_slug NEW_SLUG=new_slug NEW_NAME="New Name"
|
||||
rename-domain:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN (old slug) is required."; exit 1)
|
||||
@test -n "$(NEW_SLUG)" || (echo "ERROR: NEW_SLUG is required."; exit 1)
|
||||
@test -n "$(NEW_NAME)" || (echo "ERROR: NEW_NAME is required."; exit 1)
|
||||
curl -sf -X PATCH http://127.0.0.1:8000/domains/$(DOMAIN)/rename \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_slug\": \"$(NEW_SLUG)\", \"new_name\": \"$(NEW_NAME)\"}" | python3 -m json.tool
|
||||
|
||||
## Register this machine's local path for a repo: make register-path REPO=marki-docx PATH=/home/tegwick/marki-docx
|
||||
register-path:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make register-path REPO=<slug> PATH=<path>"; exit 1)
|
||||
@test -n "$(PATH)" || (echo "ERROR: PATH is required. Usage: make register-path REPO=<slug> PATH=<path>"; exit 1)
|
||||
curl -sf -X POST "http://127.0.0.1:8000/repos/$(REPO)/paths" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"host\": \"$$(hostname)\", \"path\": \"$(PATH)\"}" | python3 -m json.tool
|
||||
|
||||
## List repos for a domain: make list-repos DOMAIN=railiance
|
||||
list-repos:
|
||||
@test -n "$(DOMAIN)" || (echo "ERROR: DOMAIN is required."; exit 1)
|
||||
curl -sf "http://127.0.0.1:8000/repos/?domain=$(DOMAIN)" | python3 -m json.tool
|
||||
|
||||
## Ingest SBOM data for a repo (all mechanisms: lockfiles + ansible + sbom-tools.yaml).
|
||||
## Auto-detect all sources: make ingest-sbom REPO=the-custodian REPO_PATH=/home/worsch/the-custodian
|
||||
## Single lockfile (explicit): make ingest-sbom REPO=the-custodian LOCKFILE=/path/to/uv.lock
|
||||
## Dry-run (no submit): make ingest-sbom REPO=the-custodian REPO_PATH=... DRY_RUN=1
|
||||
## Tip: run capture-tools first for repos with system-level tool dependencies.
|
||||
ingest-sbom:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_sbom.py --repo "$(REPO)" \
|
||||
$(if $(LOCKFILE),--lockfile "$(LOCKFILE)") \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Ingest capability declarations from SCOPE.md into the catalog.
|
||||
## Usage: make ingest-capabilities REPO=the-custodian [REPO_PATH=/home/worsch/the-custodian]
|
||||
## Or: make ingest-capabilities-all
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
ingest-capabilities:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_capabilities.py --repo "$(REPO)" \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
ingest-capabilities-all:
|
||||
$(UV) run python scripts/ingest_capabilities.py --all \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Check Repository Definition of Integrated (DoI) criteria for a repo.
|
||||
## Usage: make check-doi REPO=llm-connect
|
||||
## Or: make check-doi-all
|
||||
## Add JSON=1 for machine-readable output.
|
||||
check-doi:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/check_doi.py --repo "$(REPO)" $(if $(JSON),--json)
|
||||
|
||||
check-doi-all:
|
||||
$(UV) run python scripts/check_doi.py --all $(if $(JSON),--json)
|
||||
|
||||
## Ingest tpsc.yaml service declarations from a repo into the TPSC catalog.
|
||||
## Usage: make ingest-tpsc REPO=llm-connect
|
||||
## Or: make ingest-tpsc-all
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
ingest-tpsc:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/ingest_tpsc.py --repo "$(REPO)" \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
ingest-tpsc-all:
|
||||
$(UV) run python scripts/ingest_tpsc.py --all \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Run SBOM capture agent for a repo — generates/updates sbom-tools.yaml.
|
||||
## Usage: make capture-tools REPO=railiance-infra [REPO_PATH=/home/worsch/railiance-infra]
|
||||
## Add DRY_RUN=1 to preview without writing.
|
||||
capture-tools:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required."; exit 1)
|
||||
$(UV) run python scripts/capture_sbom_tools.py --repo "$(REPO)" \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)") \
|
||||
$(if $(DRY_RUN),--dry-run)
|
||||
|
||||
## Check a repo for ADR-001 compliance: make validate-adr REPO=/path/to/repo [DOMAIN=custodian]
|
||||
validate-adr:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make validate-adr REPO=<path> [DOMAIN=<slug>]"; exit 1)
|
||||
$(UV) run python scripts/validate_repo_adr.py "$(REPO)" $(if $(DOMAIN),--domain "$(DOMAIN)",)
|
||||
|
||||
## Consistency exit contract:
|
||||
## - Direct scripts/consistency_check.py: 0 clean, 2 warnings-only, 1 failures.
|
||||
## - Agent/operator Make wrappers below normalize warning-only 2 to shell success
|
||||
## while preserving visible WARN output and keeping real failures non-zero.
|
||||
## Check a single repo for ADR-001 consistency: make check-consistency REPO=the-custodian [REPO_PATH=/override]
|
||||
## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures
|
||||
check-consistency:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make check-consistency REPO=<slug>"; exit 1)
|
||||
$(UV) run python scripts/consistency_check.py --repo "$(REPO)" \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check and auto-fix a single repo: make fix-consistency REPO=the-custodian [REPO_PATH=/override]
|
||||
## Exit 0 = clean or warnings-only (warnings stay visible), exit 1 = failures
|
||||
fix-consistency:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make fix-consistency REPO=<slug>"; exit 1)
|
||||
$(UV) run python scripts/consistency_check.py --repo "$(REPO)" --fix \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(REPO_PATH),--repo-path "$(REPO_PATH)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Normalize workplan frontmatter and task status literals in attached repos.
|
||||
## Usage: make normalize-attached-workplans REPO=artifact-store
|
||||
## make normalize-attached-workplans DIRTY=1
|
||||
normalize-attached-workplans:
|
||||
$(UV) run python scripts/normalize_attached_repo_workplans.py \
|
||||
$(if $(REPO),--repo "$(REPO)",) \
|
||||
$(if $(DIRTY),--dirty,) \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
@test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO=<slug> or DIRTY=1"; exit 1)
|
||||
|
||||
## Regenerate AGENTS.md / CLAUDE.md / .claude/rules from templates.
|
||||
## Usage: make update-agent-instructions REPO=artifact-store
|
||||
## make update-agent-instructions DIRTY=1
|
||||
update-agent-instructions:
|
||||
$(UV) run python scripts/update_agent_instruction_files.py \
|
||||
$(if $(REPO),--repo "$(REPO)",) \
|
||||
$(if $(DIRTY),--dirty,)
|
||||
@test -n "$(REPO)$(DIRTY)" || (echo "ERROR: set REPO=<slug> or DIRTY=1"; exit 1)
|
||||
|
||||
## Reconcile measured token sources against State Hub.
|
||||
## Usage: make token-reconcile [SINCE=2026-05-19] [APPLY=1] [ZERO_FALLBACKS=1]
|
||||
token-reconcile:
|
||||
$(PYTHON) scripts/token_reconcile.py \
|
||||
$(if $(SINCE),--since "$(SINCE)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(CODEX_HOME),--codex-home "$(CODEX_HOME)",) \
|
||||
$(if $(CLAUDE_HOME),--claude-home "$(CLAUDE_HOME)",) \
|
||||
$(if $(APPLY),--apply,) \
|
||||
$(if $(ZERO_FALLBACKS),--zero-superseded-fallbacks,)
|
||||
|
||||
## Pull then fix: single repo or all repos if REPO omitted
|
||||
## make fix-consistency-remote — smart pull+fix all repos that need it
|
||||
## make fix-consistency-remote REPO=slug — pull+fix one repo
|
||||
fix-consistency-remote:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
$(if $(REPO),--repo "$(REPO)",--all) \
|
||||
--remote \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",) \
|
||||
$(if $(NO_WRITEBACK),--no-writeback,); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Infer repo slug from git remote URL and check: make check-consistency-here [REPO_PATH=/path/to/repo]
|
||||
## Omit REPO_PATH to use the Python script's CWD (i.e. pass an empty --here flag).
|
||||
check-consistency-here:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
--here $(if $(REPO_PATH),"$(REPO_PATH)",) \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Infer repo slug from git remote URL and fix: make fix-consistency-here [REPO_PATH=/path/to/repo]
|
||||
fix-consistency-here:
|
||||
$(UV) run python scripts/consistency_check.py \
|
||||
--here $(if $(REPO_PATH),"$(REPO_PATH)",) \
|
||||
--fix \
|
||||
$(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check all registered repos for ADR-001 consistency
|
||||
check-consistency-all:
|
||||
$(UV) run python scripts/consistency_check.py --all $(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Check and auto-fix all registered repos
|
||||
fix-consistency-all:
|
||||
$(UV) run python scripts/consistency_check.py --all --fix $(if $(API_BASE),--api-base "$(API_BASE)",); \
|
||||
e=$$?; [ $$e -eq 2 ] && exit 0 || exit $$e
|
||||
|
||||
## Cancel open tasks belonging to completed/archived workstreams.
|
||||
## Safe to run at any time; also suitable for a daily cron job.
|
||||
## Cron example: 0 3 * * * cd ~/state-hub && make cleanup-stale
|
||||
cleanup-stale:
|
||||
$(UV) run python scripts/cleanup_stale_tasks.py
|
||||
|
||||
## Install custodian post-commit sync hook into one repo: make install-hooks REPO=marki-docx
|
||||
install-hooks:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make install-hooks REPO=<slug>"; exit 1)
|
||||
bash scripts/install_hooks.sh --repo "$(REPO)"
|
||||
|
||||
## Install custodian post-commit sync hook into all active registered repos
|
||||
install-hooks-all:
|
||||
bash scripts/install_hooks.sh --all
|
||||
|
||||
## Remove custodian post-commit sync hook from one repo: make remove-hooks REPO=marki-docx
|
||||
remove-hooks:
|
||||
@test -n "$(REPO)" || (echo "ERROR: REPO is required. Usage: make remove-hooks REPO=<slug>"; exit 1)
|
||||
bash scripts/install_hooks.sh --repo "$(REPO)" --remove
|
||||
|
||||
## Compare Gitea coulomb org repos against state-hub registered repos
|
||||
## Requires GITEA_TOKEN in env or .env: make gitea-inventory GITEA_TOKEN=<token>
|
||||
gitea-inventory:
|
||||
$(UV) run python scripts/gitea_inventory.py $(if $(JSON),--json)
|
||||
|
||||
## Register/update one repo from .repo-classification.yaml:
|
||||
## make register-from-classification REPO=state-hub
|
||||
## make register-from-classification PATH=/path/to/repo
|
||||
## Optional: DRY_RUN=1
|
||||
register-from-classification:
|
||||
@test -n "$(REPO)" -o -n "$(PATH)" || (echo "ERROR: REPO or PATH is required."; exit 1)
|
||||
$(UV) run python scripts/register_from_classification.py \
|
||||
$(if $(PATH),--repo-path "$(PATH)",--slug "$(REPO)") \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
## Bulk register/update all active repos with accessible local paths
|
||||
register-from-classification-all:
|
||||
$(UV) run python scripts/register_from_classification.py --bulk \
|
||||
$(if $(DRY_RUN),--dry-run,)
|
||||
|
||||
clean:
|
||||
$(COMPOSE) down -v
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
import socket
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response, status
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
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,
|
||||
WorkplanRead,
|
||||
WorkplanUpdate,
|
||||
)
|
||||
from api.services.lifecycle import transition_workplan_status
|
||||
from api.services.legacy_compat import retire_legacy_route
|
||||
from api.services.wait_kind import annotate_workplans
|
||||
from api.work_record_flavor import (
|
||||
RESIDUAL_FLAVOR,
|
||||
WORK_RECORD_FLAVORS,
|
||||
normalize_flavor,
|
||||
)
|
||||
from api.workplan_status import (
|
||||
is_supported_workplan_status,
|
||||
normalize_workplan_status,
|
||||
ready_review_status,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/workstreams", tags=["workstreams"])
|
||||
workplan_router = APIRouter(prefix="/workplans", tags=["workplans"])
|
||||
|
||||
_INDEX_CACHE: dict[str, Any] | None = None
|
||||
_INDEX_CACHE_AT: float = 0.0
|
||||
_INDEX_TTL = 30.0
|
||||
_INDEX_REFRESH_TASK: asyncio.Task | None = None
|
||||
_INDEX_LAST_ERROR: str | None = None
|
||||
|
||||
_COMPLETED_WORKPLAN_EVENT = "org.statehub.workplan.completed"
|
||||
|
||||
|
||||
def _repo_path(repo: ManagedRepo) -> Path | None:
|
||||
hostname = socket.gethostname()
|
||||
candidates = []
|
||||
host_paths = repo.host_paths or {}
|
||||
if host_paths.get(hostname):
|
||||
candidates.append(host_paths[hostname])
|
||||
if repo.local_path:
|
||||
candidates.append(repo.local_path)
|
||||
for raw in candidates:
|
||||
path = Path(raw).expanduser()
|
||||
if path.is_dir():
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def _frontmatter(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return {}
|
||||
if not text.startswith("---\n"):
|
||||
return {}
|
||||
end = text.find("\n---", 4)
|
||||
if end == -1:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return yaml.safe_load(text[4:end].strip()) or {}
|
||||
except yaml.YAMLError:
|
||||
return {}
|
||||
|
||||
|
||||
def _legacy_key(method: str, route: str) -> str:
|
||||
return f"rest_api:{method} {route}"
|
||||
|
||||
|
||||
async def _list_workplans(
|
||||
*,
|
||||
topic_id: uuid.UUID | None,
|
||||
repo_id: uuid.UUID | None,
|
||||
repo_goal_id: uuid.UUID | None,
|
||||
status_filter: str | None,
|
||||
owner: str | None,
|
||||
slug: str | None,
|
||||
flavor: str | None = None,
|
||||
include_residuals: bool = True,
|
||||
session: AsyncSession,
|
||||
) -> list[Workplan]:
|
||||
q = select(Workplan)
|
||||
if topic_id:
|
||||
q = q.where(Workplan.topic_id == topic_id)
|
||||
if repo_id:
|
||||
q = q.where(Workplan.repo_id == repo_id)
|
||||
if repo_goal_id:
|
||||
q = q.where(Workplan.repo_goal_id == repo_goal_id)
|
||||
if status_filter:
|
||||
normalised_status = normalize_workplan_status(status_filter)
|
||||
if not is_supported_workplan_status(status_filter):
|
||||
raise HTTPException(status_code=422, detail=f"Unsupported workplan status '{status_filter}'")
|
||||
q = q.where(Workplan.status == normalised_status)
|
||||
if owner:
|
||||
q = q.where(Workplan.owner == owner)
|
||||
if slug:
|
||||
q = q.where(Workplan.slug == slug)
|
||||
wanted = normalize_flavor(flavor)
|
||||
if wanted is not None:
|
||||
if wanted not in WORK_RECORD_FLAVORS:
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=(
|
||||
f"Unknown work-record flavor {wanted!r}; "
|
||||
f"expected one of {', '.join(WORK_RECORD_FLAVORS)}"
|
||||
),
|
||||
)
|
||||
q = q.where(Workplan.flavor == wanted)
|
||||
elif not include_residuals:
|
||||
q = q.where(or_(Workplan.flavor.is_(None), Workplan.flavor != RESIDUAL_FLAVOR))
|
||||
q = q.order_by(
|
||||
Workplan.planning_priority.asc().nullslast(),
|
||||
Workplan.planning_order.asc().nullslast(),
|
||||
Workplan.updated_at.desc(),
|
||||
)
|
||||
result = await session.execute(q)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
async def _build_workplan_index(session: AsyncSession) -> dict[str, Any]:
|
||||
result = await session.execute(
|
||||
select(ManagedRepo).where(ManagedRepo.status == "active").order_by(ManagedRepo.slug)
|
||||
)
|
||||
index: dict[str, Any] = {}
|
||||
for repo in result.scalars().all():
|
||||
root = _repo_path(repo)
|
||||
if root is None:
|
||||
continue
|
||||
for directory, archived in (
|
||||
(root / "workplans", False),
|
||||
(root / "workplans" / "archived", True),
|
||||
):
|
||||
if not directory.is_dir():
|
||||
continue
|
||||
for path in sorted(directory.glob("*.md")):
|
||||
data = _frontmatter(path)
|
||||
workplan_id = data.get("state_hub_workstream_id") or data.get("state_hub_workplan_id")
|
||||
if not workplan_id:
|
||||
continue
|
||||
file_status = normalize_workplan_status(data.get("status", ""))
|
||||
review = (
|
||||
ready_review_status(
|
||||
root,
|
||||
data.get("reviewed_against_commit"),
|
||||
data.get("context_paths"),
|
||||
)
|
||||
if file_status == "ready"
|
||||
else None
|
||||
)
|
||||
index[str(workplan_id)] = {
|
||||
"filename": path.name,
|
||||
"relative_path": str(path.relative_to(root)),
|
||||
"repo_slug": repo.slug,
|
||||
"archived": archived,
|
||||
"status": file_status or None,
|
||||
"needs_review": bool(review and review.needs_review),
|
||||
"health_labels": ["needs_review"] if review and review.needs_review else [],
|
||||
}
|
||||
await _merge_db_backing_index(session, index)
|
||||
return {"workplans": index, "workstreams": index}
|
||||
|
||||
|
||||
async def _merge_db_backing_index(session: AsyncSession, index: dict[str, Any]) -> None:
|
||||
"""Fill index gaps from DB-backed file bindings synced by fix-consistency."""
|
||||
result = await session.execute(
|
||||
select(Workplan, ManagedRepo.slug)
|
||||
.join(ManagedRepo, Workplan.repo_id == ManagedRepo.id)
|
||||
.where(Workplan.backing_filename.isnot(None))
|
||||
)
|
||||
for wp, repo_slug in result.all():
|
||||
key = str(wp.id)
|
||||
if key in index:
|
||||
continue
|
||||
index[key] = {
|
||||
"filename": wp.backing_filename,
|
||||
"relative_path": wp.backing_relative_path,
|
||||
"repo_slug": repo_slug,
|
||||
"archived": bool(wp.backing_archived),
|
||||
"status": normalize_workplan_status(wp.status) if wp.status else None,
|
||||
"needs_review": False,
|
||||
"health_labels": [],
|
||||
}
|
||||
|
||||
|
||||
def _invalidate_workplan_index_cache() -> None:
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT
|
||||
_INDEX_CACHE = None
|
||||
_INDEX_CACHE_AT = 0.0
|
||||
|
||||
|
||||
def _index_with_meta(*, stale: bool, refresh_in_progress: bool) -> dict[str, Any]:
|
||||
age = time.monotonic() - _INDEX_CACHE_AT if _INDEX_CACHE_AT else None
|
||||
return {
|
||||
**(_INDEX_CACHE or {"workplans": {}, "workstreams": {}}),
|
||||
"_meta": {
|
||||
"generated_at": _INDEX_CACHE.get("_meta", {}).get("generated_at") if _INDEX_CACHE else None,
|
||||
"stale": stale,
|
||||
"cache_age_seconds": round(age, 3) if age is not None else None,
|
||||
"refresh_in_progress": refresh_in_progress,
|
||||
"last_error": _INDEX_LAST_ERROR,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def _refresh_workplan_index_background() -> None:
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT, _INDEX_LAST_ERROR
|
||||
from api.database import async_session_factory
|
||||
|
||||
try:
|
||||
async with async_session_factory() as session:
|
||||
index = await _build_workplan_index(session)
|
||||
index["_meta"] = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"stale": False,
|
||||
"cache_age_seconds": 0.0,
|
||||
"refresh_in_progress": False,
|
||||
"last_error": None,
|
||||
}
|
||||
_INDEX_CACHE = index
|
||||
_INDEX_CACHE_AT = time.monotonic()
|
||||
_INDEX_LAST_ERROR = None
|
||||
except Exception as exc:
|
||||
_INDEX_LAST_ERROR = str(exc)
|
||||
|
||||
|
||||
def _ensure_index_refresh_started() -> None:
|
||||
global _INDEX_REFRESH_TASK
|
||||
if _INDEX_REFRESH_TASK is not None and not _INDEX_REFRESH_TASK.done():
|
||||
return
|
||||
_INDEX_REFRESH_TASK = asyncio.create_task(_refresh_workplan_index_background())
|
||||
|
||||
|
||||
async def _workplan_index(
|
||||
*,
|
||||
refresh: bool,
|
||||
session: AsyncSession,
|
||||
) -> dict[str, Any]:
|
||||
"""Map file-backed workplan ids to their local workplan filenames."""
|
||||
global _INDEX_CACHE, _INDEX_CACHE_AT, _INDEX_LAST_ERROR
|
||||
cache_age = time.monotonic() - _INDEX_CACHE_AT if _INDEX_CACHE_AT else None
|
||||
if not refresh and _INDEX_CACHE is not None and cache_age is not None and cache_age < _INDEX_TTL:
|
||||
refresh_running = _INDEX_REFRESH_TASK is not None and not _INDEX_REFRESH_TASK.done()
|
||||
return _index_with_meta(stale=False, refresh_in_progress=refresh_running)
|
||||
|
||||
if not refresh and _INDEX_CACHE is not None:
|
||||
_ensure_index_refresh_started()
|
||||
return _index_with_meta(stale=True, refresh_in_progress=True)
|
||||
|
||||
index = await _build_workplan_index(session)
|
||||
index["_meta"] = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"stale": False,
|
||||
"cache_age_seconds": 0.0,
|
||||
"refresh_in_progress": False,
|
||||
"last_error": None,
|
||||
}
|
||||
_INDEX_CACHE = index
|
||||
_INDEX_CACHE_AT = time.monotonic()
|
||||
_INDEX_LAST_ERROR = None
|
||||
return _INDEX_CACHE
|
||||
|
||||
|
||||
async def _create_workplan(
|
||||
*,
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = Workplan(**body.model_dump())
|
||||
session.add(wp)
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
return wp
|
||||
|
||||
|
||||
async def _get_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
return wp
|
||||
|
||||
|
||||
async def _update_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
update_data = body.model_dump(exclude_unset=True)
|
||||
status_update = update_data.pop("status", None)
|
||||
prev_status = wp.status
|
||||
for field, value in update_data.items():
|
||||
setattr(wp, field, value)
|
||||
if status_update is not None:
|
||||
transition_workplan_status(wp, status_update)
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
|
||||
if normalize_workplan_status(prev_status) != "finished" and wp.status == "finished":
|
||||
await _publish_completion_events(wp, session)
|
||||
|
||||
return wp
|
||||
|
||||
|
||||
async def _archive_workplan(
|
||||
*,
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession,
|
||||
) -> Workplan:
|
||||
wp = await session.get(Workplan, workplan_id)
|
||||
if wp is None:
|
||||
raise HTTPException(status_code=404, detail="Workplan not found")
|
||||
transition_workplan_status(wp, "archived")
|
||||
await session.commit()
|
||||
await session.refresh(wp)
|
||||
return wp
|
||||
|
||||
|
||||
async def _publish_completion_events(wp: Workplan, session: AsyncSession) -> None:
|
||||
workplan_envelope = EventEnvelope.new(
|
||||
_COMPLETED_WORKPLAN_EVENT,
|
||||
attributes={
|
||||
"workplan_id": str(wp.id),
|
||||
"slug": wp.slug,
|
||||
"title": wp.title,
|
||||
"topic_id": str(wp.topic_id) if wp.topic_id else None,
|
||||
"repo_id": str(wp.repo_id) if wp.repo_id else None,
|
||||
"repo_goal_id": str(wp.repo_goal_id) if wp.repo_goal_id else None,
|
||||
},
|
||||
)
|
||||
asyncio.create_task(publish_event(_COMPLETED_WORKPLAN_EVENT, workplan_envelope))
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_410_GONE)
|
||||
async def list_workstreams(
|
||||
request: Request,
|
||||
response: Response,
|
||||
topic_id: uuid.UUID | None = None,
|
||||
repo_id: uuid.UUID | None = None,
|
||||
repo_goal_id: uuid.UUID | None = None,
|
||||
status: str | None = None,
|
||||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
detail="Legacy GET /workstreams/ retired; use GET /workplans/",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/", response_model=list[WorkplanRead])
|
||||
async def list_workplans(
|
||||
topic_id: uuid.UUID | None = None,
|
||||
repo_id: uuid.UUID | None = None,
|
||||
repo_goal_id: uuid.UUID | None = None,
|
||||
status: str | None = None,
|
||||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
flavor: str | None = Query(None),
|
||||
include_residuals: bool = Query(True),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> list[Workplan]:
|
||||
workplans = await _list_workplans(
|
||||
topic_id=topic_id,
|
||||
repo_id=repo_id,
|
||||
repo_goal_id=repo_goal_id,
|
||||
status_filter=status,
|
||||
owner=owner,
|
||||
slug=slug,
|
||||
flavor=flavor,
|
||||
include_residuals=include_residuals,
|
||||
session=session,
|
||||
)
|
||||
return list(await annotate_workplans(session, workplans))
|
||||
|
||||
|
||||
@router.get("/workplan-index", status_code=status.HTTP_410_GONE)
|
||||
async def workplan_index(
|
||||
request: Request,
|
||||
response: Response,
|
||||
refresh: bool = Query(False, description="Force cache invalidation"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/workplan-index"),
|
||||
replacement_ref="/workplans/index",
|
||||
detail="Legacy GET /workstreams/workplan-index retired; use GET /workplans/index",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/index")
|
||||
async def workplan_index_preferred(
|
||||
refresh: bool = Query(False, description="Force cache invalidation"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> dict[str, Any]:
|
||||
return await _workplan_index(refresh=refresh, session=session)
|
||||
|
||||
|
||||
@workplan_router.put("/index/bindings")
|
||||
async def sync_workplan_bindings(
|
||||
body: WorkplanBindingsSync,
|
||||
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
|
||||
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
|
||||
wp.backing_filename = entry.filename
|
||||
wp.backing_relative_path = entry.relative_path
|
||||
wp.backing_archived = entry.archived
|
||||
wp.backing_synced_at = synced_at
|
||||
updated += 1
|
||||
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)
|
||||
async def create_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("POST", "/workstreams/"),
|
||||
replacement_ref="/workplans/",
|
||||
detail="Legacy POST /workstreams/ retired; use POST /workplans/",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.post("/", response_model=WorkplanRead, status_code=status.HTTP_201_CREATED)
|
||||
async def create_workplan(
|
||||
body: WorkplanCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _create_workplan(body=body, session=session)
|
||||
|
||||
|
||||
@router.get("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def get_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("GET", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy GET /workstreams/{workstream_id} retired; use GET /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.get("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def get_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
wp = await _get_workplan(workplan_id=workplan_id, session=session)
|
||||
await annotate_workplans(session, [wp])
|
||||
return wp
|
||||
|
||||
|
||||
@router.patch("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def update_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("PATCH", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="/workplans/{workplan_id}",
|
||||
detail="Legacy PATCH /workstreams/{workstream_id} retired; use PATCH /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.patch("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def update_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
body: WorkplanUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _update_workplan(workplan_id=workplan_id, body=body, session=session)
|
||||
|
||||
|
||||
@router.delete("/{workstream_id}", status_code=status.HTTP_410_GONE)
|
||||
async def archive_workstream(
|
||||
request: Request,
|
||||
response: Response,
|
||||
workstream_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> None:
|
||||
await retire_legacy_route(
|
||||
session=session,
|
||||
request=request,
|
||||
response=response,
|
||||
interface_key=_legacy_key("DELETE", "/workstreams/{workstream_id}"),
|
||||
replacement_ref="DELETE /workplans/{workplan_id}",
|
||||
detail="Legacy DELETE /workstreams/{workstream_id} retired; use DELETE /workplans/{workplan_id}",
|
||||
)
|
||||
|
||||
|
||||
@workplan_router.delete("/{workplan_id}", response_model=WorkplanRead)
|
||||
async def archive_workplan(
|
||||
workplan_id: uuid.UUID,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Workplan:
|
||||
return await _archive_workplan(workplan_id=workplan_id, session=session)
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
"""Exact closed source bindings predating canonical Forge identity.
|
||||
|
||||
These are frozen historical source views, not aliases or UUID allocation rules.
|
||||
An altered/missing source requires explicit owner review; generic source UUIDs
|
||||
remain untrusted. Shared Activity views represent the one archived parent.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
import json
|
||||
|
||||
BINDINGS = json.loads(r'''[
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/archived/260707-custodian-WP-0003-event-bridge.md",
|
||||
"sha256": "83ea8bcb151e003419b73c59dbd028f4a1ce3d1120a82e4a128a772503ff3b76",
|
||||
"record_id": "custodian-WP-0003",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0001-temporal-backbone.md",
|
||||
"sha256": "020e2eac973f2daa41c1687f8e867da9d521f945547e6aa82c87993b3ff6fc64",
|
||||
"record_id": "custodian-WP-0001",
|
||||
"uuid": "e22516cc-4fdf-4dad-a928-a9e04a9fa7c7",
|
||||
"parent_record_id": "custodian-WP-0001"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0002-triggers-ops.md",
|
||||
"sha256": "d8cbc61631c01b6f11a03a63f2fb6275fc853ff7f7459ce65973ed1a233daa01",
|
||||
"record_id": "custodian-WP-0002",
|
||||
"uuid": "3a4f47d9-8bc1-434e-acb4-bed5d4dacda0",
|
||||
"parent_record_id": "custodian-WP-0002"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md",
|
||||
"sha256": "ffa6682e9ba3aaf559bd44203d561c90dea69bcf1b1e778209a7b209e6e5cdd3",
|
||||
"record_id": "custodian-WP-0003a",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md",
|
||||
"sha256": "4fc34e0f821e2df51e5c08c7ab34da35b65690381d01f0cfdf55d00c970b414c",
|
||||
"record_id": "custodian-WP-0003b",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003c-event-bridge-context-integration.md",
|
||||
"sha256": "b6cd62b26515e509670ca39736b6bcec9633beedd46c9bd8f7598c06bac7276b",
|
||||
"record_id": "custodian-WP-0003c",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0004-railiance-ops.md",
|
||||
"sha256": "20df02eea740e109c937130e7f8801a636fc0990ed8c6d2ced55659f8957adb1",
|
||||
"record_id": "custodian-WP-0004",
|
||||
"uuid": "759b1255-aa78-42b5-8fab-c5fcf168f5f4",
|
||||
"parent_record_id": "custodian-WP-0004"
|
||||
},
|
||||
{
|
||||
"repo": "the-custodian",
|
||||
"path": "workplans/ADHOC-2026-08-25-retired.md",
|
||||
"sha256": "344a1293dbb3a67a944eb34ca4a0337487d8f047226027a2107922346fbab2dd",
|
||||
"record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED",
|
||||
"uuid": "1c53d3db-6633-5e2d-987f-10706d73be9e",
|
||||
"parent_record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED"
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0002-k3s-baseline.md",
|
||||
"sha256": "8d6f8533c119117686bc0d5bb9ed5349726aa6c3189ff98b53409c68ac685ca9",
|
||||
"record_id": "RAIL-BS-WP-0002",
|
||||
"uuid": "4c63dfc6-9eac-4e79-9f77-8f644ad7147d",
|
||||
"parent_record_id": "RAIL-BS-WP-0002",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "3f042630-eab0-4c6a-9167-e2b28ff20e40",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "e8510646-46ed-4697-a345-f3d3009eea78",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "dab2c07f-8aa0-4635-8df6-857e87e93fc5",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "5c3d40e4-239b-488e-9519-6f7a38d2325f",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T05",
|
||||
"uuid": "7f9e0e58-a130-467a-a2d0-b3f2564e496f",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0003-pgpool-ha-failover-fix.md",
|
||||
"sha256": "4dc2f61e15adc3c5c81f9ecf8b933512275b95e8f829fac52ae7cde1385cea4a",
|
||||
"record_id": "RAIL-BS-WP-0003",
|
||||
"uuid": "7ee9ee22-1fae-4567-9194-8d70a9e0f45b",
|
||||
"parent_record_id": "RAIL-BS-WP-0003",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "6841c93a-f146-47eb-9f7c-8fa0e02c1bbc",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "ab166073-30a7-4702-a037-4091e8706e20",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "140da396-8e30-4f4d-b88c-c42c0cd46c01",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "d8a3ba40-fda0-4c1f-a9f1-ffcd621a5b3d",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-cluster",
|
||||
"path": "workplans/archived/260622-RAIL-BS-WP-0004-safety-net.md",
|
||||
"sha256": "04f787078232395bd2d43861de16c521848559fa286f06e1caa9cb779aa71ac3",
|
||||
"record_id": "RAIL-BS-WP-0004",
|
||||
"uuid": "7e8b0c20-51eb-40c9-9e3b-85dd380d7625",
|
||||
"parent_record_id": "RAIL-BS-WP-0004",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "T01",
|
||||
"uuid": "4526a842-ea31-4874-9231-92ab556cfe7b",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T02",
|
||||
"uuid": "a6313e06-1976-46a7-8e31-df4eb2eca880",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T03",
|
||||
"uuid": "05d42a55-921f-4aa7-bb76-e8af9c7e0ac3",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T04",
|
||||
"uuid": "08233868-d522-4117-bc4e-6c0f52545665",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T05",
|
||||
"uuid": "2d5acff7-4a4e-4ddd-ad06-08237ad3dac8",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"label": "T06",
|
||||
"uuid": "f8e4a094-c367-40eb-b895-da17bc144b07",
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
}
|
||||
]''')
|
||||
|
||||
|
||||
def bound_sources(repo_root: Path, repo_slug: str):
|
||||
entries = [entry for entry in BINDINGS if entry["repo"] == repo_slug]
|
||||
result = []
|
||||
for entry in entries:
|
||||
path = repo_root / entry["path"]
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("Historical source binding missing or symlinked: " + entry["path"])
|
||||
data = path.read_bytes()
|
||||
if sha256(data).hexdigest() != entry["sha256"]:
|
||||
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")
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: state-hub
|
||||
description: State Hub API service for private Railiance operation
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "main-51bc70d"
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
# GENERATED — do not edit.
|
||||
# Synced from the-custodian/canon/standards/repo-classification.allowed.yaml
|
||||
# by scripts/sync_classification_allowed.py (CUST-WP-0067-T09).
|
||||
# Canon is authoritative; this copy exists only so the container has one.
|
||||
# Machine-readable allowed-values for the Repo Classification Standard.
|
||||
#
|
||||
# Single source of truth for the standard's controlled vocabularies, derived
|
||||
# from canon/standards/repo-classification-standard_v1.0.md. Consumed by:
|
||||
# - the per-repo .repo-classification.yaml linter (tools/validate_repo_classification.py)
|
||||
# - the State Hub registration validator (CUST-WP-0050 T04)
|
||||
#
|
||||
# When the standard's vocabularies change, update this file and bump `version`
|
||||
# to match the standard version. CUST-WP-0050 T01.
|
||||
|
||||
standard: "Repo Classification Standard"
|
||||
version: "1.1"
|
||||
canon_id: "canon-repo-classification"
|
||||
|
||||
# category — exactly 1 required (§5)
|
||||
categories:
|
||||
- experimental
|
||||
- research
|
||||
- project
|
||||
- tooling
|
||||
- product
|
||||
- business
|
||||
- publication
|
||||
|
||||
# domain / secondary_domains — primary exactly 1; secondaries 0..n (§6)
|
||||
domains:
|
||||
- infotech
|
||||
- financials
|
||||
- communication
|
||||
- consumer
|
||||
- health
|
||||
- industrials
|
||||
- energy
|
||||
- utilities
|
||||
- materials
|
||||
- realestate
|
||||
- crypto
|
||||
- agents
|
||||
- space
|
||||
- government
|
||||
|
||||
# business_stake — 0..n; 2..6 recommended (§8)
|
||||
business_stake:
|
||||
- execution
|
||||
- intelligence
|
||||
- finance
|
||||
- legal
|
||||
- sales
|
||||
- experience
|
||||
- technology
|
||||
- operations
|
||||
- product
|
||||
- people
|
||||
- procurement
|
||||
- sustainability
|
||||
- automation
|
||||
|
||||
# business_mechanics — 0..n, optional (§9)
|
||||
business_mechanics:
|
||||
- intention
|
||||
- control
|
||||
- coordination
|
||||
- operation
|
||||
- adaptation
|
||||
|
||||
# capability_tags are intentionally OPEN-ENDED (§7): lowercase kebab-case, not
|
||||
# restricted to this set. The families below are the standard's recommended
|
||||
# canonical tags — used to warn on likely synonyms/typos, never to reject.
|
||||
capability_families:
|
||||
identity_and_access:
|
||||
- identity
|
||||
- authentication
|
||||
- authorization
|
||||
- access-control
|
||||
- user-management
|
||||
- tenancy
|
||||
knowledge_and_evidence:
|
||||
- knowledge
|
||||
- citations
|
||||
- evidence
|
||||
- source-management
|
||||
- traceability
|
||||
- documentation
|
||||
- decision-support
|
||||
- history
|
||||
- participation
|
||||
platform_and_operations:
|
||||
- platform
|
||||
- deployment
|
||||
- operations
|
||||
- observability
|
||||
- feature-control
|
||||
- configuration
|
||||
- orchestration
|
||||
market_and_coordination:
|
||||
- marketplace
|
||||
- pricing
|
||||
- monetization
|
||||
- reputation
|
||||
- challenges
|
||||
- bounties
|
||||
- collaboration
|
||||
- coordination
|
||||
product_and_lifecycle:
|
||||
- product-development
|
||||
- lifecycle
|
||||
governance_and_control:
|
||||
- governance
|
||||
- policy
|
||||
- compliance
|
||||
- risk
|
||||
- audit
|
||||
- control
|
||||
games_and_entertainment:
|
||||
- game
|
||||
- entertainment
|
||||
|
||||
# Validation guidance (advisory bounds the linter applies as warnings)
|
||||
guidance:
|
||||
secondary_domains_max: 3
|
||||
business_stake_recommended_min: 2
|
||||
business_stake_recommended_max: 6
|
||||
capability_tag_pattern: "^[a-z0-9]+(-[a-z0-9]+)*$"
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# Production values for the State Hub Railiance chart handoff.
|
||||
# Non-secret values only. DATABASE_URL comes from the Secret `state-hub-env`.
|
||||
|
||||
namespace:
|
||||
create: false
|
||||
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/state-hub
|
||||
tag: "main-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015"
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# MCP layer on central (CUST-WP-0067-T08). Enabled together with the image tag
|
||||
# above: MCP_HOST landed in main-6c1262e, and enabling this on an older image
|
||||
# would deploy a pod that binds loopback and never becomes reachable.
|
||||
# ClusterIP only — no ingress, same reason the API's is disabled.
|
||||
mcp:
|
||||
enabled: true
|
||||
resources:
|
||||
requests:
|
||||
# 2026-09-10: 1,078 observed 5-minute samples, p99 2.35m, peak 30.45m.
|
||||
# Preserve the 500m limit for bursts; free 40m for the API rollout surge.
|
||||
cpu: 10m
|
||||
|
||||
config:
|
||||
# This deployment is the authoritative hub. Callers verify this rather than
|
||||
# trusting that whatever answered on a port is central (CUST-WP-0067-T03).
|
||||
instanceRole: primary
|
||||
instanceLabel: railiance01
|
||||
sbomNexusUrl: "http://sbom-nexus.sbom-nexus.svc.cluster.local:8010"
|
||||
# Reversible T04 read cutover; set back to `legacy` to roll back.
|
||||
sbomNexusReadMode: nexus
|
||||
# Reversible T04 write cutover; set back to `legacy` to roll back future writes.
|
||||
sbomNexusWriteMode: nexus
|
||||
|
||||
resources:
|
||||
# Restored to the chart's 250m on 2026-09-21. The earlier 100m was a workaround
|
||||
# for a node with <250m unallocated; ~830m of Knative/Kourier requests were
|
||||
# released that day (the-custodian docs/kubernetes-change-gate-decision.md).
|
||||
# State Hub idles near 5m but bursts past 400m under concurrent agent load.
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
sweep:
|
||||
# RMGR-WP-0005-T11: disabled while railiance01 checkouts still target the
|
||||
# stale gitea-remote lineage. Re-enable only after the governed remote
|
||||
# reconciliation and registrar preflight are complete.
|
||||
enabled: false
|
||||
# Present in the live release but previously missing here, so deploying from
|
||||
# this file alone silently dropped it. Inert while enabled is false.
|
||||
hostname: 239.62.205.92.host.secureserver.net
|
||||
hostPath: /home/tegwick
|
||||
sshHostPath: /home/tegwick/.ssh
|
||||
|
||||
# Forge read credential (STATE-WP-0084-T02). Coordinates only — the token lives
|
||||
# in OpenBao at platform/workloads/state-hub/forge-derivation and reaches the
|
||||
# pod through Kubernetes auth, never through this file or a Secret.
|
||||
forgeRead:
|
||||
enabled: true
|
||||
|
||||
# RPF-WP-0035-T04: dedicated ESO signing key, API-only explicit reference.
|
||||
renamePreflight:
|
||||
enabled: true
|
||||
secretName: state-hub-rename-preflight
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Sync the canon classification allowed-values into the chart (CUST-WP-0067-T09).
|
||||
|
||||
The State Hub validates repository classification against
|
||||
``the-custodian/canon/standards/repo-classification.allowed.yaml``. That file is
|
||||
canon and must stay authoritative, but a container has no the-custodian
|
||||
checkout, so a copy has to travel with the release.
|
||||
|
||||
A copy that nobody checks is a copy that silently drifts. This script owns the
|
||||
copy: ``--check`` fails when it diverges from canon, so the release can refuse
|
||||
to ship a stale vocabulary rather than validate against yesterday's rules.
|
||||
|
||||
Usage:
|
||||
python scripts/sync_classification_allowed.py # write the copy
|
||||
python scripts/sync_classification_allowed.py --check # verify, exit 1 on drift
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
CHART_COPY = (
|
||||
REPO_ROOT
|
||||
/ "deploy/railiance/apps/charts/state-hub/files/repo-classification.allowed.yaml"
|
||||
)
|
||||
CANON_CANDIDATES = (
|
||||
Path("/home/worsch/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
Path("/home/tegwick/the-custodian/canon/standards/repo-classification.allowed.yaml"),
|
||||
REPO_ROOT.parent / "the-custodian/canon/standards/repo-classification.allowed.yaml",
|
||||
)
|
||||
|
||||
HEADER = (
|
||||
"# GENERATED — do not edit.\n"
|
||||
"# Synced from the-custodian/canon/standards/repo-classification.allowed.yaml\n"
|
||||
"# by scripts/sync_classification_allowed.py (CUST-WP-0067-T09).\n"
|
||||
"# Canon is authoritative; this copy exists only so the container has one.\n"
|
||||
)
|
||||
|
||||
|
||||
def find_canon() -> Path:
|
||||
for candidate in CANON_CANDIDATES:
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
raise SystemExit(
|
||||
"ERROR: canon allowed-values not found. Looked in:\n "
|
||||
+ "\n ".join(str(c) for c in CANON_CANDIDATES)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"--check",
|
||||
action="store_true",
|
||||
help="Verify the chart copy matches canon; exit 1 on drift",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
canon = find_canon()
|
||||
desired = HEADER + canon.read_text(encoding="utf-8")
|
||||
|
||||
if args.check:
|
||||
if not CHART_COPY.is_file():
|
||||
print(f"DRIFT: {CHART_COPY} is missing; run without --check to create it")
|
||||
return 1
|
||||
current = CHART_COPY.read_text(encoding="utf-8")
|
||||
if current != desired:
|
||||
print(f"DRIFT: chart copy differs from canon ({canon})")
|
||||
sys.stdout.writelines(
|
||||
difflib.unified_diff(
|
||||
current.splitlines(keepends=True),
|
||||
desired.splitlines(keepends=True),
|
||||
fromfile="chart copy",
|
||||
tofile="canon",
|
||||
)
|
||||
)
|
||||
return 1
|
||||
print(f"OK: chart copy matches canon ({canon})")
|
||||
return 0
|
||||
|
||||
CHART_COPY.parent.mkdir(parents=True, exist_ok=True)
|
||||
CHART_COPY.write_text(desired, encoding="utf-8")
|
||||
print(f"Synced {canon} -> {CHART_COPY}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,60 @@
|
|||
"""Real disposable Git proof; does not import owner app or test conftest."""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
SOURCE=Path(__file__).resolve().parents[1]/'scripts/consistency_check.py'
|
||||
def actual_writeback():
|
||||
tree=ast.parse(SOURCE.read_text());nodes=[]
|
||||
for node in tree.body:
|
||||
if isinstance(node,ast.FunctionDef) and node.name in {'_git_identity_config','_git_commit_writeback'}:
|
||||
node.returns=None
|
||||
for arg in node.args.args+node.args.kwonlyargs:arg.annotation=None
|
||||
nodes.append(node)
|
||||
env=dict(subprocess=subprocess,sys=sys,Path=Path)
|
||||
exec(compile(ast.Module(body=nodes,type_ignores=[]),str(SOURCE),'exec'),env)
|
||||
return env['_git_commit_writeback']
|
||||
def git(root,*args):return subprocess.check_output(['git','-C',str(root),*args]).decode().strip()
|
||||
@pytest.fixture
|
||||
def repo(tmp_path):
|
||||
root=tmp_path/'owner';root.mkdir();git(root,'init','-b','main');git(root,'config','user.name','Synthetic Test');git(root,'config','user.email','synthetic@example.invalid');git(root,'config','core.hooksPath','.git/hooks')
|
||||
for name in ['writeback.md','.custodian-brief.md','evidence.json']:(root/name).write_text('original\n')
|
||||
git(root,'add','.');git(root,'commit','-m','baseline')
|
||||
(root/'.custodian-brief.md').write_text('staged unrelated\n');git(root,'add','.custodian-brief.md');(root/'.custodian-brief.md').write_text('working unrelated\n')
|
||||
(root/'evidence.json').write_text('dirty evidence\n')
|
||||
return root
|
||||
|
||||
def test_writeback_commits_only_target_preserves_unrelated_stage_and_worktree(repo):
|
||||
before_index=git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json');before_head=git(repo,'rev-parse','HEAD')
|
||||
target=repo/'writeback.md';target.write_text('new status\n')
|
||||
assert actual_writeback()(str(repo),target,['synthetic status'])
|
||||
assert git(repo,'rev-parse','HEAD')!=before_head
|
||||
assert git(repo,'diff-tree','--no-commit-id','--name-only','-r','HEAD')=='writeback.md'
|
||||
assert git(repo,'show','HEAD:.custodian-brief.md')=='original'
|
||||
assert git(repo,'show',':.custodian-brief.md')=='staged unrelated'
|
||||
assert git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json')==before_index
|
||||
assert (repo/'.custodian-brief.md').read_text()=='working unrelated\n'
|
||||
assert (repo/'evidence.json').read_text()=='dirty evidence\n'
|
||||
|
||||
def test_noop_never_commits_unrelated_staged_change(repo):
|
||||
head=git(repo,'rev-parse','HEAD');index=git(repo,'ls-files','--stage')
|
||||
assert not actual_writeback()(str(repo),repo/'writeback.md',['no change'])
|
||||
assert git(repo,'rev-parse','HEAD')==head and git(repo,'ls-files','--stage')==index
|
||||
|
||||
@pytest.mark.parametrize('name',['new writeback.md','-leading-dash.md'])
|
||||
def test_new_target_and_argument_boundary(repo,name):
|
||||
target=repo/name;target.write_text('new file\n')
|
||||
assert actual_writeback()(str(repo),target,['new synthetic target'])
|
||||
assert git(repo,'diff-tree','--no-commit-id','--name-only','-r','HEAD')==name
|
||||
assert git(repo,'show',':.custodian-brief.md')=='staged unrelated'
|
||||
|
||||
def test_normal_hook_refusal_preserves_unrelated_state(repo):
|
||||
hook=repo/'.git/hooks/pre-commit';hook.write_text('#!/bin/sh\nexit 1\n');hook.chmod(0o755)
|
||||
head=git(repo,'rev-parse','HEAD');index=git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json')
|
||||
target=repo/'writeback.md';target.write_text('new status\n')
|
||||
assert not actual_writeback()(str(repo),target,['refused synthetic change'])
|
||||
assert git(repo,'rev-parse','HEAD')==head
|
||||
assert git(repo,'ls-files','--stage','.custodian-brief.md','evidence.json')==index
|
||||
assert git(repo,'show',':.custodian-brief.md')=='staged unrelated'
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
|
||||
|
||||
T09 source repair refinement, 2026-10-17: the attempted current-client regression
|
||||
actually loaded an older packaged consistency checker from the owner virtualenv,
|
||||
not the reviewed canonical source. That trial again failed parent-row hash
|
||||
preservation; it is retained separately and does not count as R5 client proof.
|
||||
Normal C-23 also legitimately normalized WP18 ready to active and created a
|
||||
source commit, but the generic writeback commit included an unrelated staged
|
||||
brief. A narrow successor scopes git add and git commit --only to the actual
|
||||
writeback file, preserving unrelated index/worktree state. Real disposable Git
|
||||
regression tests cover pre-existing staged changes, hooks, no-op and path names.
|
||||
Verify the actually loaded CLI checker hash after ordinary owner package refresh
|
||||
before any retry; capture legitimate source HEAD transitions rather than assume
|
||||
a static revision. T07 and T10 remain wait. No original parent preimage recovery,
|
||||
new runtime delivery or broader authority follows from this source repair.
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06, STATE-WP-0095-T10]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
|
||||
|
||||
T09 source repair refinement, 2026-10-17: the attempted current-client regression
|
||||
actually loaded an older packaged consistency checker from the owner virtualenv,
|
||||
not the reviewed canonical source. That trial again failed parent-row hash
|
||||
preservation; it is retained separately and does not count as R5 client proof.
|
||||
Normal C-23 also legitimately normalized WP18 ready to active and created a
|
||||
source commit, but the generic writeback commit included an unrelated staged
|
||||
brief. A narrow successor scopes git add and git commit --only to the actual
|
||||
writeback file, preserving unrelated index/worktree state. Real disposable Git
|
||||
regression tests cover pre-existing staged changes, hooks, no-op and path names.
|
||||
Verify the actually loaded CLI checker hash after ordinary owner package refresh
|
||||
before any retry; capture legitimate source HEAD transitions rather than assume
|
||||
a static revision. T07 and T10 remain wait. No original parent preimage recovery,
|
||||
new runtime delivery or broader authority follows from this source repair.
|
||||
|
||||
|
||||
T07 current-baseline client return, 2026-10-17: after the owner refreshed the
|
||||
installed package and verified the actually loaded checker matched the reviewed
|
||||
R6 source, ordinary Cluster deep consistency and fast projection passed.
|
||||
Bindings synced 20/20, omitting the three frozen histories; all three parent
|
||||
and 15 task CURRENT full-row hashes remained unchanged across
|
||||
before-verified-client.json / after-verified-client.json. Unrelated staged brief
|
||||
state was preserved. This proves the corrected installed client stage against
|
||||
its fresh current baseline; earlier parent-hash failures remain retained
|
||||
historical exceptions, not restored preimages or backfilled successes.
|
||||
|
||||
T10 remains wait/needs_human for exact proposal
|
||||
STATE-WP-0095-T10-frozen-bindings-guard-r1 revision 1, metadata
|
||||
sha256:81d365926fc585a186955d8c98ee83501fd3cec12135b2256d71b46a75097932,
|
||||
pending decision df703266-6719-4c04-ae1b-ba9afd3bca1c. The inspected candidate
|
||||
image is main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1,
|
||||
from runtime source c103cbf3c8bed5dba87a03e320595ddded9301a3. Image-only
|
||||
server dry-run passed eight identities with only three image-field changes.
|
||||
The exact proposal and recovery limits are retained in the Custodian dated
|
||||
2026-10-17 guard-successor-review-r1 packet. No new API runtime is approved or
|
||||
delivered by this source-only note; its later workplan commit does not change
|
||||
the image's source pin. T09 stays done, T07/T10 stay wait pending reviewed API
|
||||
delivery and explicit successor/current-baseline acceptance disposition.
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"schema": "state95.proposal-revision-close.v1",
|
||||
"status": "applied",
|
||||
"source_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"remote_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"instance_role": "primary",
|
||||
"instance_label": "railiance01",
|
||||
"expected_commit": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"derived_commit": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"tasks": [
|
||||
{
|
||||
"uuid": "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7",
|
||||
"record_id": "STATE-WP-0095-T01",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "ebb24194-e4cc-5130-aaa6-663f2ea2c822",
|
||||
"record_id": "STATE-WP-0095-T02",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "e8566341-cf6e-5b9c-ad80-8564d14cd52a",
|
||||
"record_id": "STATE-WP-0095-T03",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "3bbb311f-194e-5862-8aed-358a580f22b4",
|
||||
"record_id": "STATE-WP-0095-T04",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "339b9a37-181c-5599-9a89-0393e199bd4b",
|
||||
"record_id": "STATE-WP-0095-T05",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4",
|
||||
"record_id": "STATE-WP-0095-T06",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "cfd1ea99-e75b-542e-a434-acefb62f3c7d",
|
||||
"record_id": "STATE-WP-0095-T07",
|
||||
"status": "wait",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "2dd9b04f-2250-5a8a-b5bc-c8a009872858",
|
||||
"record_id": "STATE-WP-0095-T08",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "8be8f13b-6352-5295-9755-de6fbfb13593",
|
||||
"record_id": "STATE-WP-0095-T09",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "88585c69-d9b9-5e1f-a256-6416ff71e824",
|
||||
"record_id": "STATE-WP-0095-T10",
|
||||
"status": "wait",
|
||||
"needs_human": true
|
||||
}
|
||||
],
|
||||
"whole_unrelated_index_dirty_preserved": true,
|
||||
"proposal_revision": 2,
|
||||
"proposal_status": "pending_unapproved_unexecuted",
|
||||
"original_raw_receipt": "docs/changes/netkingdom-target-2026-10-16/cluster-historical-publication-r1/owner-followthrough-r8/source-commit-result.json",
|
||||
"original_raw_receipt_sha256": "sha256:3291234c856b2dfd4a84f054475d0372bc7cbf7bdee37b6c08a6d327f46ab322",
|
||||
"runtime_changed": false,
|
||||
"new_UUIDs": false
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
{
|
||||
"repo_slug": "state-hub",
|
||||
"repo_path": "/home/worsch/state-hub",
|
||||
"source_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager warning: workplans/: repository uses more than one workplan prefix: ['CUST-WP', 'STATE-WP']",
|
||||
"file_path": "workplans/",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-33",
|
||||
"message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)",
|
||||
"file_path": "WORK-RECORDS.md",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'ACTIVITY-WP-0026' is not linked to State Hub",
|
||||
"file_path": "workplans/STATE-WP-0078-ops-run-read-projection.md",
|
||||
"db_id": "",
|
||||
"file_value": "ACTIVITY-WP-0026",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-39",
|
||||
"message": "Wait task 'STATE-WP-0094-T03' in workplan 'STATE-WP-0094' has neither depends_on, needs_human: true, blocked_on: message-from:\u2026 nor wait_until \u2014 unqualified wait",
|
||||
"file_path": "workplans/STATE-WP-0094-routed-gates-and-owner-returns.md#STATE-WP-0094-T03",
|
||||
"db_id": "",
|
||||
"file_value": "wait",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-39",
|
||||
"message": "Human wait task 'STATE-WP-0095-T10' in workplan 'STATE-WP-0095' has needs_human: true without decision_id \u2014 unrouted human gate",
|
||||
"file_path": "workplans/STATE-WP-0095-historical-source-receiving.md#STATE-WP-0095-T10",
|
||||
"db_id": "",
|
||||
"file_value": "needs_human: true",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'STATE-WP-0095-T10' in workplan 'STATE-WP-0095' \u2014 every depends_on target is terminal (STATE-WP-0095-T09)",
|
||||
"file_path": "workplans/STATE-WP-0095-historical-source-receiving.md#STATE-WP-0095-T10",
|
||||
"db_id": "",
|
||||
"file_value": "STATE-WP-0095-T09",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'adhoc-2026-07-01@retired-20260827' (id=1cdb288a\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "1cdb288a-9bcb-4e53-8c4f-cb43e3abe9c5",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'adhoc-2026-06-04@retired-20260827' (id=2a8f3aff\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "2a8f3aff-8f5d-4e42-b33f-225d60e0b30b",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-25",
|
||||
"message": "Owner-return task 'STATE-WP-0094-T05' waiting on railiance-platform has 1 reply/replies after its last dated note (or unread when undated) \u2014 owner action needed: 639c6969-42ce-4080-b2f8-3eb3bb18c970",
|
||||
"file_path": "workplans/STATE-WP-0094-routed-gates-and-owner-returns.md#STATE-WP-0094-T05",
|
||||
"db_id": "",
|
||||
"file_value": "message-from:railiance-platform",
|
||||
"db_value": "",
|
||||
"fixable": false,
|
||||
"repo_slug": "state-hub",
|
||||
"source_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"source_revision_reason": null,
|
||||
"owner_return": {
|
||||
"task_record_id": "STATE-WP-0094-T05",
|
||||
"message_ids": [
|
||||
"639c6969-42ce-4080-b2f8-3eb3bb18c970"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "2 unread inbox message(s) older than 3 day(s): the-custodian:23172cb2, the-custodian:51e9eace",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "2 unread message(s) lack thread_id for supersession tracking: the-custodian:23172cb2, the-custodian:51e9eace",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'CUST-WP-0003' uses prefix 'CUST-WP' but repo canonical prefix is 'STATE-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/CUST-WP-0003-whi-kpi-card.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0003",
|
||||
"db_value": "STATE-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'CUST-WP-0012' uses prefix 'CUST-WP' but repo canonical prefix is 'STATE-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/CUST-WP-0012-multi-user-onboarding.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0012",
|
||||
"db_value": "STATE-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'CUST-WP-0038' uses prefix 'CUST-WP' but repo canonical prefix is 'STATE-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/CUST-WP-0038-state-hub-threephoenix-ha.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0038",
|
||||
"db_value": "STATE-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'CUST-WP-0042' uses prefix 'CUST-WP' but repo canonical prefix is 'STATE-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/CUST-WP-0042-workplan-state-model-cleanup.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0042",
|
||||
"db_value": "STATE-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'CUST-WP-0011' uses prefix 'CUST-WP' but repo canonical prefix is 'STATE-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260706-CUST-WP-0011-state-hub-threephoenix-migration.md",
|
||||
"db_id": "",
|
||||
"file_value": "CUST-WP-0011",
|
||||
"db_value": "STATE-WP",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 70/70"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 14,
|
||||
"info": 2
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: wait
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06, STATE-WP-0095-T10]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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.
|
||||
|
||||
|
||||
T09 source repair refinement, 2026-10-17: the attempted current-client regression
|
||||
actually loaded an older packaged consistency checker from the owner virtualenv,
|
||||
not the reviewed canonical source. That trial again failed parent-row hash
|
||||
preservation; it is retained separately and does not count as R5 client proof.
|
||||
Normal C-23 also legitimately normalized WP18 ready to active and created a
|
||||
source commit, but the generic writeback commit included an unrelated staged
|
||||
brief. A narrow successor scopes git add and git commit --only to the actual
|
||||
writeback file, preserving unrelated index/worktree state. Real disposable Git
|
||||
regression tests cover pre-existing staged changes, hooks, no-op and path names.
|
||||
Verify the actually loaded CLI checker hash after ordinary owner package refresh
|
||||
before any retry; capture legitimate source HEAD transitions rather than assume
|
||||
a static revision. T07 and T10 remain wait. No original parent preimage recovery,
|
||||
new runtime delivery or broader authority follows from this source repair.
|
||||
|
||||
|
||||
T07 current-baseline client return, 2026-10-17: after the owner refreshed the
|
||||
installed package and verified the actually loaded checker matched the reviewed
|
||||
R6 source, ordinary Cluster deep consistency and fast projection passed.
|
||||
Bindings synced 20/20, omitting the three frozen histories; all three parent
|
||||
and 15 task CURRENT full-row hashes remained unchanged across
|
||||
before-verified-client.json / after-verified-client.json. Unrelated staged brief
|
||||
state was preserved. This proves the corrected installed client stage against
|
||||
its fresh current baseline; earlier parent-hash failures remain retained
|
||||
historical exceptions, not restored preimages or backfilled successes.
|
||||
|
||||
T10 remains wait/needs_human for exact proposal
|
||||
STATE-WP-0095-T10-frozen-bindings-guard-r1, now revision 2, metadata
|
||||
sha256:5d0ddebc21bcb035809cc8b57f4c2e1ba8ed09204be5b965460e178a90913f1e,
|
||||
pending decision df703266-6719-4c04-ae1b-ba9afd3bca1c. The inspected candidate
|
||||
image is main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1,
|
||||
from runtime source c103cbf3c8bed5dba87a03e320595ddded9301a3. Image-only
|
||||
server dry-run passed eight identities with only three image-field changes.
|
||||
The exact proposal and recovery limits are retained in the Custodian dated
|
||||
2026-10-17 guard-successor-review-r2 packet. No new API runtime is approved or
|
||||
delivered by this source-only note; its later workplan commit does not change
|
||||
the image's source pin. T09 stays done, T07/T10 stay wait pending reviewed API
|
||||
delivery and explicit successor/current-baseline acceptance disposition.
|
||||
|
||||
|
||||
T10 proposal revision history: revision 1 metadata
|
||||
sha256:81d365926fc585a186955d8c98ee83501fd3cec12135b2256d71b46a75097932
|
||||
and its original helper remain immutable local evidence, unapproved and
|
||||
unexecuted. Revision 2 of the same stable proposal changes only the native
|
||||
probe's EXPECTED hash metadata into module/sha256 records for normal publication
|
||||
scanning; probe behavior, exact image, recovery and acceptance scope remain
|
||||
unchanged. The same decision df703266-6719-4c04-ae1b-ba9afd3bca1c remains open
|
||||
and pending. This source-only amendment grants no runtime authority or new
|
||||
image/code change; T07/T10 remain wait and T09 remains done.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"schema": "state95.conditional-verified-closure.v1",
|
||||
"status": "preparation_only_not_reviewed_for_publication",
|
||||
"base_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"approval": {
|
||||
"proposal_id": "STATE-WP-0095-T10-frozen-bindings-guard-r1",
|
||||
"revision": 2,
|
||||
"sha256": "5d0ddebc21bcb035809cc8b57f4c2e1ba8ed09204be5b965460e178a90913f1e",
|
||||
"disposition": "founder_approved_reported_by_root_execution_owned_by_root"
|
||||
},
|
||||
"T10_done_requires": [
|
||||
"Exact approved new image/delivered guard hashes and expected schema/primary/API+MCP Ready",
|
||||
"Eight exact native binding probe results with enforced rollback and fresh transaction restoration; no committed synthetic effects",
|
||||
"Exact owned trial cleanup/quiescence or explicit verified no recovery needed; no undisclosed recovery condition"
|
||||
],
|
||||
"T07_done_requires": [
|
||||
"Post-delivery actually loaded reviewed checker metadata verified before normal Cluster consistency/fast projection",
|
||||
"Fresh CURRENT full3parent15task row hashes before/after ordinary pipeline equal",
|
||||
"All15 original task identities/row hashes and all3 archive byte digests unchanged; no new/moved/renamed historical records",
|
||||
"Actual receiving/Cluster owner return for RMGR19T06 verified, not source-only status assumption",
|
||||
"Retained original three-parent hash mismatches accepted only as approved historical exception, never restored/backfilled"
|
||||
],
|
||||
"owner_source_close": [
|
||||
"All10 tasks completed only after above evidence",
|
||||
"Review own remaining scope; real residual work requires live record before finished; no invented acceptance",
|
||||
"After root reviewed final source patch, commit only approved paths with normal hooks preserving original staged brief/dirty WORK/weekly evidence",
|
||||
"Required normal owner rmgr sync --push applied primary/railiance01 exact HEAD=derived, remote and10UUID/status/human/edges readback"
|
||||
],
|
||||
"runtime_source_pin": "c103cbf3c8bed5dba87a03e320595ddded9301a3",
|
||||
"original_frozen_chart_unchanged": true,
|
||||
"candidate_live_action": false,
|
||||
"final_evidence_prose": "pending_root_exact_delivery_receipt_no_placeholder_may_be_published",
|
||||
"source_pin_overlay": "separate reviewable proposal because Make/values still select previous image; not applied"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
--- 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-51bc70d"
|
||||
+appVersion: "main-c103cbf"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
--- 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-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015
|
||||
+RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1
|
||||
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/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-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015"
|
||||
+ tag: "main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1"
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
---
|
||||
id: STATE-WP-0095
|
||||
type: workplan
|
||||
title: "Preserve exact closed historical source bindings in Forge receiving"
|
||||
domain: infotech
|
||||
repo: state-hub
|
||||
status: active
|
||||
owner: codex
|
||||
created: "2026-10-04"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "79579572-8120-5d3b-aca5-dd4aff60d058"
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Normal Forge receiving must preserve the seven bound Activity legacy source
|
||||
views and the Custodian retired ADHOC source without new identities, source
|
||||
rewrites, task movement, deletion, relabeling or unretirement. This implements
|
||||
receiving support before RMGR-WP-0019 registrar admission. CUST-WP-0077 owns
|
||||
cross-owner coordination; deployment and replay remain explicit owner returns.
|
||||
|
||||
## Verify source bindings and shared views
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7"
|
||||
```
|
||||
|
||||
Reviewed seven exact Activity files: four existing workplans and 64 UUID-bound
|
||||
closed tasks. The archived custodian-WP-0003 parent and three split_from views
|
||||
share one existing parent UUID; every repeated task binding/content agrees.
|
||||
Custodian ADHOC-2026-08-25-retired retains parent
|
||||
1c53d3db-6633-5e2d-987f-10706d73be9e and task
|
||||
d8af3e6b-26ae-580c-bbeb-caa8ad419bec. No identity migration is authorized.
|
||||
|
||||
## Implement preservation receiving and global collision refusal
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ebb24194-e4cc-5130-aaa6-663f2ea2c822"
|
||||
```
|
||||
|
||||
Prepared exact source digest manifest, existing-parent/task verification,
|
||||
immutable receiving and UUID-based historical diff. Ordinary canonical sources
|
||||
continue deriving their identities. Missing/changed/symlinked source, absent
|
||||
bound rows or nonterminal hub histories refuse. Check task UUID holders globally,
|
||||
including retired and acknowledged-stale parent workplans, before INSERT.
|
||||
|
||||
Offline preview tests and snapshot proof are evidence of source behavior only;
|
||||
finish after applying reviewed source and its focused tests to the owning repo.
|
||||
|
||||
## Deliver receiving through normal deployment and verify owner return
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T03
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T02]
|
||||
state_hub_task_id: "e8566341-cf6e-5b9c-ad80-8564d14cd52a"
|
||||
decision_id: "36772026-f43d-4331-b466-5f4b1afe4eca"
|
||||
```
|
||||
|
||||
No direct live code patch. This task is a deployment return, not policy authority
|
||||
or permission to resolve identity decisions. Exact receiving fixture acceptance
|
||||
and global collision rejection must hold in the delivered version.
|
||||
|
||||
## Verify normal historical replay preserves existing identities
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T04
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T03, RMGR-WP-0019-T03]
|
||||
state_hub_task_id: "3bbb311f-194e-5862-8aed-358a580f22b4"
|
||||
```
|
||||
|
||||
Verify Activity four historical parent UUIDs/64 task UUIDs and Custodian retired
|
||||
parent/task unchanged; no historical row creation, movement, deletion,
|
||||
relabeling or unretirement. Unrelated stale projections remain separate decisions.
|
||||
|
||||
2026-10-05: Source exact-set retirement support now exists. Repository projection
|
||||
reconcile accepts `expected_retirement_uuids`; that set must equal the stale
|
||||
projections, and any task or dependency edge on a stale row refuses the call.
|
||||
Live apply still needs a reviewed rollout. On 2026-10-05 the founder accepted
|
||||
review `review-1ece4f3e-d0e3-4fd6-abc8-272f1b1876f4`, which names only the three
|
||||
empty projections and preserves parent `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`.
|
||||
That acceptance is not a live retirement and does not resolve
|
||||
`91b7c633-a66f-5d43-9f4b-6565aefb9356`.
|
||||
|
||||
## Offline evidence, 2026-10-04
|
||||
|
||||
Prepared receiver suite: Forge projection regression plus historical preservation,
|
||||
missing/changed source, terminality, immutable UUID diff, and global holder query.
|
||||
Real-source offline snapshot proof preserves four Activity parents/64 tasks and
|
||||
one Custodian parent/task without mutations; no API writes or deployment occurred.
|
||||
Exact original bindings are maintained in api/services/historical_projection.py.
|
||||
|
||||
2026-10-04 actual owning-source validation: 155 focused receiver/qualifier/planner
|
||||
cases passed; the independent offline current-source/current-hub-snapshot proof
|
||||
retained five existing parents and 65 task UUIDs, no mutations, clean historical
|
||||
diff. T02 is source-complete; T03/T04 remain wait for reviewed deployment and
|
||||
normal registrar/replay receiving, respectively. Evidence: docs/evidence/2026-10-04-historical-receiving-source.json.
|
||||
|
||||
|
||||
## Verified receiving rollout and remaining replay gate, 2026-10-04
|
||||
|
||||
T03 is done on actual founder approval of proposal
|
||||
STATE-WP-0095-T03-historical-receiving revision 1, SHA256
|
||||
`d6789359b3fe7988a6201fff6f367cb46948aa197e1cc0ca66c31f45e1ea9981`,
|
||||
approval receipt reference `user_reply:call_o87uEfFs3AtXfxO5YqUnSpcg:0`.
|
||||
The normal owner Helm path delivered `main-5dd3df9`, commit
|
||||
`5dd3df9c69eb564af10ea5ea01e349786212501a`, revision 72; API and MCP are Ready,
|
||||
primary/railiance01 health and schema a0b1c2d3e4f5 verified, running source
|
||||
hashes match. Rollback remains revision 71; no receipt-table downgrade is needed.
|
||||
Actual historical preservation and global task-holder refusal were verified in
|
||||
the delivered version. Durable receipt:
|
||||
[verified historical receiving rollout](../docs/evidence/2026-10-04-historical-receiving-rollout.json).
|
||||
|
||||
Custodian immutable replay applied while preserving the retired historical
|
||||
parent/task UUIDs. Activity immutable replay retained all four historical parent
|
||||
and 64 task UUIDs and historical row contents, but refused transactionally on
|
||||
three unrelated stale projections: custodian-wp-0003 (archived),
|
||||
custodian-wp-0003a (done), custodian-wp-0003b (done). No retirement acknowledgement
|
||||
was issued and that refusal is not represented as normal replay success.
|
||||
|
||||
Registrar source `c41d36c2` has 38 actual passing tests and Repo Manager's own
|
||||
normal sync succeeded; registrar admission does not authorize retiring Activity
|
||||
projections. T04 therefore remains wait, retaining RMGR-WP-0019-T03 and awaiting
|
||||
Activity Core's precise reviewed disposition of those three stale rows. No
|
||||
rename, identity migration, new paid native trial or further live action is
|
||||
included in this source status update.
|
||||
|
||||
## Activity Core return, 2026-10-06
|
||||
|
||||
Activity Core returned the exact disposition (message
|
||||
`cf2d5847-8b3f-4653-9d9f-d5fcfbe3f1e4`, ACTIVITY-WP-0045, activity-core commit
|
||||
`941fac0`). `expected_retirement_uuids`:
|
||||
`683724d9-4249-5e0e-88e0-7baff4330d58` (custodian-wp-0003),
|
||||
`644017c7-c5f2-5a64-81c3-d8ee806f8251` (custodian-wp-0003a),
|
||||
`a5806df9-5b9a-50da-81c1-7e606076bed9` (custodian-wp-0003b). Each has zero
|
||||
tasks and zero dependency edges. None is preserved parent
|
||||
`b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`. Repo Manager independently verified the
|
||||
set (message `916841f6`, repo-manager commit `498682c`). Nothing was retired.
|
||||
T04 now waits on decision `91b7c633` and a reviewed live rollout. After that,
|
||||
Repo Manager re-runs the Activity owner sync and closes RMGR-WP-0019-T03.
|
||||
|
||||
## Retirement rollout and T04 verification, 2026-10-06
|
||||
|
||||
The founder resolved decision `91b7c633` (retire exactly the three projections)
|
||||
and approved the rollout. The founder ran the normal Helm upgrade to
|
||||
`main-968a811` (revision 74; rollback revision 73, `main-fad9b3f`). API and MCP
|
||||
are Ready, health is ok on primary/railiance01, and schema `b3c4d5e6f7a8` is
|
||||
unchanged; no migrations. The activity-core reconcile at commit
|
||||
`67a92d2ff8e033baf2c36998d24187fe83edec84` with `expected_retirement_uuids` set
|
||||
to the three UUIDs applied: 0 created, 3 retired (tombstoned with an
|
||||
`@retired-20261006` slug; UUIDs and statuses kept), 0 refused, 0 tasks
|
||||
cancelled. All 7 bound Activity source files still match their digests. The four
|
||||
historical parents and 64 task UUIDs are present under their bound parents with
|
||||
unchanged fields. The Custodian retired parent and task are unchanged. Activity
|
||||
had 54 workplans and 288 tasks before and after. Evidence:
|
||||
[activity stale retirement rollout](../docs/evidence/2026-10-06-activity-stale-retirement-rollout.json).
|
||||
Repo Manager's re-run of the Activity owner sync (RMGR-WP-0019-T03) and
|
||||
ACTIVITY-WP-0045-T02 remain with their owners.
|
||||
|
||||
|
||||
## Cluster exact historical receiving successor, 2026-10-17
|
||||
|
||||
This scoped extension retains the completed Activity/Custodian outcomes above.
|
||||
Three frozen cluster archives contain three existing parent UUIDs and 15 existing
|
||||
closed task UUIDs. Extend the existing exact historical receiving path before
|
||||
registrar admission; never derive replacement identities or modify those
|
||||
sources. Cross-owner observation attaches to existing SHR-WP-0001, with
|
||||
RMGR-WP-0019 as obligor/beneficiary and NK-WP-0049-T35 benefiting indirectly.
|
||||
The local derived case is waiting; no live worker injection is authorized.
|
||||
|
||||
## Prepare exact cluster source preservation and refusal cases
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "339b9a37-181c-5599-9a89-0393e199bd4b"
|
||||
```
|
||||
|
||||
Prepared the same full-file SHA256 and 15 task binding manifest as Repo Manager.
|
||||
Candidate receiving tests pass 26: existing rows remain entirely unchanged;
|
||||
missing/foreign/nonterminal rows and unknown, changed, missing or symlinked
|
||||
source refuse. Read-only public metadata confirms all 18 existing rows have the
|
||||
expected ownership and terminal status. These are source and metadata checks,
|
||||
not delivered receiving or a committed native trial.
|
||||
|
||||
## Deliver reviewed cluster receiving successor before registrar admission
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T06
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T05]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 4 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4"
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06, STATE-WP-0095-T10]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
```
|
||||
|
||||
Capture normal cluster owner publication/receiving receipts and compare entire
|
||||
existing row snapshots and exact source bytes. No creation, movement, relabel,
|
||||
unretirement or retirement is authorized for these histories. A rollback first
|
||||
fences cluster sync and restores registrar refusal before removing receiving
|
||||
support; it never rewrites archived IDs or UUIDs. This work does not establish
|
||||
original TLS recovery or owner-native admission.
|
||||
|
||||
## Fix policy-compatible Secret-template server dry-run preparation
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2dd9b04f-2250-5a8a-b5bc-c8a009872858"
|
||||
```
|
||||
|
||||
Prepared the narrow Makefile successor after the existing readiness command
|
||||
refused its first Secret-template phase. Ordinary client-side apply adds the
|
||||
last-applied annotation, correctly denied by the existing Platform
|
||||
reject-secret-last-applied policy. Only the two Secret-template server
|
||||
dry-run requests now use server-side apply with dry-run; no force-conflicts,
|
||||
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 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: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T09]
|
||||
needs_human: false
|
||||
intervention_note: "Exact revision 2 approved and verified; no pending rollout intervention."
|
||||
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.
|
||||
|
||||
|
||||
T09 source repair refinement, 2026-10-17: the attempted current-client regression
|
||||
actually loaded an older packaged consistency checker from the owner virtualenv,
|
||||
not the reviewed canonical source. That trial again failed parent-row hash
|
||||
preservation; it is retained separately and does not count as R5 client proof.
|
||||
Normal C-23 also legitimately normalized WP18 ready to active and created a
|
||||
source commit, but the generic writeback commit included an unrelated staged
|
||||
brief. A narrow successor scopes git add and git commit --only to the actual
|
||||
writeback file, preserving unrelated index/worktree state. Real disposable Git
|
||||
regression tests cover pre-existing staged changes, hooks, no-op and path names.
|
||||
Verify the actually loaded CLI checker hash after ordinary owner package refresh
|
||||
before any retry; capture legitimate source HEAD transitions rather than assume
|
||||
a static revision. T07 and T10 remain wait. No original parent preimage recovery,
|
||||
new runtime delivery or broader authority follows from this source repair.
|
||||
|
||||
|
||||
T07 current-baseline client return, 2026-10-17: after the owner refreshed the
|
||||
installed package and verified the actually loaded checker matched the reviewed
|
||||
R6 source, ordinary Cluster deep consistency and fast projection passed.
|
||||
Bindings synced 20/20, omitting the three frozen histories; all three parent
|
||||
and 15 task CURRENT full-row hashes remained unchanged across
|
||||
before-verified-client.json / after-verified-client.json. Unrelated staged brief
|
||||
state was preserved. This proves the corrected installed client stage against
|
||||
its fresh current baseline; earlier parent-hash failures remain retained
|
||||
historical exceptions, not restored preimages or backfilled successes.
|
||||
|
||||
T10 remains wait/needs_human for exact proposal
|
||||
STATE-WP-0095-T10-frozen-bindings-guard-r1, now revision 2, metadata
|
||||
sha256:5d0ddebc21bcb035809cc8b57f4c2e1ba8ed09204be5b965460e178a90913f1e,
|
||||
pending decision df703266-6719-4c04-ae1b-ba9afd3bca1c. The inspected candidate
|
||||
image is main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1,
|
||||
from runtime source c103cbf3c8bed5dba87a03e320595ddded9301a3. Image-only
|
||||
server dry-run passed eight identities with only three image-field changes.
|
||||
The exact proposal and recovery limits are retained in the Custodian dated
|
||||
2026-10-17 guard-successor-review-r2 packet. No new API runtime is approved or
|
||||
delivered by this source-only note; its later workplan commit does not change
|
||||
the image's source pin. T09 stays done, T07/T10 stay wait pending reviewed API
|
||||
delivery and explicit successor/current-baseline acceptance disposition.
|
||||
|
||||
|
||||
T10 proposal revision history: revision 1 metadata
|
||||
sha256:81d365926fc585a186955d8c98ee83501fd3cec12135b2256d71b46a75097932
|
||||
and its original helper remain immutable local evidence, unapproved and
|
||||
unexecuted. Revision 2 of the same stable proposal changes only the native
|
||||
probe's EXPECTED hash metadata into module/sha256 records for normal publication
|
||||
scanning; probe behavior, exact image, recovery and acceptance scope remain
|
||||
unchanged. The same decision df703266-6719-4c04-ae1b-ba9afd3bca1c remains open
|
||||
and pending. This source-only amendment grants no runtime authority or new
|
||||
image/code change; T07/T10 remain wait and T09 remains done.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
--- a/workplans/STATE-WP-0095-historical-source-receiving.md
|
||||
+++ b/workplans/STATE-WP-0095-historical-source-receiving.md
|
||||
@@ -235,7 +235,7 @@
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T07
|
||||
-status: wait
|
||||
+status: done
|
||||
priority: high
|
||||
depends_on: [STATE-WP-0095-T06, RMGR-WP-0019-T06, STATE-WP-0095-T10]
|
||||
state_hub_task_id: "cfd1ea99-e75b-542e-a434-acefb62f3c7d"
|
||||
@@ -325,11 +325,11 @@
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T10
|
||||
-status: wait
|
||||
+status: done
|
||||
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."
|
||||
+needs_human: false
|
||||
+intervention_note: "Exact revision 2 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "88585c69-d9b9-5e1f-a256-6416ff71e824"
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"normal_make_render_exit": 0,
|
||||
"objects": 8,
|
||||
"verified_actual_image_fields": 3,
|
||||
"T10": "done",
|
||||
"T07": "wait",
|
||||
"parent": "active",
|
||||
"runtime_mutation": false
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"schema": "state95.verified-guard-source-followthrough.v1",
|
||||
"base_revision": "4c03c9d2c6a47f5c7247473f74b2e2f188b787bd",
|
||||
"files": [
|
||||
{
|
||||
"path": "workplans/STATE-WP-0095-historical-source-receiving.md",
|
||||
"base_sha256": "163acbdf8252f37e309f4cb0b51d48706c6bd43d439fdbf7802b953b78ae35ac",
|
||||
"candidate_sha256": "bcde1a6502dfad71586dc38b2f681723dcb310c22f66f052b305ded89bf9b00d"
|
||||
},
|
||||
{
|
||||
"path": "deploy/railiance/apps/charts/state-hub/Chart.yaml",
|
||||
"base_sha256": "61a989fcf277d05ffbececb7332ee3ca1d8f369c72f960ab1bc4dbc6815536b8",
|
||||
"candidate_sha256": "7d6d71dfe6dc85005f067d65b76d5f70215b96c6aa1c82b218ded1152c40fb43"
|
||||
},
|
||||
{
|
||||
"path": "Makefile",
|
||||
"base_sha256": "c5b70e8d4b8ae70380e0ade1c35f9e7fe80038de10080a4723b21ef07a98001e",
|
||||
"candidate_sha256": "3bc22d33cc055d0b7cb0a3b4ee6df406aa7b32d5916cb456264cbb348a3cf764"
|
||||
},
|
||||
{
|
||||
"path": "deploy/railiance/apps/helm/state-hub-values.yaml",
|
||||
"base_sha256": "b51296fc937450f4ecf0ae43243262a631b551389c0d09c8bfe58fa10e2f3ee9",
|
||||
"candidate_sha256": "0e05127167fce1508321866792ad58796eaa4dedfb2bcd05e2e3ff39db804c5b"
|
||||
}
|
||||
],
|
||||
"patch_sha256": "sha256:d5b3a123ed0ffaee4ff811b0864b9f1d35b39b8e793fd382c5f4a02f916b942b",
|
||||
"T10": "done_verified",
|
||||
"T07": "wait_for_RMGR19T06_actual_closure",
|
||||
"parent": "active",
|
||||
"new_UUIDs": false,
|
||||
"runtime_changed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
{
|
||||
"ok": true,
|
||||
"status": "applied",
|
||||
"repo_slug": "state-hub",
|
||||
"commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"requests": 2,
|
||||
"elapsed_ms": 19035.3,
|
||||
"git": {
|
||||
"upstream": "origin/main",
|
||||
"behind": 0,
|
||||
"ahead": 0
|
||||
},
|
||||
"identifiers": {
|
||||
"schema": "repo-manager.identifier-assignment.v1",
|
||||
"ok": true,
|
||||
"executed": true,
|
||||
"repo": "state-hub",
|
||||
"namespace": "helixforge",
|
||||
"assignments": [],
|
||||
"historical_invalid_identifiers": [],
|
||||
"historical_identity_collisions": [],
|
||||
"files_changed": []
|
||||
},
|
||||
"receipt": {
|
||||
"schema": "state-hub.repository-projection-reconcile.v1",
|
||||
"instance_role": "primary",
|
||||
"instance_label": "railiance01",
|
||||
"expected_commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"derived_commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"outcome": {
|
||||
"schema": "state-hub.projection-reset.v1",
|
||||
"repo_slug": "state-hub",
|
||||
"commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"status": "applied",
|
||||
"counts": {
|
||||
"created": 0,
|
||||
"updated": 70,
|
||||
"retired": 0,
|
||||
"refused": 0,
|
||||
"released": 0,
|
||||
"created_tasks": 0,
|
||||
"updated_tasks": 1,
|
||||
"cancelled_tasks": 0,
|
||||
"created_edges": 0,
|
||||
"removed_edges": 1,
|
||||
"unresolved_edges": 0,
|
||||
"external_edges": 6
|
||||
},
|
||||
"created": [],
|
||||
"updated": [
|
||||
"CUST-WP-0003",
|
||||
"CUST-WP-0011",
|
||||
"CUST-WP-0012",
|
||||
"CUST-WP-0038",
|
||||
"CUST-WP-0042",
|
||||
"STATE-WP-0043",
|
||||
"STATE-WP-0044",
|
||||
"STATE-WP-0045",
|
||||
"STATE-WP-0046",
|
||||
"STATE-WP-0047",
|
||||
"STATE-WP-0048",
|
||||
"STATE-WP-0049",
|
||||
"STATE-WP-0050",
|
||||
"STATE-WP-0051",
|
||||
"STATE-WP-0052",
|
||||
"STATE-WP-0053",
|
||||
"STATE-WP-0054",
|
||||
"STATE-WP-0055",
|
||||
"STATE-WP-0056",
|
||||
"STATE-WP-0057",
|
||||
"STATE-WP-0058",
|
||||
"STATE-WP-0059",
|
||||
"STATE-WP-0060",
|
||||
"STATE-WP-0061",
|
||||
"STATE-WP-0062",
|
||||
"STATE-WP-0063",
|
||||
"STATE-WP-0064",
|
||||
"STATE-WP-0065",
|
||||
"STATE-WP-0066",
|
||||
"STATE-WP-0067",
|
||||
"STATE-WP-0068",
|
||||
"STATE-WP-0069",
|
||||
"STATE-WP-0070",
|
||||
"STATE-WP-0071",
|
||||
"STATE-WP-0072",
|
||||
"STATE-WP-0073",
|
||||
"STATE-WP-0074",
|
||||
"STATE-WP-0075",
|
||||
"STATE-WP-0076",
|
||||
"STATE-WP-0077",
|
||||
"STATE-WP-0078",
|
||||
"STATE-WP-0079",
|
||||
"STATE-WP-0080",
|
||||
"STATE-WP-0081",
|
||||
"STATE-WP-0082",
|
||||
"STATE-WP-0083",
|
||||
"STATE-WP-0084",
|
||||
"STATE-WP-0085",
|
||||
"STATE-WP-0086",
|
||||
"STATE-WP-0087",
|
||||
"STATE-WP-0088",
|
||||
"STATE-WP-0089",
|
||||
"STATE-WP-0090",
|
||||
"STATE-WP-0091",
|
||||
"STATE-WP-0092",
|
||||
"STATE-WP-0093",
|
||||
"STATE-WP-0094",
|
||||
"STATE-WP-0095",
|
||||
"STATE-WP-0096",
|
||||
"STATE-WP-0097",
|
||||
"STATE-WP-0098",
|
||||
"STATE-WP-ADHOC-2026-06-04",
|
||||
"STATE-WP-ADHOC-2026-07-01",
|
||||
"STATE-WP-ADHOC-2026-08-08",
|
||||
"STATE-WP-ADHOC-2026-08-23",
|
||||
"STATE-WP-ADHOC-2026-08-30",
|
||||
"STATE-WP-ADHOC-2026-09-04",
|
||||
"STATE-WP-ADHOC-2026-09-22",
|
||||
"STATE-WP-ADHOC-2026-10-06",
|
||||
"STATE-WP-ADHOC-2026-10-13"
|
||||
],
|
||||
"retired": [],
|
||||
"released": [],
|
||||
"created_tasks": [],
|
||||
"updated_tasks": [
|
||||
"STATE-WP-0095-T10"
|
||||
],
|
||||
"cancelled_tasks": [],
|
||||
"created_edges": [],
|
||||
"removed_edges": [
|
||||
{
|
||||
"id": "2dbd5851-295e-4260-ba2f-2357324e5c8c",
|
||||
"from_workplan_id": "79579572-8120-5d3b-aca5-dd4aff60d058",
|
||||
"from_task_id": "88585c69-d9b9-5e1f-a256-6416ff71e824",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "8be8f13b-6352-5295-9755-de6fbfb13593",
|
||||
"relationship_type": "starts_after"
|
||||
}
|
||||
],
|
||||
"unresolved_edges": [],
|
||||
"external_edges": [
|
||||
{
|
||||
"workplan": "STATE-WP-0098",
|
||||
"from": "STATE-WP-0098-T04",
|
||||
"target": "HUB-WP-0011-T01",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "0c68970e-295b-57fc-ac70-e472ee3a7bc4",
|
||||
"from_task_id": "077797c8-289b-55ec-9597-28f223891b02",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "588e4b58-4694-513c-8e7a-1fab38fb6ce3"
|
||||
},
|
||||
{
|
||||
"workplan": "CUST-WP-0038",
|
||||
"from": "CUST-WP-0038",
|
||||
"target": "CUST-WP-0011",
|
||||
"relationship_type": "blocks",
|
||||
"from_workplan_id": "ccb87fa4-6381-5f22-b097-6e026f56a9c1",
|
||||
"from_task_id": null,
|
||||
"to_workplan_id": "967baafb-d92d-405a-ba0b-0d00d37c4940",
|
||||
"to_task_id": null
|
||||
},
|
||||
{
|
||||
"workplan": "STATE-WP-0079",
|
||||
"from": "STATE-WP-0079-T04",
|
||||
"target": "HUB-WP-0011-T03",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "ed077b62-7048-5752-bf65-f90471f45854",
|
||||
"from_task_id": "3a454438-1955-547f-a491-8eab7cd25710",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "61c727a5-f4b1-54c0-b634-7d71a5416496"
|
||||
},
|
||||
{
|
||||
"workplan": "STATE-WP-0079",
|
||||
"from": "STATE-WP-0079-T04",
|
||||
"target": "HUB-WP-0006-T06",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "ed077b62-7048-5752-bf65-f90471f45854",
|
||||
"from_task_id": "3a454438-1955-547f-a491-8eab7cd25710",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "7c8225f7-03dc-57b0-a668-c3d781bd828a"
|
||||
},
|
||||
{
|
||||
"workplan": "STATE-WP-0079",
|
||||
"from": "STATE-WP-0079-T06",
|
||||
"target": "STATE-WP-0079-T04",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "ed077b62-7048-5752-bf65-f90471f45854",
|
||||
"from_task_id": "9230c99b-af58-5794-9291-452e619a30fa",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "3a454438-1955-547f-a491-8eab7cd25710"
|
||||
},
|
||||
{
|
||||
"workplan": "STATE-WP-0079",
|
||||
"from": "STATE-WP-0079-T06",
|
||||
"target": "STATE-WP-0079-T05",
|
||||
"relationship_type": "starts_after",
|
||||
"from_workplan_id": "ed077b62-7048-5752-bf65-f90471f45854",
|
||||
"from_task_id": "9230c99b-af58-5794-9291-452e619a30fa",
|
||||
"to_workplan_id": null,
|
||||
"to_task_id": "f6cf8c01-eb59-5ca8-a541-23928d4a000c"
|
||||
}
|
||||
],
|
||||
"refused": [],
|
||||
"notes": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from pathlib import Path
|
||||
import hashlib,json,importlib.util,subprocess,yaml,re
|
||||
p=Path(__file__).resolve().parent;owner=Path('/home/worsch/state-hub')
|
||||
spec=importlib.util.spec_from_file_location('pub',p.parents[1]/'owner-followthrough-r5/publish_followthrough_source.py');pub=importlib.util.module_from_spec(spec);spec.loader.exec_module(pub)
|
||||
d=json.loads((p/'manifest.json').read_text());sha=lambda b:hashlib.sha256(b).hexdigest();names={e['path'] for e in d['files']};assert len(names)==4
|
||||
assert pub.git(owner,'rev-parse','HEAD').stdout.decode().strip()==d['base_revision']
|
||||
assert sha((p/'state-hub-verified-followthrough.patch').read_bytes())=='d5b3a123ed0ffaee4ff811b0864b9f1d35b39b8e793fd382c5f4a02f916b942b'
|
||||
before=pub.preserved_state(owner,names)
|
||||
for e in d['files']:
|
||||
f=owner/e['path'];s=p/'state-hub'/e['path'];assert not f.is_symlink() and not s.is_symlink()
|
||||
assert sha(f.read_bytes())==e['base_sha256'] and sha(s.read_bytes())==e['candidate_sha256']
|
||||
assert not pub.git(owner,'diff','--cached','--',e['path']).stdout
|
||||
for e in d['files']:(owner/e['path']).write_bytes((p/'state-hub'/e['path']).read_bytes())
|
||||
assert pub.preserved_state(owner,names)==before
|
||||
render=subprocess.run(['make','railiance-state-hub-render'],cwd=owner,capture_output=True,text=True,timeout=45);assert render.returncode==0
|
||||
objects=[o for o in yaml.safe_load_all(render.stdout[render.stdout.index('---'):]) if o];images=[]
|
||||
def walk(v):
|
||||
if isinstance(v,dict):
|
||||
for k,x in v.items():
|
||||
if k=='image':images.append(x)
|
||||
walk(x)
|
||||
elif isinstance(v,list):
|
||||
for x in v:walk(x)
|
||||
for obj in objects:walk(obj)
|
||||
assert len(objects)==8 and len(images)==3 and set(images)=={'forgejo.coulomb.social/coulomb/state-hub:main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1'}
|
||||
text=(owner/'workplans/STATE-WP-0095-historical-source-receiving.md').read_text();tasks={b['id']:b for b in (yaml.safe_load(x) for x in re.findall(r'```task\n(.*?)```',text,re.S))};assert len(tasks)==10
|
||||
assert tasks['STATE-WP-0095-T10']['status']=='done' and tasks['STATE-WP-0095-T10']['needs_human'] is False
|
||||
assert tasks['STATE-WP-0095-T07']['status']=='wait'
|
||||
(p/'canonical-validation.json').write_text(json.dumps({'normal_make_render_exit':0,'objects':8,'verified_actual_image_fields':3,'T10':'done','T07':'wait','parent':'active','runtime_mutation':False},indent=2)+'\n')
|
||||
r=pub.commit_source(owner,d,pub.SCANNER,execute=True,push=False);assert pub.preserved_state(owner,names)==before
|
||||
(p/'source-commit-result.json').write_text(json.dumps(r,indent=2)+'\n');print(json.dumps({k:v for k,v in r.items() if k not in {'preserved_before','preserved_after'}}))
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
from pathlib import Path
|
||||
import subprocess,json
|
||||
p=Path(__file__).resolve().parent
|
||||
r=subprocess.run(['make','sync','SYNC_PATH=.','SYNC_PUSH=1','STATE_HUB_API_BASE=http://127.0.0.1:8000'],cwd='/home/worsch/state-hub',capture_output=True,text=True,timeout=180)
|
||||
try:d=json.loads(r.stdout[r.stdout.index('{'):])
|
||||
except Exception:d={'status':'unverified','reason':'normal_sync_receipt_unparseable','exit':r.returncode}
|
||||
(p/'normal-sync-result.json').write_text(json.dumps(d,indent=2)+'\n')
|
||||
receipt=d.get('receipt',{})
|
||||
print(json.dumps({'exit':r.returncode,'status':d.get('status'),'commit':d.get('commit'),'instance_role':receipt.get('instance_role'),'instance_label':receipt.get('instance_label'),'expected_commit':receipt.get('expected_commit'),'derived_commit':receipt.get('derived_commit'),'identifier_assignments':d.get('identifiers',{}).get('assignments')}))
|
||||
raise SystemExit(r.returncode)
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"schema": "state95.verified-guard-source-readback.v1",
|
||||
"status": "applied",
|
||||
"source_revision": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"remote_revision": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"instance_role": "primary",
|
||||
"instance_label": "railiance01",
|
||||
"expected_commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"derived_commit": "bb525bc304cbd79b8a8a1540aa28bf073a4e2c32",
|
||||
"tasks": [
|
||||
{
|
||||
"uuid": "6a5f87bb-e63d-5e20-b9fc-c1b5cd403ff7",
|
||||
"record_id": "STATE-WP-0095-T01",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "ebb24194-e4cc-5130-aaa6-663f2ea2c822",
|
||||
"record_id": "STATE-WP-0095-T02",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "e8566341-cf6e-5b9c-ad80-8564d14cd52a",
|
||||
"record_id": "STATE-WP-0095-T03",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "3bbb311f-194e-5862-8aed-358a580f22b4",
|
||||
"record_id": "STATE-WP-0095-T04",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "339b9a37-181c-5599-9a89-0393e199bd4b",
|
||||
"record_id": "STATE-WP-0095-T05",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "6af973b4-1964-5d4d-a2f8-8dbd4c01fda4",
|
||||
"record_id": "STATE-WP-0095-T06",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "cfd1ea99-e75b-542e-a434-acefb62f3c7d",
|
||||
"record_id": "STATE-WP-0095-T07",
|
||||
"status": "wait",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "2dd9b04f-2250-5a8a-b5bc-c8a009872858",
|
||||
"record_id": "STATE-WP-0095-T08",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "8be8f13b-6352-5295-9755-de6fbfb13593",
|
||||
"record_id": "STATE-WP-0095-T09",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
},
|
||||
{
|
||||
"uuid": "88585c69-d9b9-5e1f-a256-6416ff71e824",
|
||||
"record_id": "STATE-WP-0095-T10",
|
||||
"status": "done",
|
||||
"needs_human": false
|
||||
}
|
||||
],
|
||||
"parent_status": "active",
|
||||
"whole_unrelated_index_dirty_preserved": true,
|
||||
"new_UUIDs": false,
|
||||
"runtime_mutated_by_source_followthrough": false,
|
||||
"declared_runtime_source": "c103cbf3c8bed5dba87a03e320595ddded9301a3",
|
||||
"declared_image": "main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1",
|
||||
"original_frozen_archive_unchanged": true,
|
||||
"T07": "wait_for_RMGR19T06_published_closure"
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
--- a/workplans/STATE-WP-0095-historical-source-receiving.md
|
||||
+++ b/workplans/STATE-WP-0095-historical-source-receiving.md
|
||||
@@ -325,11 +325,11 @@
|
||||
|
||||
```task
|
||||
id: STATE-WP-0095-T10
|
||||
-status: wait
|
||||
+status: done
|
||||
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."
|
||||
+needs_human: false
|
||||
+intervention_note: "Exact revision 2 approved and verified; no pending rollout intervention."
|
||||
state_hub_task_id: "88585c69-d9b9-5e1f-a256-6416ff71e824"
|
||||
```
|
||||
|
||||
@@ -392,3 +392,31 @@
|
||||
unchanged. The same decision df703266-6719-4c04-ae1b-ba9afd3bca1c remains open
|
||||
and pending. This source-only amendment grants no runtime authority or new
|
||||
image/code change; T07/T10 remain wait and T09 remains done.
|
||||
+
|
||||
+
|
||||
+T10 verified receiving return, 2026-10-17: the founder approved and the owner
|
||||
+executed exact revision 2 of STATE-WP-0095-T10-frozen-bindings-guard-r1,
|
||||
+sha256:5d0ddebc21bcb035809cc8b57f4c2e1ba8ed09204be5b965460e178a90913f1e,
|
||||
+under decision df703266-6719-4c04-ae1b-ba9afd3bca1c. Helm revision 77 delivered
|
||||
+runtime source c103cbf3c8bed5dba87a03e320595ddded9301a3 and image
|
||||
+main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1.
|
||||
+API/MCP are Ready on expected schema c4d5e6f7a8b9. Eight actual native cases
|
||||
+passed with zero probe commits. All three parent and 15 task CURRENT full-row
|
||||
+hashes remained unchanged after delivery/probes and the actual normal Cluster
|
||||
+pipeline (20 ordinary bindings, three frozen bindings omitted by the verified
|
||||
+loaded checker). All three archived sources remained byte-identical. Exact
|
||||
+owned Job/pod success was observed, then their absence and removal of owned
|
||||
+chart staging were verified; no recovery was needed.
|
||||
+
|
||||
+The verified return is the Custodian dated 2026-10-17
|
||||
+ guard-successor-execution-r2/verified-receiving-return.json,
|
||||
+sha256:09958bd69367cc6903e2a21a56eb81b6a8a46c2a1907afc93f787cd63732e92a.
|
||||
+The original three-parent hash failures remain the founder-accepted historical
|
||||
+exception: unavailable original preimages were not guessed/restored, and failed
|
||||
+outcomes were not backfilled. The 15 original task rows and archived bytes stay
|
||||
+preserved. Earlier pending review notes are retained as proposal history; T10
|
||||
+is now done and needs_human false. T07 and this parent remain open until the
|
||||
+actual RMGR-WP-0019-T06 owner closure is published and returned. No native
|
||||
+security-target gate or broader autonomy is established by this infrastructure
|
||||
+return. Source appVersion and both maintained image pins now declare this same
|
||||
+verified image; the approved frozen rollout archive remains unchanged.
|
||||
--- 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-51bc70d"
|
||||
+appVersion: "main-c103cbf"
|
||||
--- 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-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015
|
||||
+RAILIANCE_STATE_HUB_IMAGE_TAG ?= main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1
|
||||
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/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-51bc70d@sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015"
|
||||
+ tag: "main-c103cbf@sha256:4cc4ce17bbcdb1b66132d07c5b7b741f46df83124ef1c8403ffabdafba6993d1"
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
Loading…
Add table
Add a link
Reference in a new issue