Skip to content

Adds a restart-surviving demo host - #15

Merged
johnnyt merged 9 commits into
mainfrom
sp-4an.4-restart-demo
Aug 22, 2026
Merged

Adds a restart-surviving demo host#15
johnnyt merged 9 commits into
mainfrom
sp-4an.4-restart-demo

Conversation

@johnnyt

@johnnyt johnnyt commented Aug 22, 2026

Copy link
Copy Markdown
Member

Why

The sp-4an charter's validation gate: the design rule says the first
production embedder drives the API, so before calling the storage
behaviour, lifecycle, and executor seam stable, a concrete embedder-shaped
pipeline has to drive them end to end. sp-4an.4 asks for a demo host that
runs a multi-step chart across a simulated restart with no Session
process - persist mid-run, drop everything volatile, cold-boot from the
run id alone, continue, finish - with a pending durable timer and an
in-flight async invocation live at the kill point.

What

A demo embedder under test/support/demo/ (Host, durable Ledger,
volatile Runtime, an ADR-0051 invoke handler, and the Scenario bodies),
driven by ordinary tests so mix quality is the acceptance gate:

  • straight-through run to :completed, executor call log asserted on
    exact contents;
  • the restart: kill at enriching, boot from the run id, recover/1
    re-arms timers from durable rows and re-establishes the invocation
    through the handler palette (different live pid, dead old pid), the
    run finishes with the identical call log - nothing re-emitted, per
    st-ADR-0060's "resume restores position, not liveness";
  • replay determinism: the recorded tape (plus the recorded session id,
    the one generated create input) against a fresh store reproduces the
    configuration sequence and the effect structs field for field;
  • the wrong-revision refusal: stepping the stored run against a
    different compilation returns {:error, {:identity_mismatch, expected, actual}} with both identities and an untouched stored position;
  • the same scenario bodies over Storage.Ecto/Postgres, exercising the
    advisory-plus-row lock on every step;
  • docs: docs/restart-demo.md walkthrough, a README pointer, and a
    dated validation note on ADR-0004 naming the one finding (byte-identical
    replay needs the recorded session id; initialize: [session_id: ...]
    already covers it - no new surface).

Notes

  • Full mix quality green before this push (142 tests, 95.8% coverage,
    mix gate.verify attested: scope all, no profile). No changelog
    fragment: tests and docs only, no public lib/ surface changed.
  • Mixed-writer branch history: the first commits came from a
    since-halted detached loop agent; the takeover worker verified all of
    it hands-on (every sabotage mutation re-run and confirmed red against
    the intended assertion, then reverted) and the full gate above ran
    against the final HEAD.
  • The plan's review-type manual-verification items (reading the host as
    prose, doc readability) are deferred in
    docs/plans/260822-sp-4an.4-restart-demo-host.md for the operator's
    verify walk; only literally-executed checks are marked done.
  • The demo owns its durable timer rows deliberately (durable-timers
    "Route B"); handing SendDelayed/Cancel to a real scheduler is
    statifier_oban's charter and a natural mirrored follow-up recorded in
    the plan's Open Questions, not filed here.

Closes: sp-4an.4

Introduces the demo embedder that drives a multi-step chart through
the loop with no Session process: a durable ledger, a volatile
runtime, an ADR-0051 invoke handler, and a host struct whose executor
seam records every effect. The first scenario runs the chart straight
through to completion with no restart, proving the host and the seam
before Phase 2 adds the kill point.

Refs: sp-4an.4
Kills the volatile runtime mid-run and proves the loop finishes
anyway: recover/1 re-arms durable timers unconditionally and
re-establishes only the invocations the engine still considers live,
and restart/1 composes stop, boot, and recover into one cold start.
The restart test walks the kill point, the dead pid, the freshly
recompiled chart, and the tail to completion, then asserts no side
effect or executor call ran twice across the restart.

Refs: sp-4an.4
Asserts the executor call log on exact contents - identical to the
straight-through run's, so the restart re-emitted nothing - and the
idempotency ledger on exact keys (the previous uniqueness check was
vacuous against a ledger that dedups by construction). Notes boot/4
and recover/1 as the host moduledoc's two deliberate cold-boot
exceptions. Both Phase 1-2 sabotage mutations (run_status :done ->
:active; write_run position: :skip on :update) were re-run, confirmed
red, and reverted; the plan's manual checkboxes are ticked.

Refs: sp-4an.4
Carries run_id on the across-restart result so the test can assert
the ledger holds no open timer row once the run completes - the
reminder-timer armed after the restart was cancelled and dropped,
and the fired sla-timer's row was consumed.

Refs: sp-4an.4
Phase 3 of docs/plans/260822-sp-4an.4-restart-demo-host.md.
Scenario.replay/4 drives the recorded tape against a fresh store with
no Demo.Runtime at all; the test asserts the configuration sequence
and the effect sequence reproduce the original struct for struct. The
one generated create input - the session id MachineState.new/2 stamps
into the datamodel_init effect - is part of the recorded inputs, so
the replay re-supplies it via initialize: [session_id: ...]. The
across-restart scenario now carries the input tape over the restart
(the recorder's own log; boot/4 deliberately restores no tape).

The wrong-revision test steps the stored run against a different
compilation and asserts {:error, {:identity_mismatch, expected,
actual}} with both identities present and the stored position
untouched. Both sabotage mutations (identity-mismatch arm collapsed
to :chart_not_found; write_run position: :skip on :update) were run,
confirmed red on the intended assertions, and reverted.

Refs: sp-4an.4
Phase 4 of docs/plans/260822-sp-4an.4-restart-demo-host.md: the same
three scenario bodies - straight-through, across a restart, replay -
driven against Storage.Ecto over the ADR-0005 harness, with the
default AdapterLock serialization taking the advisory-plus-row lock
on every step. The restart variant asserts the post-restart boot
re-read the chart and run from the database and that the executor
call log is exactly the straight-through run's; the replay variant
re-supplies the recorded session id and reproduces the path struct
for struct. Sabotage coverage is carried by the shared scenario
bodies, per the plan's Testing Strategy.

Refs: sp-4an.4
Phase 5 of docs/plans/260822-sp-4an.4-restart-demo-host.md:
docs/restart-demo.md walks a host author through the kill point, the
survives/does-not-survive split, and the five obligations the position
leaves to the host, pointing at the tests as the executable truth; the
README gains a Surviving a restart pointer; ADR-0004 gains a dated
validation note recording that decisions 3, 4 and 6 were driven end to
end, with the one finding named - byte-identical replay needs the
recorded session id, already covered by initialize: [session_id: ...].

Also converts the plan's review-type manual-verification items back to
deferred status for the operator's verify walk; only literally-executed
checks (sabotage runs, repeated runs) stay checked.

Refs: sp-4an.4
The gate's sabotage scanner reads the note directly above each test;
the module-level pointer alone left the three Ecto variants unnamed.
Each note names the shared InMemory-variant mutation that reds it.

Refs: sp-4an.4
@johnnyt
johnnyt merged commit d4bd6ae into main Aug 22, 2026
1 check passed
@johnnyt
johnnyt deleted the sp-4an.4-restart-demo branch August 22, 2026 18:49
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.

1 participant