← Commit history

Count component-review evidence as continuation progress while ignoring activity timestamps

John Lauer ·5d2acb188d · ·parent 47c8ec5
3 files changed +32
docs/codex-supervisor.md+2
@@ -24,3 +24,5 @@ On **any** user pause or cancellation, the AI must run `pause` immediately befor State and receipts live in `<run>/supervisor/{status,state,pause}.json` and `events.jsonl`. A process PID alone is not proof it is alive: check the last check time and the process. A `start-acknowledged` receipt is not proof the model completed subsequent work. Verify the next turn in the same conversation. No engineering gate, board or acceptance limit is changed by this helper.  Validation: eleven policy/dispatch tests cover active/approval states, explicit pause, failed/interrupted turns, completion, malformed guards, queue interference, duplicate suppression, uncertain dispatch, state races and no-progress. Live metadata discovery was verified against Codex 0.154.0-alpha.6.2. Live continuation was observed in the same electronic-load thread: queue/add auto-started its next turn, carrying the exact client message ID. The initial observer incorrectly classified that as a race; it now attributes its own auto-start by client ID, covered by an additional regression test. Source availability, local activation and a packaged release are separate claims.++Component-review progress is included from the run’s `components.json`, excluding activity-only timestamps. This lets evidence and disposition changes count before a PCB mutation. Malformed component JSON fails closed. The three-unchanged-turn pause still applies; adding idle timestamps does not keep a run alive.
tools/continuation/supervisor.py+12
@@ -104,6 +104,18 @@ def progress_hash(run, report):     data = read(run/'run.json')     payload = {k: data.get(k) for k in ['currentStep', 'outcomes', 'boards', 'delivery', 'continuation']}     payload['remaining'] = report.get('remaining')+    # Component review is real engineering progress before any board change.+    # Keep activity-only stamps out of the comparison and fail closed on bad JSON.+    register = run/'components.json'+    if register.exists():+        def evidence(value):+            if isinstance(value, dict):+                return {k: evidence(v) for k, v in value.items()+                        if k not in {'checkedAt', 'updatedAt', 'createdAt', 'timestamp', 'lastActivityAt'}}+            if isinstance(value, list):+                return [evidence(v) for v in value]+            return value+        payload['componentReview'] = evidence(read(register))     return hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()  
tools/continuation/test_supervisor.py+18
@@ -34,6 +34,24 @@ class PolicyTests(unittest.TestCase):         for g in [{},{'decision':'continue','mayStop':True},{'decision':'user-paused'}]:             self.assertTrue(self.policy(guard=g).startswith('error')) +class ProgressTests(unittest.TestCase):+    def test_component_review_changes_count_but_activity_does_not(self):+        with tempfile.TemporaryDirectory() as d:+            p = Path(d)+            s.write(p/'run.json', {'currentStep':'components','updatedAt':'old'})+            s.write(p/'components.json', {'components':[{'reference':'Q1','review':{'status':'pending'}}]})+            before = s.progress_hash(p, {'remaining':['libraries']})+            s.write(p/'run.json', {'currentStep':'components','updatedAt':'new'})+            s.write(p/'components.json', {'updatedAt':'new','components':[{'reference':'Q1','review':{'status':'pending','checkedAt':'new'}}]})+            self.assertEqual(before, s.progress_hash(p, {'remaining':['libraries']}))+            s.write(p/'components.json', {'components':[{'reference':'Q1','review':{'status':'stale','geometryEvidence':['lead-hole-review.json']}}]})+            self.assertNotEqual(before, s.progress_hash(p, {'remaining':['libraries']}))+    def test_invalid_component_register_fails_closed(self):+        with tempfile.TemporaryDirectory() as d:+            p=Path(d);s.write(p/'run.json', {})+            (p/'components.json').write_text('{broken')+            with self.assertRaises(ValueError):s.progress_hash(p, {})+ class FakeClient:     calls=[]     race=False