Runtime Composition Confluence Harness

August 14, 2026 ยท View on GitHub

AC-962 adds a TypeScript harness for answering a concrete runtime question: after a bounded dynamic history settles, is the observable runtime equivalent to a clean boot into the same final configuration? The harness covers the component graph and the transactional activation boundaries introduced by AC-959 through AC-961.

Observable snapshot

RuntimeCompositionInventory attaches host-observable resources to their owning RuntimeComponentScope. Disposal removes the registration even when the resource's inverse fails. captureRuntimeCompositionSnapshot combines that inventory with a component-graph snapshot and active artifact pointer. It reports:

  • active component and provider instance identities;
  • hooks, tools, command grants, subscriptions, timers, tasks, temporary resources, and other owned resources;
  • the active artifact pointer;
  • blocked capabilities and unresolved lifecycle errors; and
  • the number of live irreversible effects excluded from exact comparison.

Snapshots are sorted and contain no callback bodies, arguments, tool results, credentials, or thrown error text. Equivalence deliberately ignores graph revision and the transient transitioning flag because those record history, not the settled runtime. assertRuntimeCompositionQuiescent separately requires that transitions have ended and that no blocked capability or unresolved lifecycle error remains.

Workspace commands and tools registered with a composition inventory require an owning component scope. Their inventory entries disappear with that scope, and retained workspace or tool handles fail after disposal. ExtensionAPI hook registrations use the same ownership path.

Effect equivalence

Reversible effects compare by their stable resource id. Compensatable effects must declare observationalEquivalence; the harness compares that declared post-compensation identity instead of attempt-specific ids. This assumes the host-provided inverse is valid and has completed before quiescence.

Irreversible emissions are not claimed to be exactly equal across dynamic and clean histories. They are excluded from identity comparison and surfaced only as an exclusion count. Tests may assert their external history separately, but must not use this harness to claim that an already-observed emission was undone.

Fault and interleaving coverage

The focused suite injects failures at apply, validate, activate, drain, runtime cutover, durable-pointer cutover, and prior-runtime disposal. It also constructs interrupted journals at every durable precommit boundary and requires bounded recovery to the baseline.

DeterministicRuntimeTransitionScheduler cooperatively interleaves promise and async-generator tasks using a reproducible seed. It refuses to run beyond a caller-supplied step budget, so a non-quiescent history fails instead of hanging CI. Provider replacement races must converge to the latest requested configuration.

The ordinary property test uses seed 962, a bounded history length, and 24 runs by default. Run the longer, separately invokable sweep from ts/ with:

npm run test:runtime-confluence:sweep

Set AUTOCTX_CONFLUENCE_RUNS when invoking the focused Vitest files directly to choose a different positive run count.

Assumptions and exclusions

The guarantee applies to resources registered in the host-owned inventory and to a valid, acyclic component graph with exclusive providers. Untracked process-global effects, external systems without a declared observation, cross-process scheduling, distributed transactions, wall-clock ordering, and Python parity are outside this harness. Cross-process activation still needs a lease around the single-process transaction contract.