tenseleyflow/loader / 39463de

Browse files

Surface stale verification lifecycle

Authored by espadonne
SHA
39463de50fede9e9dee0e410ce69207d96e4a3aa
Parents
96f060e
Tree
babff4b

4 changed files

StatusFile+-
M src/loader/cli/main.py 2 0
M src/loader/runtime/workflow_timeline_read_model.py 2 0
M tests/test_inspection.py 94 0
M tests/test_workflow_timeline_read_model.py 35 0
src/loader/cli/main.pymodified
@@ -1513,6 +1513,7 @@ def _print_status_snapshot(snapshot: StatusSnapshot) -> None:
15131513
         for item in snapshot.recent_verification:
15141514
             result = {
15151515
                 "pending": "[cyan]pending[/cyan]",
1516
+                "stale": "[yellow]stale[/yellow]",
15161517
                 "passed": "[green]pass[/green]",
15171518
                 "failed": "[red]fail[/red]",
15181519
                 "skipped": "[yellow]skip[/yellow]",
@@ -1772,6 +1773,7 @@ def _session_show_main(session_id: str) -> None:
17721773
         for item in detail.recent_verification:
17731774
             result = {
17741775
                 "pending": "[cyan]pending[/cyan]",
1776
+                "stale": "[yellow]stale[/yellow]",
17751777
                 "passed": "[green]pass[/green]",
17761778
                 "failed": "[red]fail[/red]",
17771779
                 "skipped": "[yellow]skip[/yellow]",
src/loader/runtime/workflow_timeline_read_model.pymodified
@@ -158,6 +158,8 @@ def workflow_timeline_highlights(
158158
             prefix = "Skipped verify:"
159159
         elif verify_entry.policy_outcome == "pending":
160160
             prefix = "Verify pending:"
161
+        elif verify_entry.policy_outcome == "stale":
162
+            prefix = "Verify stale:"
161163
         else:
162164
             prefix = "Verify observed:"
163165
         highlights.append(prefix + " " + workflow_entry_explanation(verify_entry))
tests/test_inspection.pymodified
@@ -529,6 +529,53 @@ def _persist_session_with_pending_verification(temp_dir: Path) -> str:
529529
     return snapshot.session_id
530530
 
531531
 
532
+def _persist_session_with_stale_verification(temp_dir: Path) -> str:
533
+    snapshot = SessionSnapshot(
534
+        session_id="20260406T160700Z-stale1234",
535
+        created_at="2026-04-06T16:07:00Z",
536
+        updated_at="2026-04-06T16:07:30Z",
537
+        messages=[
538
+            Message(role=Role.USER, content="Keep working on the runtime"),
539
+            Message(role=Role.ASSISTANT, content="Fresh verification is required again."),
540
+        ],
541
+        current_task="Keep working on the runtime",
542
+        runtime_owner_type="RuntimeHandle",
543
+        runtime_owner_path="runtime-handle",
544
+        workflow_mode="execute",
545
+        permission_mode="workspace-write",
546
+        prompt_format="native",
547
+        prompt_sections=["Runtime Config", "Workflow Context", "Mode Guidance"],
548
+        workflow_timeline=[
549
+            WorkflowTimelineEntry(
550
+                timestamp="2026-04-06T16:07:30Z",
551
+                kind="verify_observation",
552
+                mode="execute",
553
+                reason_code="verification_stale",
554
+                summary="verify: previous verification became stale after new mutating work",
555
+                decision_kind="forced",
556
+                policy_stage="verification",
557
+                policy_outcome="stale",
558
+                verification_observations=[
559
+                    VerificationObservation(
560
+                        status="stale",
561
+                        summary=(
562
+                            "verification became stale for `uv run pytest -q` "
563
+                            "after new mutating work"
564
+                        ),
565
+                        command="uv run pytest -q",
566
+                        kind="runtime",
567
+                        detail="write changed src/loader/runtime/finalization.py",
568
+                    )
569
+                ],
570
+                prompt_format="native",
571
+                prompt_sections=["Runtime Config", "Workflow Context", "Mode Guidance"],
572
+            )
573
+        ],
574
+    )
575
+    SessionStore(temp_dir).save(snapshot)
576
+    return snapshot.session_id
577
+
578
+
532579
 @pytest.mark.asyncio
533580
 async def test_collect_doctor_report_passes_for_healthy_workspace(temp_dir: Path) -> None:
534581
     _write_python_workspace(temp_dir)
@@ -842,6 +889,30 @@ def test_collect_status_snapshot_surfaces_pending_verification(
842889
     ]
843890
 
844891
 
892
+def test_collect_status_snapshot_surfaces_stale_verification(
893
+    temp_dir: Path,
894
+) -> None:
895
+    _write_python_workspace(temp_dir)
896
+    _ensure_loader_dirs(temp_dir)
897
+    _persist_session_with_stale_verification(temp_dir)
898
+
899
+    snapshot = collect_status_snapshot(temp_dir)
900
+
901
+    assert snapshot.latest_policy_summary is not None
902
+    assert "verification_stale" in snapshot.latest_policy_summary
903
+    assert "policy-outcome=stale" in snapshot.latest_policy_summary
904
+    assert snapshot.latest_policy_observed_verification == [
905
+        "verification became stale for `uv run pytest -q` after new mutating work [write changed src/loader/runtime/finalization.py]"
906
+    ]
907
+    assert [item.status for item in snapshot.recent_verification] == ["stale"]
908
+    assert [item.command for item in snapshot.recent_verification] == [
909
+        "uv run pytest -q"
910
+    ]
911
+    assert [item.detail for item in snapshot.recent_verification] == [
912
+        "write changed src/loader/runtime/finalization.py"
913
+    ]
914
+
915
+
845916
 def test_collect_prompt_diff_uses_persisted_prompt_history(temp_dir: Path) -> None:
846917
     _write_python_workspace(temp_dir)
847918
     _ensure_loader_dirs(temp_dir)
@@ -1000,6 +1071,29 @@ def test_workflow_command_renders_policy_accountability_context(
10001071
     assert "handoff" not in policy_result.output
10011072
 
10021073
 
1074
+def test_workflow_command_renders_stale_verification_context(
1075
+    temp_dir: Path,
1076
+    monkeypatch: pytest.MonkeyPatch,
1077
+) -> None:
1078
+    _write_python_workspace(temp_dir)
1079
+    _ensure_loader_dirs(temp_dir)
1080
+    session_id = _persist_session_with_stale_verification(temp_dir)
1081
+    runner = CliRunner()
1082
+
1083
+    monkeypatch.chdir(temp_dir)
1084
+
1085
+    result = runner.invoke(cli_main_module.workflow_cli, ["show"])
1086
+
1087
+    assert result.exit_code == 0
1088
+    assert session_id in result.output
1089
+    assert "Verify stale:" in result.output
1090
+    assert "verification_stale" in result.output
1091
+    assert "policy-outcome=stale" in result.output
1092
+    assert "Observed Verification" in result.output
1093
+    assert "uv run pytest -q" in result.output
1094
+    assert "new mutating work" in result.output
1095
+
1096
+
10031097
 def test_collect_workflow_timeline_can_focus_on_policy_accountability(
10041098
     temp_dir: Path,
10051099
 ) -> None:
tests/test_workflow_timeline_read_model.pymodified
@@ -159,6 +159,41 @@ def test_project_workflow_timeline_highlights_pending_verification() -> None:
159159
     assert any(item.startswith("Verify pending:") for item in projection.highlights)
160160
 
161161
 
162
+def test_project_workflow_timeline_highlights_stale_verification() -> None:
163
+    entries = [
164
+        WorkflowTimelineEntry(
165
+            timestamp="2026-04-09T12:04:00Z",
166
+            kind="verify_observation",
167
+            mode="execute",
168
+            reason_code="verification_stale",
169
+            summary="verify: previous verification became stale after new mutating work",
170
+            decision_kind="forced",
171
+            policy_stage="verification",
172
+            policy_outcome="stale",
173
+            verification_observations=[
174
+                VerificationObservation(
175
+                    status="stale",
176
+                    summary=(
177
+                        "verification became stale for `pytest -q` after new mutating work"
178
+                    ),
179
+                    command="pytest -q",
180
+                    kind="runtime",
181
+                    detail="write changed README.md",
182
+                )
183
+            ],
184
+        )
185
+    ]
186
+
187
+    projection = project_workflow_timeline(entries, accountability_only=True)
188
+
189
+    assert projection.latest_policy_summary is not None
190
+    assert "policy-outcome=stale" in projection.latest_policy_summary
191
+    assert "observed=verification became stale for `pytest -q` after new mutating work [write changed README.md]" in (
192
+        projection.latest_policy_summary
193
+    )
194
+    assert any(item.startswith("Verify stale:") for item in projection.highlights)
195
+
196
+
162197
 def test_project_workflow_timeline_applies_policy_filters_and_limits() -> None:
163198
     entries = [
164199
         WorkflowTimelineEntry(