plans/codex-correct-state-machines.md
Status: Proposed
Date: 2026-07-28
Evidence window: Merges after 1014afff63622100bbf2e8a76fb536afc8cb9c88
through 9d4b4d2 on main
Scope: State-machine, distributed-machine, ownership, effect, persistence,
renderer, and conformance-test frameworks
The recent migration did not primarily expose bad pure transition functions. It exposed many implicit state machines around otherwise explicit reducers:
Review has been acting as a high-quality but expensive concurrency explorer.
After the architecture ADR, 21 merged PRs accumulated 256 top-level inline
review findings. The five main production migrations account for 192 of those
findings and 95 commits. rules/state-machines.md grew from 277 to 499 lines,
but the APIs still allow authors to assemble the newly documented invalid
states.
The highest-leverage response is not XState, a new statechart DSL, or more
prose. It is an executable machine contract plus a small ownership runtime and
mandatory adversarial conformance, built on the existing dispatcher,
ActorHost, fake transport, reachability explorer, and runCosim.
The proposed author-facing model is:
MachineSpec using a recipe appropriate to an entity actor,
collection actor, shared window resource, or durable workflow.OperationHandle, conformance suites, and a contract report.The framework should make its own invariants true by construction and deterministically explore declared domain semantics. It must not claim to prove arbitrary filesystem, Git, provider, compensation, or infinite-state behavior.
Build three reinforcing layers:
| Layer | Purpose | Primary mechanism |
|---|---|---|
| Executable contract | Make protocol and policy omissions visible at type-check or registration time | MachineSpec, discriminated recipes, branded identities, exhaustive intent/effect/outcome declarations |
| Ownership runtime | Make invalid lifetime, admission, retry, and settlement assembly unavailable through normal APIs | generation-safe leases, prepared admission, pinned receipt ledger, actor-owned operation scope, typed effect context |
| Generated conformance | Explore async interleavings TypeScript cannot prove | tiered scenario packs, historical mutants, fault injection, cosimulation, leak checks, minimized traces |
Retain the current pure TypeScript reducer model. Transition syntax was not the dominant source of review churn. An optional exhaustive transition-table helper can follow if reducer omissions remain measurable after the protocol and lifetime work.
The analysis used:
rules/state-machines.md;src/state_machines/,
src/distributed_machines/, all six distributed definitions, domain remote
managers, presentation services, and deletion/reset fences;Counts are a snapshot from 2026-07-28. GitHub review can continue after merge; for example, the newest version-preview recovery finding arrived after PR #4123 had merged.
“Findings” below means top-level GitHub review threads, excluding replies.
| PR | Focus | Commits | Findings | Main signal |
|---|---|---|---|---|
| #4097 | Golden single-window behavior | 3 | 0 | Established migration parity |
| #4098 | Explicit preview runtime owners | 3 | 1 | Ownership began moving to main |
| #4099 | App-run codecs and safe projection | 6 | 4 | Independently valid key/event schemas were not jointly valid |
| #4101 | Transport-neutral app runtime | 4 | 4 | Compatibility and delivery boundaries |
| #4103 | C2 registry documentation | 2 | 0 | Documentation only |
| #4102 | Multi-window infrastructure and harness | 6 | 5 | Window lifetime and shared ownership |
| #4100 | Local distributed actor kernel | 14 | 14 | Construction/disposal/reentrancy invariants |
| #4104 | Audit delivery rewiring | 11 | 6 | Projection and delivery compatibility |
| #4105 | Remote transport | 9 | 10 | Authorization TOCTOU, dedupe, and subscription admission |
| #4106 | Remote client and React binding | 3 | 2 | Bootstrap generation and ref-count leaks |
| #4108 | App-run authority migration | 14 | 31 | Request/runtime identity and waiter settlement |
| #4109 | Delete legacy app-run path | 2 | 2 | Cutover safety |
| #4116 | GitHub operations migration | 5 | 11 | Routing ownership and terminal cleanup |
| #4117 | Delete legacy GitHub path | 1 | 0 | Cutover safety |
| #4122 | Main registry hardening | 2 | 3 | Unresolved-entry retention reappeared outside actors |
| #4121 | Image-generation migration | 10 | 19 | Effect settlement, deletion, retention, presentation |
| #4124 | Release workflow hardening | 1 | 4 | Unrelated to the state-machine program |
| #4126 | Stop stream versus queued prompts | 3 | 3 | Cross-owner cancellation policy |
| #4119 | Chat/plan authority migration | 50 | 96 | Queue ownership, finalization, tombstones, deletion |
| #4120 | Delete legacy chat adapters | 4 | 6 | Compatibility and cleanup |
| #4123 | Version-preview migration | 16 | 35 | Persistence, Git recovery, window interest, presentation |
| Total | 169 | 256 |
Additional signals:
rules/state-machines.md grew from 277 to 499 lines and from 67 to 114 rule
bullets: 47 new rules capturing review-discovered edge cases.runCosim
has no domain-machine users.Classifying the 46 foundation findings by primary cause gives:
| Primary cause | Finding instances |
|---|---|
| Actor/subscription lifetime, admission, and disposal barriers | 17 |
| Identity, correlation, authorization, and deduplication | 9 |
| Bounded retention, backpressure, and resource ownership | 8 |
| Cross-window projection and delivery compatibility | 5 |
| Error isolation and classification | 4 |
| Abort and terminal-settlement semantics | 3 |
These are finding instances rather than deduplicated bugs. Repetition is intentional because review iteration is the churn this plan aims to reduce.
The current reducer kernel already has several good foundations:
TransitionResult values;ActorHost and remote transport/client layers;TaskScope, TimerLease, InvocationRef, and invocation registries;runCosim; andThe problem is that correctness-critical protocol state remains distributed across callbacks, maps, booleans, service objects, and renderer hooks:
flowchart LR
UI[Renderer action] --> WI[Wire intent]
WI --> AA[Async authorization]
AA --> AH[ActorHost admission]
AH --> TR[Pure transition]
TR --> EF[Command/effect runner]
EF --> ST[Terminal settlement]
ST --> PR[Presentation routing]
TR --> PS[Persistence/checkpoint]
AA -. hand-built lifetime .-> AH
EF -. hand-built correlation .-> ST
ST -. hand-built waiter/replay .-> UI
PR -. hand-built window ownership .-> UI
PS -. hand-built crash recovery .-> EF
The reducer is explicit; the dotted relationships are usually not. Each dotted relationship is another state machine, but today it is assembled ad hoc.
| Failure mode | Review evidence | Why the current API permits it | Required construction guarantee |
|---|---|---|---|
| Construction/disposal overlap | same key reserved through disposal, dispose barrier published too late | Parallel maps encode an implicit combined lifetime; async factories register too late | One explicit lifetime slot and a synchronously published, generation-bound lease |
| Ingress bypasses deletion/reset | host ingress bypass, machine disposal admission | Local refs, command output, and remote dispatch have different gates | One host-level admission transaction for every external ingress path |
| Authorization TOCTOU | disconnect during authorization, stale before execution | Authorization returns a boolean, then identity/lifetime changes before admission | Authorization mints an actor/window/revision-bound lease consumed by synchronous admission |
| Unsettled dedupe eviction | unresolved entry eviction | One cache combines in-flight capacity and settled retention | Pending receipt entries are pinned; in-flight and settled bounds are separate |
| Subscription ownership drift | subscribe after unsubscribe, subscription admission bypass | Retain, refresh, bootstrap, and release are separately callable | A generation-bound subscription lease; refresh never acquires ownership |
| Request versus invocation aliasing | request/runtime waiter mismatch, concurrent ensure alias | One string is reused for transport, request, runtime, and replay roles | Branded identities and an actor-owned operation ledger with distinct request and invocation fields |
| Lost or ambiguous completion | runtime failure reported as success, disposal strands waiters | Raw dispatch reports admission only; command runners can omit terminal output; waiter maps are bespoke | Typed admission/completion APIs, exhaustive effect outcomes, exactly-once in-process settlement |
| Stale queue mutation | queue edit races admission, stale queue mutation | Revision checks and side effects are split across owners | Actor-owned claim/CAS before side effects, explicit owner capability, typed rollback/settlement |
| Late producer recreates deleted actor | actor creation races deletion, tombstone cleared early | Effect output uses creating ensure() paths and deletion fences do not own continuations | Non-creating captured actor sink plus a fence that drains construction and command continuations |
| Presentation misroutes or evicts live work | duplicate operation can hijack result routing, final window release races actor creation | Three services implement different initiator maps and capacity policies | One first-writer, generation-bound route registry with unresolved claims pinned |
| UI acts before authoritative admission | pane hidden before cleanup accepted | Raw receipt promises are discarded or transport “applied” is treated as domain completion | Generated operation facade exposes distinct admission and completion milestones |
| Persistence closes over partial external work | startup reconcile overwrites active operation, checkpoint switch ordering, post-merge interrupted restore | Persistence is declarative only; effects can start before an exact recovery checkpoint is flushed | Checkpoint-before-effect runtime, exact progress schema, hydration barrier, domain reconciliation |
as z.ZodType<...Event>. The client definition then
duplicates codecs. Producer-only events and host-enriched metadata are not
structurally excluded from the renderer boundary.RemoteMachineContract accepts independently decoded key and event values
plus an arbitrary async authorizeDispatch callback. The callback can mutate
parsed data, and the key/event/sender/hash relationship is not one typed
admission operation.ActorLifecyclePolicy is a bag of loosely related booleans and optional
hooks. Invalid or underspecified policy combinations are representable.ActorHost tracks construction, activation, machine construction, machine
disposal, host disposal, and cleanup in parallel maps. The combined
lifecycle is implicit.createCommandRunner is an arbitrary function. No type requires each
command to classify itself as correlated, streaming, checkpointed, or
intentionally fire-and-forget, or to produce success/failure/cancellation
output.RemoteActorRef.dispatch() returns a raw transport receipt. Callers can
discard rejection, confuse admission with completion, or change UI state
before authoritative acceptance.MachinePersistencePolicy declares intent but does not enforce a durable
flush before an external mutation or a restart reconciliation barrier.“Correct by construction” must have a precise meaning.
await.These guarantees hold within the declared host/session lifetime. An in-memory receipt ledger does not provide exactly-once behavior across process crashes.
The framework requires these policies to be declared and testable; it does not invent them.
ensure() a successor into existence.MachineSpecAdd a thin, runtime-available contract builder. “Builder” is preferred over “compiler” in APIs because it enforces declarations and generates adapters and tests; it does not prove arbitrary TypeScript.
The exact API should be prototyped, but its conceptual shape is:
const spec = defineMachineSpec({
state: {
initialState,
transition,
keyOf,
variants,
invariants,
},
lifecycle: entityActor({
terminal: isTerminal,
admissionDuringDisposal: cleanupIntents,
}),
remote: remoteProtocol({
keyCodec,
snapshotCodec,
intents: {
START: remoteIntent({
codec: startIntentCodec,
completion: "await-settlement",
retry: stableIdRetry({ maxAttempts: 2 }),
authorization: "entity-member",
}),
STOP: remoteIntent({
codec: stopIntentCodec,
completion: "admission-only",
retry: "none",
authorization: "entity-member",
}),
},
admit: admitRemoteIntent,
}),
effects: defineEffects({
StartRuntime: correlatedEffect({
/* ... */
}),
PublishStatus: fireAndForgetEffect({
/* ... */
}),
}),
outcomes: defineOutcomes({
requestId: (request) => request.requestId,
retention: boundedOutcomes({ maxSettled: 64 }),
}),
testing: {
fixtures,
capabilities,
exclusions,
domainScenarios,
},
});
The builder must:
satisfies Record<Variant, ...>-style maps to keep variant inventories
exhaustive without deeply recursive type machinery;Replace independent lifecycle booleans with discriminated recipes:
ephemeralEntityActorretainedEntityActorsingletonCollectionActorwindowInterestResourcepersistentEntityActorSelecting a recipe makes its required policy explicit. For example, a persistent recipe must supply schema version, load, save, hydration behavior, and reconciliation. A bounded terminal actor must supply a terminal classifier and retention policy. Unsupported combinations fail at definition registration.
Recipes compose lower-level primitives but ordinary domain authors do not manually wire those primitives.
Introduce or standardize opaque types for:
TransportMessageIdIdempotencyKeyRequestIdInvocationRefActorInstanceIdActorRevisionQueueRevisionWindowSessionIdPresentationEpochNo generic operationId: string should stand in for multiple roles. Named
conversion functions are allowed where a domain intentionally derives one role
from another; an assignment or cast is not.
Traces and contract reports display all identity roles in aligned columns so a reviewer can see accidental aliasing.
Implement one opaque LifetimeLease substrate. Build admission,
subscription, presentation-owner, and deletion/reset behavior on it.
Required properties:
await continuations;Replace ActorHost’s parallel construction/activation/disposal maps with one
explicit keyed slot:
type LifetimeSlot<Actor> =
| { kind: "constructing"; generation: number; lease: LifetimeLease }
| { kind: "active"; generation: number; actor: Actor }
| { kind: "disposing"; generation: number; barrier: Promise<void> };
Machine and host lifetime should use the same explicit vocabulary. This removes combined states such as “construction map cleared but disposal cleanup still running” from the representable implementation.
An internal KeyedAdmissionScope should:
ActorHost;await; andThis replaces domain deletion/reset fence maps. A domain can still decide whether a failed delete reopens admission, but it receives a generation-bound reopen capability rather than toggling a boolean.
Authorization must not merely return true. It should produce an opaque
prepared admission bound to:
Final synchronous admission consumes that capability. If any bound fact has changed, admission returns a typed stale/closed result. This closes the authorization-to-dispatch gap for both existing and newly constructed actors.
Evolve the distributed definition from one Event generic to
RemoteIntent plus trusted Event:
defineRemoteProtocol<ActorKey, RemoteIntent, Event, Snapshot>({
intentCodec,
snapshotCodec,
admit({ key, intent, sender, prepared }): Event {
// Creates a new immutable host event.
},
});
Requirements:
The migration should use a compatibility adapter for existing definitions. An inventory test forbids new schema-widening casts immediately and removes the adapter after all six definitions migrate.
Extract a reusable PendingReceiptLedger from the hardened remote transport
behavior and adopt it in remote transport and MCP OAuth first.
Its claim result is one of:
admittedduplicatepayload-conflictcapacity-rejectedRequired behavior:
On the renderer, prepare(intent) freezes the message ID, idempotency key, and
fingerprint before the first dispatch. A retry calls the prepared object and
therefore cannot accidentally create a new delivery identity.
The memory ledger’s guarantee ends with its host/session. A durable receiver must select a durable ledger policy separately.
OperationScopeMove request settlement out of domain waiter maps into the actor host.
OperationScope owns:
RequestId;InvocationRef;Required behavior:
await;Local callers use a typed actor.request(...) facade when they need completion.
Low-level enqueue remains admission-only and is unavailable to ordinary domain
code after migration.
defineEffectsDo not begin with a broad workflow DSL. Begin with an exhaustive command handler map that removes raw host access and requires effect metadata:
correlatedstreamingcheckpointedfire-and-forgetA correlated effect must declare success, failure, and cancellation builders. A streaming effect must declare open/item/terminal/cancellation behavior. An effect marked fire-and-forget is intentionally outside operation settlement and must declare error isolation.
The generated effect context provides:
AbortSignal, TaskScope, and timer access;Synchronous throws and async rejections use the same failure mapper. Terminal events are built from the captured command and identity, never from mutable current state. Scheduling, concurrency limits, and supersession remain domain policy.
Generate an OperationHandle<Milestones, Outcome> and useMachineAction from
each intent declaration.
The common vocabulary is:
bootstrapping
→ dispatching
→ admitted | replayed
→ running
→ succeeded | failed | cancelled | superseded | disposed
This is a vocabulary, not a mandatory fixed progression. Each intent declares which milestones exist. In particular:
Default renderer interaction semantics:
finally path.Existing renderer façades and promise consumers stay compatible during the pilot. Adoption is incremental.
Use the lifetime substrate to generate:
refresh()/resync never increments ownership;WindowSessionId invalidates callbacks after reload/disconnect; andReplace the GitHub, image-generation, and version-preview initiator maps with a main-only route registry:
OperationScope;Version-preview window interest should use the same generation lease substrate but remain a narrow domain recipe until a second shared-resource consumer proves the general API.
Durability is a separate assurance tier and a later implementation phase. Machines may declare persistence policy early, but the framework must not imply crash safety until the executable runtime and restart tests exist.
For a flush-before-run effect:
The framework can guarantee “checkpoint flushed before effect starts.” It cannot guarantee that a partially applied Git/filesystem/provider effect is automatically reversible. Each persistent machine still declares exact progress fields, external probes, compensation, and recovery policy.
The post-merge #4123 interrupted-restore bug is both:
The version-preview checkpoint must include the pre-restore HEAD plus exact
completed/next Git step. Restart reconciliation must inspect the actual HEAD
before choosing closed versus recovery-required.
Pilot the runtime on version preview, then plan handoff. A versioned DB/file journal, if needed, is a separately reviewed data migration rather than an implicit part of the in-memory framework extraction.
Chat/plan exposed the largest and most domain-specific state space. Do not use it as the first API design exercise.
After the generic operation, lease, and durability layers are proven, introduce
a narrow TransactionalOwnedQueue if the remaining duplication warrants it.
It must:
Do not hide queue scheduling or replacement semantics behind universal defaults.
After the protocol work, evaluate a lightweight
defineTransitionTable/matchVariant helper using satisfies:
phase;ignoreBecause(reason) rather than a wildcard/default.This is optional for collection aggregates where a hand-written pure reducer is clearer. It should be adopted only if measurement shows reducer omissions remain a material source of defects.
Before each primitive lands, encode accepted review defects as a named mutation or adversarial scenario. At minimum include:
await;ensure();Every one of the 46 foundation findings must map to:
This traceability becomes part of the contract report.
Mandatory testing should be proportional rather than ritual:
| Tier | Applies to | Generated checks |
|---|---|---|
| T0: pure machine | Every machine spec | variant inventory, state/event matrix, invariants, reference stability, state/command reachability, explicit exclusions |
| T1: hosted/effectful | Hosted actors and controllers | construction/activation/disposal reentry, effect success/failure/cancel/late output, request settlement, zero owned resources |
| T2: distributed | Remote definitions | codec round trips, joint key/intent validation, producer-event rejection, size budgets, two windows, auth disconnects, subscribe/resync/unsubscribe, duplicate/conflict/lost receipt, recreation/reload |
| T3: persistent | Durable policies | hydration barrier, checkpoint write failure, crash before/after each checkpoint and effect step, external reconciliation, safe terminal or explicit recovery |
| T4: composition | Declared multi-machine protocols only | bounded cosimulation, ownership transfer, queue/follow-up/plan interactions, deletion against child/producer work |
Use existing:
FakeDuplexRemoteTransport;runCosim; andDo not add a property-testing production dependency initially. Deterministic finite exploration and fault injection are easier to reproduce and already fit the repository. A later property-based generator layer is acceptable if it adds coverage without obscuring the minimal failing schedule.
CI must distinguish:
exhaustive: true; andboundReached: true.A bound hit is not a passing proof. Split orthogonal alphabets into focused suites until required searches are exhaustive.
Every generated failure should emit a minimal text and JSON timeline containing:
Definitions supply redacted serializers. Production traces remain bounded and must not include raw prompts, tokens, provider payloads, filesystem contents, or other sensitive data.
The generated PR contract report should summarize:
A graphical inspector can follow. Text/JSON diagnostics belong in the first framework milestone.
Purpose: Stop designing against happy paths and address known unsafe recovery immediately.
Deliverables:
Acceptance:
MachineSpec, reports, and diagnosticsPurpose: Establish executable acceptance contracts before extracting broad runtime abstractions.
Deliverables:
MachineSpec with T0 inventories, invariants, policy declarations, and
explicit exclusions.Acceptance:
Purpose: Remove the most repeated lifecycle and transport failure classes.
Deliverables:
LifetimeLease.ActorHost parallel lifetime maps with explicit lifetime slots.PendingReceiptLedger.Acceptance:
Purpose: Make admission, execution, and terminal settlement unambiguous.
Deliverables:
RemoteIntent/trusted Event protocol separation plus a compatibility
adapter.OperationScope.defineEffects map and captured non-creating
effect context.OperationHandle and useMachineAction.Acceptance:
Purpose: Remove the second implicit state machine in per-window delivery.
Deliverables:
Acceptance:
Purpose: Turn declared persistence policy into executable ordering and recovery guarantees.
Deliverables:
Acceptance:
Purpose: Apply proven primitives to the largest domain-specific protocol.
Deliverables:
Acceptance:
Purpose: Make the safe path the only normal production path and verify that the investment reduced churn.
Deliverables:
dispatch/enqueue for one migration wave, then deny domain
production use with exact AST boundary tests.rules/state-machines.md with links to the
primitive or generated scenario; retain genuinely domain-policy rules.Acceptance:
flowchart TD
P0[Phase 0: regression + scenario corpus] --> P1[Phase 1: MachineSpec + diagnostics]
P1 --> P2[Phase 2: lifetime + admission + receipt]
P2 --> P3[Phase 3: operations/effects + app/image pilots]
P3 --> P4[Phase 4: presentation + smaller migrations]
P4 --> P5[Phase 5: durable checkpoint runtime]
P5 --> P6[Phase 6: chat/plan queue + cosim]
P6 --> P7[Phase 7: deny raw APIs + measure]
P0 --> VFIX[Immediate #4123 focused recovery fix]
VFIX --> P5
Conformance and historical mutants land with every phase rather than as a final test pass.
Keep framework extraction separate from broad domain migrations:
MachineSpec, contract report, and trace minimizer.LifetimeLease plus hostile-reentry tests.ActorHost lifetime slots and keyed admission.PendingReceiptLedger adoption in remote transport and OAuth.RemoteIntent split plus compatibility adapter.OperationScope and minimal defineEffects.Each PR should introduce either a framework primitive or one domain adoption, not both a new primitive and several unrelated migrations.
Expected new or expanded modules under src/state_machines/:
machine_spec.tslifetime_lease.tspending_receipt_ledger.tsoperation_scope.tseffects.tstransition_validation.tstrace.tstesting.tscosim.tsExpected changes under src/distributed_machines/:
definition.tsactor_host.tsremote_transport.tsremote_client.tsremote_protocol.tsreact.tsboundaries.test.tstesting/host_conformance.tssrc/app_run/definition.tssrc/app_run/remote_manager.tssrc/chat_stream/definition.tssrc/plan_handoff/definition.tssrc/ipc/services/github_ops_definition.tssrc/ipc/services/image_generation_definition.tssrc/ipc/services/version_preview_definition.tsmessageId can carry stable delivery identity when the client prepares it
once.RemoteIntent versus Event is an internal TypeScript API break behind a
compatibility adapter. Migrate one definition at a time.await admission revalidation, late creating ensure(),
unsettled pending eviction, and omitted terminal settlement.Use all three dimensions—fault coverage, glue reduction, and review-finding reduction—for a go/no-go decision. Passing only one is not enough.
| Risk | Mitigation |
|---|---|
| Framework overclaims proof | Publish the assurance boundary in code docs and contract reports; label bounded searches explicitly |
| Abstraction is shaped by happy paths | Land historical failing scenarios first and validate with two complementary pilots |
| Too many new scopes burden authors | Expose recipes plus one operation facade; keep lease primitives internal or behind named unsafe hooks |
Type complexity slows tsgo | Prefer flat discriminated unions and satisfies maps; prototype on two machines before backfill |
| State-space explosion | Split independent fault alphabets, minimize schedules, and require exhaustive status |
| Generic retries duplicate destructive work | Default to no retry; require per-intent stable-ID, bounded opt-in |
| In-memory exactly-once is mistaken for crash safety | State the host/session boundary in APIs and require a durable policy for restart guarantees |
| Outcome history leaks memory or data | Keep unresolved and settled capacities separate; make projection bounded, redacted, and opt-in |
| External effect cannot be made exactly once | Guarantee checkpoint ordering and explicit reconciliation, not provider/filesystem atomicity |
| Migration changes product behavior | Preserve wire/renderer façades, use golden tests, and migrate one domain per PR |
| Raw APIs bypass the framework | Add immediate inventory checks, then exact AST boundary denial after one compatibility wave |
| Production traces expose sensitive data | Require definition-owned redaction and strict bounded retention |
| Chat drives premature generalization | Migrate app run and image generation first; keep chat/queue last |
The plan resolves the current questions as follows:
| Question | Decision |
|---|---|
| Replace the current reducer model? | No. Extend the existing kernel at protocol/lifetime seams. |
| Require specs for every existing machine immediately? | Gate new definitions/intents and seam changes now; stage backfill. Pure reducer-only changes may use existing matrix tests temporarily. |
| First pilots? | Image generation and app run. They cover effects, operations, retention, deletion, and presentation without chat’s full complexity. |
| Validate window ownership? | Follow immediately with a narrow version-preview window-interest/presentation slice. |
| Include crash correctness in the first in-memory milestone? | No. Fix #4123 immediately, declare persistence policy early, and implement the generic durable runtime as its own version-preview pilot. |
| Break internal APIs? | Yes, behind adapters; keep IPC and renderer façades stable. |
| Raw dispatch/enqueue removal? | Forbid new use, deprecate for one migration wave, then deny in domain production code. |
| Add property-testing dependency? | Not initially. Use deterministic finite exploration and fault injection first. |
| Build a transition-table DSL? | Not in MVP. Reassess after protocol/lifetime metrics. |
| Add a durable DB/file journal? | Decide in Phase 5 as a separate approved data change. |
| Go/no-go metric? | Require stronger fault coverage, lower glue, and lower review churn; zero HIGH/P1 covered-category pilot findings is the release gate. |
Questions that remain intentionally domain-specific must be answered in each spec:
PM, engineering, and UX/DX independently agreed on the main conclusion:
The cross-role challenges changed the plan in concrete ways:
MachineSpec with an explicit
assurance boundary;This plan does not promise mathematically correct arbitrary applications. It does make the repeatedly failing framework invariants unrepresentable through normal APIs, makes every remaining policy explicit, and turns review-discovered interleavings into mandatory executable evidence.
That is the practical path from “state machines plus careful conventions” to “framework invariants by construction, domain semantics by exhaustive contract where finite.”
Generated by dyad:swarm-to-plan.