Adds Driver.new/3's after_step: callback - #91
Merged
Merged
Conversation
ADR-0008's 2026-09-08 amendment: a host keeping its own record of what a run did cannot see the steps this package takes for it - decision 3's answer steps the parent from inside the child's drive, and Runs.fail/4's driver: option steps the parent from a call about a different run entirely. Neither reaches a host, so its fold and the stored checkpoint disagree for exactly the deliveries a subchart's lifecycle is made of. after_step: is that seam: a 3-arity callback on the driver, nil by default, called with the id of the run that was stepped, that step's machine state, and the whole effect list it produced. The seam the amendment's clause 1 left open, having ruled out widening a public Runs return: a package-internal step_reporter: option threaded to the persist tail, which hands it the effect list before the lifecycle split and once the write has landed. The driver sets it only when a callback is in play, and sets it to a fun that sends the list back on this drive's own reference - the same buffer shape a dispatched invocation's answer already uses. The driver fires the host's callback itself, after the entry point has returned, so the callback for a run never runs inside that run's exclusion. Firing at Runs.create/4 and the private step/5 covers every door: the answer path reaches step/5 through reenter/5 on a driver over the parent's chart, so the parent's step reports under the parent's id with nothing plumbed for it. A discarded delivery and a cascade_cancel step nothing and report nothing; the return is ignored and a raise propagates. A per-call after_step: outranks the driver's own, the widening the amendment's closing section left to this bead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
sp-c48(SF039, Lane P): the code half of ADR-0008's2026-09-08
after_step:amendment (sp-sli, merged at 95ea54f, still atproposed -
sp-nhlflips it).The seam
Clause 1 of the amendment left one thing to this bead and ruled out the
easy answer: the whole effect list has to reach the driver without
widening the return of a public
StatifierPersistence.Runsfunction.What landed is a package-internal
step_reporter:option onRuns.create/4andRuns.step/5, documented in thet:opt/0typedocbeside
linkage:,invoke_id:andchild_count:as "this package'sown, never a host's".
persist_tail/7calls it with the list it washanded - before
Enum.split_with/2splits the lifecycle effects off -once
write_run/6has landed.Driverwrites the option only when acallback is actually in play, and writes it as a fun that sends the list
back to the driving process on this drive's own reference, the same
buffer shape
buffer/4already uses for a dispatched invocation'sanswer. The driver drains that message and fires the host's callback
itself, after the entry point has returned.
That is what makes clause 3 true rather than asserted: the callback for a
run never runs inside that run's own exclusion, and nothing a host wrote
runs inside a serialized section this package opened.
Two fire sites cover every door -
create/3forRuns.create/4, theprivate
step/5forRuns.step/5.send_event/4,done_invocation/5,failed_invocation/5,answer_parent/3andRuns.fail/4'sdriver:path all arrive at one of them, so the parent's step on the answer path
reports under the parent's run id with nothing plumbed for it.
Also implemented, as the amendment's closing section allows: a per-call
after_step:outranks the driver's own. It isKeyword.get(opts, :after_step, driver.after_step)rather than a literalKeyword.put_new/3into theRunsopts, because the option is thedriver's and is deleted before the entry point is called; the semantics
are the shape
invoke_types:andserialization:already have.Per the record and not changed here: a discarded delivery reports
nothing,
Runs.cascade_cancel/3reports nothing, the return value isignored, a raise propagates, and no public return is widened.
Drift between the bead and the merged record
The bead's paraphrase says the callback fires "outside the run lock".
The merged record's clause 3 is narrower: the guarantee is scoped to the
run being reported. Decision 3's child is created inside the parent's
step, so the child's callback fires while the parent's exclusion is held,
and the record says explicitly not to "fix" that. The code follows the
record; a test pins the ordering.
Provenance
test/statifier_persistence/driver_subchart_test.exsis outside thebead's file map. The parent-answer case needs that file's linked-child
fixtures (
subchart_dispatch/1,parent_resolver/1,Linkage.child_run_id/3), and rebuilding them indriver_test.exswould duplicate the harness. One test and one private helper were
added; nothing existing was changed.
lib/statifier_persistence/runs.exis in the map conditionally ("onlyif the seam needs it"). It did: the option, the
t:opt/0member andits typedoc, the reporter threaded through
stepped/7andpersist_tail/7, andreport_step/3.Verification
mix qualitygreen on this HEAD: 527 tests, 95.7% coverage,credo and dialyzer clean.
reverted, recorded in a comment above the test it discriminates.