Skip to content

feat(eventlog): mirror legacy runner milestones#182

Merged
hadamrd merged 10 commits into
trunkfrom
loop/167-feat-eventlog-mirror-legacy-runner-miles
Jun 2, 2026
Merged

feat(eventlog): mirror legacy runner milestones#182
hadamrd merged 10 commits into
trunkfrom
loop/167-feat-eventlog-mirror-legacy-runner-miles

Conversation

@hadamrd
Copy link
Copy Markdown
Owner

@hadamrd hadamrd commented Jun 2, 2026

Fixes #167

What changed

  • Added forge_loop.eventlog.legacy_mirror to translate selected legacy runner JSONL milestones into the durable SQLite/WAL event log.
  • Added durable EventKind values for tick start/end, PR open/merged, merge blocked, worktree reaped, and loop halt milestones.
  • Preserved legacy JSONL behavior by keeping mirroring opt-in through append_event(..., durable_mirror=...).
  • Added replay helper coverage for reconstructing task timelines from SQLite-only events and projection cursor persistence.

Acceptance criteria covered

  • Representative legacy runner events mirror to durable event kinds/payloads.
  • Repeat mirror calls are idempotent through stable keys derived from legacy kind/tick/issue/worker/PR.
  • Reopening SQLite and replaying from sequence 0 reconstructs the task timeline.
  • JSONL event output is still written when durable mirroring is enabled.
  • Merge-blocked critic verdicts are mirrored as both critique and merge-blocked milestones.

Verification

  • env -u VIRTUAL_ENV uv run --extra dev pytest tests/test_eventlog_legacy_mirror.py -q
  • env -u VIRTUAL_ENV uv run --extra dev pytest tests/test_eventlog_sqlite.py -q
  • env -u VIRTUAL_ENV uv run --extra dev pytest tests/test_events.py -q
  • env -u VIRTUAL_ENV uv run --extra dev ruff check src/forge_loop/eventlog/models.py src/forge_loop/eventlog/legacy_mirror.py src/forge_loop/eventlog/__init__.py src/forge_loop/events.py tests/test_eventlog_legacy_mirror.py
  • env -u VIRTUAL_ENV uv run --extra dev ruff format --check src/forge_loop/eventlog/models.py src/forge_loop/eventlog/legacy_mirror.py src/forge_loop/eventlog/__init__.py src/forge_loop/events.py tests/test_eventlog_legacy_mirror.py
  • env -u VIRTUAL_ENV uv run --extra dev mypy src/forge_loop/eventlog/models.py src/forge_loop/eventlog/legacy_mirror.py src/forge_loop/eventlog/__init__.py src/forge_loop/events.py
  • env -u VIRTUAL_ENV uv run --extra dev pyright src/forge_loop/eventlog/models.py src/forge_loop/eventlog/legacy_mirror.py src/forge_loop/eventlog/__init__.py src/forge_loop/events.py

Lumen discovery was skipped because mcp__lumen__semantic_search was unavailable in this session.

Add a durable mirror for selected legacy runner JSONL milestones so current loop activity can be replayed from the SQLite/WAL event log without changing the JSONL operator stream. The mirror uses enum-based legacy discriminators, stable idempotency keys, and focused replay helpers/tests for issue #167.
@hadamrd hadamrd added the critic:blocking Critic found blocking issues label Jun 2, 2026
Copy link
Copy Markdown
Owner Author

@hadamrd hadamrd left a comment

Choose a reason for hiding this comment

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

[sev1/correctness] The mirror is never wired into any normal runner call site: the PR only adds a durable_mirror escape hatch and tests that pass a mirror manually. Issue #167's goal is to mirror existing runner JSONL milestones into SQLite/WAL during runner operation; as written, real ticks still only append JSONL and the durable event log remains empty. Construct a LegacyEventMirror(SqliteEventLog(...)) in the runner path and pass it through the existing milestone emissions, while keeping JSONL output unchanged.

