Skip to content

feat(cli): read-only loop status + loop replay runtime verbs (Refs #55) - #70

Merged
SollanSystems merged 1 commit into
mainfrom
feat/s2-status-replay-verbs
Jul 15, 2026
Merged

SollanSystems merged 1 commit into
mainfrom
feat/s2-status-replay-verbs

Conversation

@SollanSystems

Copy link
Copy Markdown
Owner

Refs #55 (phase 1 partial — delivers status/replay; the crash-safe single-worker run verb follows separately; #55 stays open).

phase3-run S2 — shipped via the governed Claudex lane (worker gpt-5.6-terra/medium, codex session 019f65a3-b696-76d3-b779-68298ccf4046, receipt cx_s2_verbs_a1, attempt 1 accepted).

What

  • loop status <workspace> — folds .loop/events.db once through the deterministic reducer and reports the projected run state (terminal/in-flight, iteration cursor, active task, completion-policy satisfaction) plus whether .loop/state.json agrees, with typed ContractIssue-shaped divergence findings.
  • loop replay <workspace> — double-folds the log and asserts identical projections (determinism proof), surfaces illegal event sequences as typed findings (no tracebacks), and checks the desynced-terminal window bidirectionally (file-without-event, event-without-file, value mismatch — the R-001 split-write class, including terminal_superseded corrections the snapshot never absorbed).

Mechanics: store opened strictly read-only (file:…?mode=ro URI) — loop/events.py/loop/reducer.py byte-unchanged; --mode joins the existing _extract_mode_flag path (reducer domain checks run unconditionally); exit codes mirror doctor (0 healthy / 1 problem found / 2 cannot-attempt via new typed RuntimeStoreError: missing, empty, corrupt store, ambiguous run_id). Decides the previously-open §16 spec questions: .loop/events.db location, one-run-per-store discovery (multi-run deferred); the stale "undecided" sentence in reference/repo-os-contract.md is updated.

Evidence

  • Deterministic gates 4/4 exit 0 in a fresh worktree; totals exact: extras 721 passed / 16 skipped (694+27), pyyaml-only 671 / 66 (644+27); 27 new unconditional tests (no skip/xfail; the jsonschema-absent branch is asserted env-honestly, not skipped).
  • Fresh sonnet review: PASS, 0 blockers, 2 advisories (pre-snapshot file-set in the no-write test can't see net-new .loop/ files; per-row validate_event result discarded — both noted, non-blocking).
  • Governor holdout gate (5 probes the worker never saw): Succeeded 5/5, false_completion: false — two-run ambiguity typed exit-2, raw-SQL tamper true-negative (kernel trigger blocks it, replay stays clean), symlinked events.db read-through with zero writes, terminal_superseded desync flagged against the corrected terminal, raw-INSERT empty-run_id row surfaces as typed ambiguous_run_id (kernel append already rejects run_id="" at write time — probe premise corrected against source).

…er the EventStore

New loop/runtime.py: status_report projects the event log once via the
deterministic reducer and reconciles it with .loop/state.json (typed
divergence findings); replay_report double-folds for determinism, reports
illegal event sequences as typed findings (never a traceback), and checks the
desynced-terminal window bidirectionally (file-without-event,
event-without-file, value mismatch — the R-001 class).

The store opens strictly read-only (file:...?mode=ro URI); loop/events.py and
loop/reducer.py are byte-unchanged. --mode joins the existing
_extract_mode_flag path; reducer domain checks run unconditionally. Exit
codes mirror doctor: 0 healthy, 1 ran-and-found-a-problem, 2
cannot-attempt-a-report (missing/empty/corrupt store, ambiguous run_id via
new RuntimeStoreError). This slice decides the previously-open §16 questions:
store location .loop/events.db, one run per store (multi-run deferred).

27 new unconditional tests in scripts/test_loop_cli_status_replay.py.

Refs #55 (phase 1 partial — status/replay; the run verb remains).
Copilot AI review requested due to automatic review settings July 15, 2026 12:20
@SollanSystems
SollanSystems enabled auto-merge (squash) July 15, 2026 12:20
@SollanSystems
SollanSystems merged commit 2e7473e into main Jul 15, 2026
11 checks passed
@SollanSystems
SollanSystems deleted the feat/s2-status-replay-verbs branch July 15, 2026 12:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7b3289bf7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread loop/runtime.py
Comment on lines +145 to +147
disk_terminal = json.loads(paths.terminal.read_text(encoding="utf-8")) if paths.terminal.exists() else None
except (OSError, json.JSONDecodeError):
disk_terminal = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat malformed terminal files as present

When terminal_state.json exists but is invalid JSON (or unreadable) and the event log has no terminal event, this except collapses the file to None; the next branch then sees both sides as absent and replay returns ok: true with no findings. That misses the file-without-event half of the terminal desync window for exactly the corrupt terminal-file case, so the parse/read failure should produce a finding or preserve a separate “file present” marker instead of treating it as absent.

Useful? React with 👍 / 👎.

Comment thread loop/runtime.py
Comment on lines +74 to +75
if not isinstance(run_id, str):
raise RuntimeStoreError("corrupt_store", f"event store has invalid run_id: {path}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty run IDs during discovery

If a store is tampered or raw-inserted with a single run_id = '', discovery accepts it because it only checks the type; the CLI then reports an illegal_event_sequence problem and exits like a replay failure rather than treating the store as un-attemptable/corrupt. Since event@1 requires a non-empty run_id, this guard should reject empty strings before returning the discovered run id.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new read-only runtime CLI verbs (status and replay) that project the .loop/events.db event log into a deterministic runtime projection, optionally validate under the existing --mode contract, and emit JSON reports with doctor-style exit codes.

Changes:

  • Introduces loop/runtime.py with status_report() and replay_report() over a strictly read-only SQLite connection.
  • Extends the CLI entrypoint to expose loop status / loop replay and route --mode consistently with doctor-family commands.
  • Adds contract-style tests for the new verbs and updates the repo-OS contract reference to pin .loop/events.db as the on-disk location.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
scripts/test_loop_cli_status_replay.py New CLI + runtime report contract tests for status/replay, including read-only behavior assertions.
reference/repo-os-contract.md Updates event-store location and clarifies single-run-per-store discovery for runtime readers.
loop/runtime.py New read-only runtime reporting implementation over the SQLite event store (status + replay).
loop/main.py Registers status/replay commands, documents them in --help, and wires --mode + exit codes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread loop/runtime.py
Comment on lines +10 to +13
from .completion import CompletionPolicyError, criteria_satisfy_completion
from .contract import ContractIssue
from .events import EVENT_SCHEMA_ID, EVENT_TYPES, validate_event
from .paths import resolve_loop_paths
Comment on lines +136 to +141
files = sorted((workspace / ".loop").iterdir())
before = {p.name: (p.stat().st_mtime_ns, hashlib.sha256(p.read_bytes()).hexdigest()) for p in files if p.is_file()}
status_report(workspace)
replay_report(workspace)
after = {p.name: (p.stat().st_mtime_ns, hashlib.sha256(p.read_bytes()).hexdigest()) for p in files if p.is_file()}
assert after == before
SollanSystems added a commit that referenced this pull request Jul 15, 2026
…afe resume (#71)

One bounded dispatch step per call over the SQLite EventStore: reducer-projected
next-task selection (depends_on + event-log task_passed completions; TASKS.json stays
read-only), a pluggable verifier seam defaulting to fail-loud
VerifierNotImplementedError (exit 2, zero writes), EVENT-APPEND-FIRST crash safety (the
CAS iteration_appended COMMIT is the single source-of-truth boundary; RUNLOG.md /
state.json materialize idempotently from the recorded payload; resume is the same call
replaying missing materializations), and Succeeded-only terminals via the existing
emit.terminate / emit.sync_state_to_terminal. Adds an idempotency guard to
emit.append_iteration (duplicate iteration_id skips the RUNLOG block, still writes
state.json). 20 new unconditional tests incl. 3 real-SIGKILL crash-injection tests at
the named commit barriers.

Refs #55 (phase 1 partial: status/replay landed in #70; isolated verifier subprocess +
typed stubs follow in the next slice).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants