The park state

Version: 2

When a flow parks (on a timer, an await, a limit, or a callback's captured continuation), the engine writes everything a resume needs to rebuild the scopes the leaf was parked in as one JSON value, ParkedScopeState. A host stores it, through park.sink or park.callbacks.mint (undefined), and hands it back byte for byte on resume. A host never reads inside it. The contract an embedder serves is the persistence park.sink describes; this document is the value it persists, which engines read.

Versions #

The shape #

The shapes are TypeScript-ish: x?: T may be absent. The two versions differ only in how they carry code (ParkedCode in park_state.ts: CodeAsTree for 1, CodeByPlace for 2) and in version 2's libraries; every other member is the same.

interface ParkedScopeState {
  version: 1 | 2;
  address: string;                   // the parked statement's lexical address (`s0.b1.s0`): the resume's entry
  bindingName: string | null;        // what a step-past resume binds the answer to; null for a value-less statement
  scopeChain: SerializedScope[];     // root first: the bindings feeding the parked statement (the innermost body's, when `frames` is present)
  presenceBind?: boolean;            // added: `expired = await sleep(…)` binds `true` at wake
  until?: boolean;                   // added: an `await until(…)` timer park, resumed by re-entering
  frames?: SerializedFrame[];        // added: the bodies the address passes into, outermost first
  suspended?: true;                  // added: an engine-initiated suspension, re-run on resume
  journal?: SerializedCallEvent[];   // added: the parked statement's finished calls, replayed on re-run
  libraries?: ParkedLibrary[];       // version 2: every library the run linked, pinned
}

type ParkedLibrary =                 // version 2
  | { path: string; source: string } // its text, where the host keeps no source store
  | { path: string; hash: string };  // the hash the host's `park.sources` keeps its text by

interface SerializedScope {
  bindings: Record<string, BindingDescriptor>;
  id?: number;                       // added: its number, when a closure holds it by reference
  parent?: ScopeLink;                // added: with `id`, the scope it is nested in
}

type ScopeLink =                     // where a closure's scope is (body closures hold it by reference)
  | { kind: 'scope'; id: number; parent?: ScopeLink; bindings: Record<string, BindingDescriptor> }
  | { kind: 'scopeRef'; id: number }
  | { kind: 'file'; file?: string };

interface SerializedCallEvent {
  index: number;                     // the k-th call the statement made
  value: BindingDescriptor | null;   // what it handed back
  next: number;                      // the statement's call count after it
}

type SerializedFrame =
  | { kind: 'call'; index: number; next: number; callee: string; journal: SerializedCallEvent[]; scopeChain: SerializedScope[] }
  | { kind: 'member'; index: number; next: number; member: number; op: CollectionOpExpression | { span: Span }; journal: SerializedCallEvent[]; scopeChain: SerializedScope[] }
  | { kind: 'arm'; arm: number; body: ClosureExpression | CodePlace };

// Version 1 carries code as its syntax tree (a closure's `closure`, a lazy walk's
// `head` and `mapping`, a `localShape`'s `declaration`, a frame's `op` and `body`);
// version 2 as its place in the pinned program, an op as its span.
type CodePlace = string;             // an AST path: `/statements/2/body/0/value/closure`, `@lib~1deals/statements/0`

BindingDescriptor is a tagged union, one variant per kind of value a scope can hold: event, instance, handle, extractRoot, extractPosition, sourcePosition, resource, blockMeta, positions, tuple, closure, value, recordValue, callback, shape, localShape, movement, fileClosure, shapePosition, nodePosition, nodeRef, handleRef, lazyWalk and opaque. Each variant's members are in park_state.ts. Three rules govern them:

Members added to a descriptor after it was first written are optional in the same way as the five above (fieldOrder, a node's id, a landed edge's landing, a node's landedOn: the edge of a run-built node it landed on, which lets write e { … } update it in place after the resume; fileFields on a node, a shape position and an extraction's emission). Spellings a park written earlier still carries are read too (a file source's ownerAdapterType, a landing's landingShape, a resource's unbranded fileRef). A descriptor's __brand: "FileRef" is written as its spelling, and no reader branches on it.

Park reasons #

Beside the state, a host records why a leaf waits. The core owns three reasons: timer, await and limit (CORE_PARK_REASONS). Any other reason is a unit's own, carried as an opaque string.

Changes #