Comment thread src/forge_loop/events.py Outdated
DeprecationWarning,
stacklevel=3,
)
durable_mirror = fields.pop("durable_mirror", None)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sev1/correctness] The mirror is never wired into any normal runner call site: the PR only adds a durable_mirror escape hatch and tests that pass a mirror manually. Issue #167's goal is to mirror existing runner JSONL milestones into SQLite/WAL during runner operation; as written, real ticks still only append JSONL and the durable event log remains empty. Construct a LegacyEventMirror(SqliteEventLog(...)) in the runner path and pass it through the existing milestone emissions, while keeping JSONL output unchanged.

Copy link
Copy Markdown
Owner Author

@hadamrd hadamrd left a comment

Choose a reason for hiding this comment

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

[sev2/product] No-scaffold-theatre violation: LegacyEventMirror is a new adapter/plugin surface with no downstream consumer wired up in the same PR. Either wire the runner to use it against the repo's durable event log or quarantine the mirror behind the experimental extra until it has a real in-repo consumer.

pr_url: str | None = None


class LegacyEventMirror:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sev2/product] No-scaffold-theatre violation: LegacyEventMirror is a new adapter/plugin surface with no downstream consumer wired up in the same PR. Either wire the runner to use it against the repo's durable event log or quarantine the mirror behind the experimental extra until it has a real in-repo consumer.

Copy link
Copy Markdown
Owner Author

@hadamrd hadamrd left a comment

Choose a reason for hiding this comment

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

[sev2/correctness] The idempotency key includes worker=..., but _AppendSpec.worker is never populated for worker/session/heartbeat records, so distinct worker progress records for the same kind/tick/issue collapse to one durable event. Populate worker from session_id, worker id, or another stable record field for worker milestones, and add a test with two distinct worker records that both persist.

return payload


def _idempotency_key(legacy_kind: LegacyRunnerEventKind, spec: _AppendSpec) -> str:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sev2/correctness] The idempotency key includes worker=..., but _AppendSpec.worker is never populated for worker/session/heartbeat records, so distinct worker progress records for the same kind/tick/issue collapse to one durable event. Populate worker from session_id, worker id, or another stable record field for worker milestones, and add a test with two distinct worker records that both persist.

Copy link
Copy Markdown
Owner Author

@hadamrd hadamrd left a comment

Choose a reason for hiding this comment

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

[sev2/tests] replay_task_timeline is a new public replay helper but only has a happy-path test. Add an adversarial test for empty/non-task events or malformed payloads so the replay contract is covered on bad input, per TE-002.

)


def replay_task_timeline(events: Iterable[EventEnvelope]) -> dict[str, dict[str, Any]]:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sev2/tests] replay_task_timeline is a new public replay helper but only has a happy-path test. Add an adversarial test for empty/non-task events or malformed payloads so the replay contract is covered on bad input, per TE-002.

Copy link
Copy Markdown
Owner Author

@hadamrd hadamrd left a comment

Choose a reason for hiding this comment

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

Manifesto violations:

  • [sev2] testing.md#TE-002+def replay_task_timeline(events: Iterable[EventEnvelope]) -> dict[str, dict[str, Any]]: → Add at least one adversarial test for replay_task_timeline, such as empty input, events without an issue/task_id, or malformed tick/status payloads, and assert the observable replay result.

@hadamrd
Copy link
Copy Markdown
Owner Author

hadamrd commented Jun 2, 2026

Monitor follow-up: reran direct Forge critic after the latest patches. The final pass returned only a synthetic sev3 note saying it found no blocking defect, while GitHub replay is green on head 13c8696. Focused local verification: pytest tests/test_eventlog_legacy_mirror.py tests/test_eventlog_sqlite.py tests/test_events.py tests/test_state.py -q => 47 passed; ruff check/format, mypy, and pyright passed on the touched eventlog/event/state files.

@hadamrd hadamrd removed the critic:blocking Critic found blocking issues label Jun 2, 2026
@hadamrd hadamrd merged commit 8fcfd84 into trunk Jun 2, 2026
2 checks passed
@hadamrd hadamrd deleted the loop/167-feat-eventlog-mirror-legacy-runner-miles branch June 2, 2026 22:57
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.

feat(eventlog): mirror legacy runner milestones into SQLite WAL

1 participant