Skip to content

feat(kernel): loop-engineer/event@1 EventStore + SQLite/WAL + deterministic reducer (#50) - #62

Merged
SollanSystems merged 1 commit into
mainfrom
feat/s4-eventstore
Jul 13, 2026
Merged

SollanSystems merged 1 commit into
mainfrom
feat/s4-eventstore

Conversation

@SollanSystems

Copy link
Copy Markdown
Owner

Closes #50 — Phase 1 foundation: append-only event log + deterministic replay.

What

  • schemas/event.schema.jsonevent@1 envelope (run_id, monotonic per-run sequence, event_id, type, actor, ts, payload, artifact_hashes); 4 event types with required payload fields, incl. iteration_id on receipt_appended.
  • loop/events.pyEventStore Protocol + SQLiteEventStore: WAL, synchronous=FULL, busy_timeout=5000, append-only (no-update/no-delete triggers), expected_sequence CAS, duplicate event_id rejection. validate_event honors --mode basic|strict|release with a type-checked structural fallback (S3 parity lesson). Read-cursor contract: since_sequence=None → full stream; int N (including 0) → strictly after N — the N=0 resume/fold pattern round-trips byte-identically.
  • loop/reducer.py — deterministic fold enforcing FSM transition legality, G1 all-required completion, and terminal immutability at replay time via the same loop.fsm/loop.completion functions the writers use (two-layer enforcement, never re-implemented).
  • 32 targeted tests — replay determinism, all 4 Succeeded-G1 tamper cases, CAS success+conflict halves, PRAGMA asserts, N=0 resume regression.
  • reference/repo-os-contract.md §16 — event-sourcing layer documented.

Verification

  • Deterministic gates 7/7 in the lane worktree: extras 618 passed / 15 skipped, pyyaml-only 588 / 45, targeted 32/32, self_eval 13/13 (baselines at 157c31b: 586/15, 563/38 — zero regressions).
  • 9/9 independent governor probes (N=0 resume round-trip, receipt iteration_id enforced + type-checked in both modes, CAS success).
  • Fresh sonnet review (attempt 1: 1 BLOCKER + 1 MAJOR + 4 minors → bounded repair) + focused fresh re-review: PASS on all 6 findings, no new issues.
  • Claudex lane: gpt-5.6-terra medium→high, receipts cx_s4_eventstore_a1 (repair_requested) + cx_s4_eventstore_a2 (accepted); codex sessions 019f5cf6…, 019f5d0a….

Kernel writers byte-unchanged; pure-stdlib runtime preserved (jsonschema/pyyaml stay optional extras).

https://claude.ai/code/session_01EJ8zA8Cbi4o2amawpj8bZW

…tore, deterministic reducer (#50)

- schemas/event.schema.json: event@1 envelope — run_id/sequence/event_id/
  type/actor/ts/payload/artifact_hashes; 4 event types (contract_opened,
  iteration_appended, receipt_appended, terminal_written) with required
  payload fields incl. receipt iteration_id
- loop/events.py: EventStore Protocol + SQLiteEventStore (WAL,
  synchronous=FULL, busy_timeout=5000, append-only via no-update/no-delete
  triggers, expected_sequence CAS, duplicate event_id rejection);
  validate_event honors --mode basic|strict|release with type-checked
  structural fallback; read(since_sequence=None) = full stream, int N
  (incl. 0) = strictly after N — the N=0 resume pattern round-trips
- loop/reducer.py: deterministic fold — FSM transition legality, G1
  all-required completion, terminal immutability enforced at replay via
  the same loop.fsm/loop.completion functions the writers use
- tests: 32 targeted (replay determinism, 4-of-4 Succeeded G1 tamper
  cases, CAS success+conflict, PRAGMA asserts, N=0 resume regression)
- reference/repo-os-contract.md §16: event-sourcing layer documented

Suites: extras 618 passed/15 skipped; pyyaml-only 588/45. Kernel writers
byte-unchanged; jsonschema/pyyaml stay optional extras.

Claude-Session: https://claude.ai/code/session_01EJ8zA8Cbi4o2amawpj8bZW
Copilot AI review requested due to automatic review settings July 13, 2026 21:17
@SollanSystems
SollanSystems enabled auto-merge (squash) July 13, 2026 21:17
@SollanSystems
SollanSystems merged commit 3952641 into main Jul 13, 2026
10 checks passed
@SollanSystems
SollanSystems deleted the feat/s4-eventstore branch July 13, 2026 21:17

@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: c1e7bf85f0

ℹ️ 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/events.py
Comment on lines +217 to +219
if expected_sequence is not None and expected_sequence != next_sequence:
conn.execute("ROLLBACK")
raise SequenceConflictError(f"expected next sequence {next_sequence} for run_id {run_id!r}, caller supplied {expected_sequence}")

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 Preserve duplicate-event retries under CAS

When a CAS append succeeds but the caller times out and retries the same event_id with the same expected_sequence, this check runs before the UNIQUE(event_id) insert and raises SequenceConflictError because the next sequence has already advanced. That makes the advertised duplicate-event retry path unavailable for the normal CAS retry scenario, so clients cannot distinguish a successfully persisted retry from a real sequence race; check for an existing event_id before treating the stale expected sequence as a conflict, or return the existing event idempotently.

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

Introduces the Phase 1 event-sourcing foundation for loop-engineer by adding an event@1 envelope schema, a SQLite/WAL-backed append-only EventStore, and a deterministic reducer that can replay an event stream into a stable projection (with targeted tests validating determinism, CAS semantics, and tamper resistance).

Changes:

  • Add loop.events.SQLiteEventStore (WAL + append-only triggers + expected-sequence CAS) and validate_event with structural fallback.
  • Add loop.reducer.reduce_events() deterministic fold enforcing replay invariants (FSM legality, G1 completion semantics, terminal immutability).
  • Add schema + contract documentation + targeted pytest coverage for store behavior and replay determinism/resume.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/test_reducer.py Adds deterministic replay/resume tests and tamper/immutability negative cases.
scripts/test_eventstore.py Adds validation, WAL/append-only, CAS, duplicate rejection, and concurrency tests for the SQLite store.
schemas/event.schema.json Defines the loop-engineer/event@1 JSON schema envelope and artifact hash shape.
reference/repo-os-contract.md Documents the new event-sourcing layer boundary and enforcement split (store shape vs reducer semantics).
loop/reducer.py Implements the deterministic reducer and replay-time domain invariant enforcement.
loop/events.py Implements EventStore protocol, SQLiteEventStore, and event validation (jsonschema + structural fallback).
loop/init.py Exposes eventstore/reducer APIs from the package top-level.

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

Comment thread loop/events.py
Comment on lines +226 to +228
except sqlite3.IntegrityError as exc:
conn.execute("ROLLBACK")
raise DuplicateEventError(record["event_id"]) from exc
Comment thread loop/events.py
from typing import Any, Mapping, Protocol, Sequence, runtime_checkable

from .contract import ContractIssue, _resolve_requested_mode, _schemas_dir
from .emit import _ITERATION_OUTCOMES, _RECEIPT_OUTCOMES, _RECEIPT_ROLES
Comment thread loop/reducer.py
Comment on lines +78 to +82
entry = dict(payload, event_id=event["event_id"], causation_id=event.get("causation_id"), correlation_id=event.get("correlation_id"), ts=event["ts"])
new_state["runlog_entries"] = new_state["runlog_entries"] + [entry]
elif event_type == "receipt_appended":
entry = dict(payload, event_id=event["event_id"], causation_id=event.get("causation_id"), correlation_id=event.get("correlation_id"), ts=event["ts"])
new_state["receipts"] = new_state["receipts"] + [entry]
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.

Introduce EventStore protocol and SQLite/WAL implementation

2 participants