Skip to content

An authored spawner never hears an unsolicited child exit: stopped is only ever the answer to its own stop effect #9227

Closed

Description

@cansirin

Summary

An authored program that spawns a child with spawn(program, {on}) never hears that the child ended on its own. The authoring layer produces a stopped event in exactly one place — stopHandler in apps/tuval/src/authoring/define-program.ts, as the answer to the program's own stop effect. A child that exits unsolicited publishes {kind: "stopped"} to the ProcessTable stream (apps/tuval/src/process/Processes.ts) and its live finalizer in apps/tuval/src/commands/core/process.ts deletes the entry; nothing subscribes to that and dispatches a stopped into the spawner's inbox.

What I was doing

Review of the cron program (#9225, PR #9226): the reviewer graded criterion 2's middle clause — "a child that ends before answering is recorded as a failed run" — and found the stopped cell unreachable under the live kernel. git grep of stopped( / "stopped" across apps/tuval/src at 0e6b5750 finds no producer other than stopHandler. Verdict: #9226 (comment).

What I observed

Why it matters

spawn with on is the authoring layer's whole story for "start a program and hear back". Without the end-of-life edge every authored parent has a silent hang mode for a crashed child, and the only recovery is a restart (which cron reconciles on resume; other programs will not).

Pointers

Suggested next step (non-binding)

Where on routing is applied at the child's emit, also apply it at the child's end: when a child spawned with a parent leaves the table on its own, deliver a stopped for that child into the parent's inbox, distinguishable from the answer to the parent's own stop. The existing stopped event shape already carries the process id.

Acceptance criteria

  • 1. A child spawned with a parent that leaves the process table on its own — ended from outside, or crashed — reaches its spawner as the authored stopped event for that child, delivered into the spawner's inbox (apps/tuval/src/process/inbox.ts).
  • 2. One child end is one stopped. A stop the parent issued itself does not produce a second event, and it is made unwritable rather than filtered: one of the two producers no longer exists, with no dedupe flag anywhere.
  • 3. The delivery cannot fail or wedge the child's finalizer, including when the spawner is itself already stopped or is the very fiber that closed the child.
  • 4. A restored child (apps/tuval/src/durability/restore.ts, which spawns with no on/parent wiring) is stated in a docblock as still uncovered; restore is not changed here.
  • 5. apps/tuval/src/cron/cron.ts — the stopped cell's docblock, the module header and the test name that today say the branch is unreachable "until An authored spawner never hears an unsolicited child exit: stopped is only ever the answer to its own stop effect #9227" are true again: they state the behaviour cron now has.
  • 6. A test drives the whole path through the real kernel — spawn through the compiled handlers, end the child from outside, assert the parent's stopped ran — not testProgram (pattern: the real-kernel spawn test A spawn on a program-valued arg looks up the arg's service key as a program id #8955 added in define-program.unit.test.ts). Its assertion fails if the delivery is removed.
  • 7. Gates from apps/tuval: pnpm typecheck, npx biome check src/commands src/process src/authoring src/cron, pnpm vitest run --project unit, pnpm vitest run --project integration, plus a clean boot with features.cron on and no HandlerFailed.
  • authoring/effect.ts no longer exports a stopped(process) constructor with no caller; the finalizer literal in commands/core/process.ts is the event's only producer and the only place its shape is built.

Filed by an agent on behalf of cansirin · from the review of PR #9226 · 2026-09-15

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions