feat(eventlog): mirror legacy runner milestones#182
Conversation
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
left a comment
There was a problem hiding this comment.
[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.
| DeprecationWarning, | ||
| stacklevel=3, | ||
| ) | ||
| durable_mirror = fields.pop("durable_mirror", None) |
There was a problem hiding this comment.
[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.
hadamrd
left a comment
There was a problem hiding this comment.
[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: |
There was a problem hiding this comment.
[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.
hadamrd
left a comment
There was a problem hiding this comment.
[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: |
There was a problem hiding this comment.
[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.
hadamrd
left a comment
There was a problem hiding this comment.
[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]]: |
There was a problem hiding this comment.
[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.
hadamrd
left a comment
There was a problem hiding this comment.
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.
Address the critic blockers on PR #182: automatically mirror canonical runner JSONL emissions into .forge/events.db, support actual worker_start/worker_done records, include stable worker identifiers in idempotency keys, and cover malformed replay input. JSONL remains authoritative if durable mirroring fails.
|
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: |
Fixes #167
What changed
forge_loop.eventlog.legacy_mirrorto translate selected legacy runner JSONL milestones into the durable SQLite/WAL event log.EventKindvalues for tick start/end, PR open/merged, merge blocked, worktree reaped, and loop halt milestones.append_event(..., durable_mirror=...).Acceptance criteria covered
Verification
env -u VIRTUAL_ENV uv run --extra dev pytest tests/test_eventlog_legacy_mirror.py -qenv -u VIRTUAL_ENV uv run --extra dev pytest tests/test_eventlog_sqlite.py -qenv -u VIRTUAL_ENV uv run --extra dev pytest tests/test_events.py -qenv -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.pyenv -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.pyenv -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.pyenv -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.pyLumen discovery was skipped because
mcp__lumen__semantic_searchwas unavailable in this session.