diff --git a/tests/engineering/test_prompt_history.py b/tests/engineering/test_prompt_history.py index 5d584372..5988853a 100644 --- a/tests/engineering/test_prompt_history.py +++ b/tests/engineering/test_prompt_history.py @@ -35,6 +35,33 @@ def test_projects_persisted_submission_and_execution_context_snapshot(self) -> N self.assertEqual(entry["producer_submission_contract_version"], "1.0") self.assertEqual(entry["execution_context_version"], "1.0") self.assertEqual(entry["execution_context"], {"context_version": "1.0", "mission_title": "Aurora"}) + + def test_imports_legacy_dismissal_evidence_once_into_canonical_storage(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + record_prompt_execution( + root, run_id="inbox-legacy-dismissal", terminal_state="BLOCKED", + prompt_title="Previously dismissed execution", executed_at="2026-08-08T10:00:00Z", + ) + audit = root / ".engineering" / "status" / "execution_dismissals.json" + audit.parent.mkdir(parents=True, exist_ok=True) + audit.write_text( + '[{"run_id":"inbox-legacy-dismissal","terminal_state":"BLOCKED",' + '"dismissed":true,"dismissed_at":"2026-08-08T10:01:00Z",' + '"dismissed_by":"dashboard_operator"}]', + encoding="utf-8", + ) + + imported = prompt_history(root)[0] + self.assertTrue(imported["dismissed"]) + self.assertEqual(imported["handling_state"], "DISMISSED") + self.assertFalse(imported["can_retry"]) + + audit.unlink() + persisted = prompt_history(root)[0] + self.assertTrue(persisted["dismissed"]) + self.assertEqual(persisted["dismissed_at"], "2026-08-08T10:01:00Z") + def test_records_terminal_run_and_serves_only_its_local_report(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) diff --git a/tools/engineering/ENGINEERING_INBOX_PROTOCOL.md b/tools/engineering/ENGINEERING_INBOX_PROTOCOL.md index 759a4fde..f3942244 100644 --- a/tools/engineering/ENGINEERING_INBOX_PROTOCOL.md +++ b/tools/engineering/ENGINEERING_INBOX_PROTOCOL.md @@ -223,6 +223,7 @@ remain immutable. A dismissed terminal execution is read-only: Retry, Resume, Dismiss and every other lifecycle-mutating action are unavailable and rejected server-side, including requests from a stale client. Queue Recovery remains the separate explicit operation for dependent Inbox work. Dismiss never resumes -that queue. +that queue. Existing records from the former JSON audit are imported +idempotently into SQLite; thereafter SQLite remains authoritative. Commands: `python3 -m tools.engineering.inbox_watcher once|run|status|install|uninstall|doctor|migrate-icloud-archives`. diff --git a/tools/engineering/storage.py b/tools/engineering/storage.py index e526fdfd..6a5b1ca3 100644 --- a/tools/engineering/storage.py +++ b/tools/engineering/storage.py @@ -20,6 +20,7 @@ DATABASE_FILENAME = "engineering.db" ENGINEERING_STORAGE_SCHEMA_VERSION = 18 JOURNAL_MODES = frozenset({"DELETE", "MEMORY"}) +LEGACY_DISMISSALS_PATH = Path(".engineering/status/execution_dismissals.json") class EngineeringStorageError(RuntimeError): @@ -577,6 +578,52 @@ def _schema_v18(connection: sqlite3.Connection) -> None: ) +def _import_legacy_execution_dismissals(root: Path, connection: sqlite3.Connection) -> None: + """Copy valid legacy dismissal evidence into the canonical datastore. + + The former JSON audit is retained as source evidence, but never consulted + by projections after its records have been copied to SQLite. Repeating the + import is safe: the canonical run key makes it idempotent and permits a + record whose history was backfilled later to be imported on a later open. + """ + path = root / LEGACY_DISMISSALS_PATH + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError: + return + except (OSError, json.JSONDecodeError): + return + if not isinstance(payload, list): + return + for record in payload: + if not isinstance(record, dict) or record.get("dismissed") is not True: + continue + run_id = record.get("run_id") + terminal_state = record.get("terminal_state") + dismissed_at = record.get("dismissed_at") + dismissed_by = record.get("dismissed_by") + if ( + not isinstance(run_id, str) + or not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", run_id) + or terminal_state not in {"COMPLETE", "BLOCKED", "FAILED"} + or not isinstance(dismissed_at, str) + or not dismissed_at.strip() + or not isinstance(dismissed_by, str) + or not dismissed_by.strip() + ): + continue + history_exists = connection.execute( + "SELECT 1 FROM prompt_execution_history WHERE run_id=?", (run_id,) + ).fetchone() + if history_exists is None: + continue + connection.execute( + "INSERT OR IGNORE INTO execution_dismissals(run_id,terminal_state,handling_state,dismissed_at,dismissed_by) " + "VALUES(?,?,?,?,?)", + (run_id, terminal_state, "DISMISSED", dismissed_at.strip(), dismissed_by.strip()), + ) + + MIGRATIONS: dict[int, Migration] = { 1: _schema_v1, 2: _schema_v2, @@ -954,6 +1001,7 @@ def open_storage( connection.execute( "INSERT INTO engineering_schema_migrations(version) VALUES(?)", (version,) ) + _import_legacy_execution_dismissals(root, connection) connection.execute("COMMIT") path.chmod(0o600) except (OSError, sqlite3.DatabaseError, EngineeringStorageError) as error: