The host families

Version: 1

The movement engine runs a program. Everything outside the program goes through one interface the caller hands it, EngineHost (packages/movement-engine/services/movement_engine/host.ts): adapters, model calls, parking, cancellation, files, the acting principal, write locks, the trace, the clock and fresh ids. The engine imports none of these itself. This document is that interface and the four entry points into the engine. How lf-platform's hosts serve it, and the engine's own notes, are in lf-platform, docs/engine-host.md. The data shapes are in the engine-contracts package (packages/engine-contracts/).

This document is the host contract, frozen as JSON. Every host call is one request and one awaited answer, each plain JSON: no callback, live object, Date, stream or shared array crosses, and instants are ISO 8601 strings. An embedder in another language or process serves exactly these shapes.

Every call is made inside a segment. Each segment.open opens exactly one segment and answers its id; every other call names that id as segmentId. The host maps the segment to the run and the team it was opened for, so no request repeats the team. Every segment.open carries the version of this contract the engine speaks (hostFamiliesVersion), and a host refuses a version it does not serve (see Versions).

Two tests hold the contract to this document (lf-platform's, apps/api/src/services/movement_engine/__test__/seam_guard.unit.test.ts):

Versions #

Conventions #

The shapes are TypeScript-ish. These rules apply to every one of them.

Shared shapes #

type TeamId = string;            // the tenant: an opaque id the host mints (`TenantId` in `engine-contracts`)
type LanguageVersion = number;   // the movement's pin (1 = "Quiet Heron", …)

// Priced calls
interface Budget { remainingMicrodollars: number | null }   // null: no cap
interface CostLine { source: string; microdollars: number } // source is `kind:name`, e.g. `model:claude-sonnet-5`
type Priced<I> = I & { budget: Budget };
type Costed<O> = O & { cost?: CostLine[] };                  // absent: spent nothing
type BudgetRefusal = Costed<{ error: HostCallError & { kind: 'budget' } }>;
type PricedAnswer<O> = Costed<O> | BudgetRefusal;

// Failures as data
interface HostCallError {
  kind: 'call_ceiling' | 'adapter_error' | 'budget';
  message: string;
  name?: string;            // the failure's own class name on the host (`SlackApiError`); absent for a plain Error
  detail?: HostErrorDetail;
}
interface HostErrorDetail {
  status?: number;          // the HTTP status the remote answered
  code?: string;            // the remote's error code
}

// A file, as a descriptor: never the bytes
interface FileRef {
  __brand: 'FileRef';
  name?: string;
  contentType?: string;
  size?: number;
  source?:
    | { adapterType: string; credentialId?: string; constructionArgs?: Record<string, string>; handle: string }
    | { store: 'document'; objectUri: string }
    | { ownerAdapterType: string; handle: string };   // an earlier spelling, read from old parks only
  url?: string | null;
}

// The event a run fired on (translation_graph/triggers/types.ts)
interface TriggerEvent {
  pipelineInputId: string;
  adapterType: string;
  triggerType: 'snapshot' | 'poll' | 'changes-feed' | 'webhook' | 'mutation' | 'extraction';
  payload: unknown;
  objectType?: string;
  triggerEntryId?: string;
  changeType?: 'create' | 'update' | 'delete';
  actor?: { type: 'user' | 'api-token' | 'system'; id: string | null };   // who acted, as the producing system asserts it; the opaque principal reference comes later
  snapshotRunId?: string;
  snapshotComplete?: boolean;
  recordId?: string;
  externalRecordRef?: { adapterType: string; externalId: string; recordType?: string };  // the source system's id for the record
  rootRecordType?: string;
  changedFields?: string[];
  idempotencyKey?: string;
  occurredAt?: string;
}

// A parked flow's whole state (`ParkedScopeState` in `engine-contracts`), opaque to the host
interface ParkedScopeState { version: 1; address: string; bindingName: string | null; scopeChain: unknown[]; /* … */ }

// Who an event acts as: an opaque reference the host mints
interface PrincipalReference { id: string; display?: string }

ParkedScopeState (undefined) and MovementTraceEntry (movement_engine/expression.ts) are engine-defined JSON. A host stores them and hands them back byte for byte; it never reads inside them.

What a run is given #

Entry points #

A run is entered one segment at a time. Each entry is one call into the engine with one JSON input, the host beside it; the engine opens one segment for it and closes it as it returns. Four engine calls serve every way in: runMovement, resumeMovement, settleRaceFrame and fireCallbackBody (movement_engine/run.ts). What follows is each input, and which a run host passes in each situation. (Production's run host is translation_graph/movement/execute.ts and the resume drivers beside it.)

The input every entry takes #

interface RunInput {
  source: string;                         // the pinned version's source, byte for byte (a resume re-parses it, so every address is the same)
  languageVersion?: LanguageVersion;      // the pin the run started under; absent: the current version
  movementName?: string;                  // which movement in the file; absent: the file's only one
  event: TriggerEvent;                    // the event the run fired on (a resume passes the run's original)
  teamId: TeamId;                         // the team the segment is opened for
  runId?: string;                         // the run record; absent for a test or a dry run
  firedAt?: string;                       // ISO 8601: when the run first fired; every clock read answers from it
  costCapMicrodollars?: number;           // the cap, read by the run host as each segment starts; absent: no cap
  priorSpentMicrodollars?: number;        // what the run spent in its earlier segments; absent: 0
  capBaselineMicrodollars?: number;       // what the run had spent when last resumed from a limit pause; absent: 0
  limitPaused?: boolean;                  // the run is paused at a limit (see "Operator limit resume")
  catalog: CatalogSnapshot;
  credentialIds?: Record<string, string>; // import name to credential id
  dryRun?: boolean;                       // rehearse every target
  suspendAt?: string[];                   // addresses to suspend at, once each
}

First firing #

runMovement(input: RunInput)

Resume #

resumeMovement(input: RunInput & {
  state: ParkedScopeState;        // the parked leaf's state, as park.sink stored it
  answer?: unknown;               // what to bind on a step-past resume (an ask's answer)
  reenter?: boolean;              // absent: false
  deferRaceSettlement?: boolean;  // batch mode; absent: false
  parkedOnEngine?: 'ts' | 'rust'; // the engine that parked it; absent: unknown
})
Parked onreenteranswer
an ask (answered)falsethe validated answer
a timer sleepfalseabsent
a timer await until(…)trueabsent
an await (woken)trueabsent
a limit suspension (operator resume)trueabsent

Race settlement #

settleRaceFrame(input: RunInput & {
  state: ParkedScopeState;  // any completed branch's state from the pass
  frameAddress: string;     // one of the pass's deferredRaceFrames
})

A callback body #

fireCallbackBody(input: RunInput & {
  state: ParkedScopeState;          // the callback's captured continuation, as park.callbacks.mint was given it
  values: Record<string, unknown>;  // the call's values, validated against the callback's parameters
  callIndex: number;                // the call's index on the callback's ledger
})

Operator limit resume #

Join closing #

The decision that a join has closed is split between the engine and the run host.

What an entry answers #

Every entry answers the segment's result (MovementRunResult, movement_engine/run.ts), or throws carrying the same result as far as it got.

The families #

FamilyMethodsOptional for an embedder
segmentopen, closeno
adaptersresolve, callno
llm.clientcallno (may throw, if the embedder's programs never call a model)
llm.extractCallcallsame as llm.client
llm.transformsinvokesame as llm.client
llmarbitrateEntitiessame as llm.client
park.sinkrecordJoin, commitTimerPark, commitAwaitPark, decrementJoin, persistBranchExport, collectBranchExports, cancelSubtrees, commitSuspension, commitLimitPauseyes: absent, a run that reaches a park fails (a dry run)
parkwakeno (may throw, if park.sink is absent: only an await park asks for a wake)
park.callbacksmint, callsyes: absent, callback(…) fails as unsupported
park.sourcessave, loadyes: absent, a park carries each library's text itself
park.bindingsrecord, findBoundCounterpart, deleteno (may throw, if no program binds)
control.cancelcancelled, cancelledNowno (answer false for a run nothing cancels)
filesresolveFileText, renderno
lookupresolveActingUserno (answer null when no user is known)
locksacquire, releaseno
traceappendyes: absent, the trace is on the run's result only
clocknowno
idsmintno

"No (may throw…)" means the family must be present, and a method of it may throw naming itself: the engine fails only a run that reaches it.

segment #

A segment is one entry into the interpreter: a fresh run, a resume, a race settlement, a callback body (see "Entry points"). The engine opens one at each entry point and closes it however the segment ends (completes, parks, throws). The host's half of a segment holds the run and team it is for, what the segment's adapter calls have read (a read memo, so a record read three times is one request), how many third-party calls they have made (an adapter call ceiling supplied by the host), the pin they answer under, and the adapter instances and lock session it holds.

segment.open #

request: { runId?: string; teamId: TeamId; languageVersion: LanguageVersion; hostFamiliesVersion: number }
answer:  { segmentId: string }

segment.close #

request: { segmentId: string }
answer:  { cost?: CostLine[] }

adapters #

The engine never holds a live adapter. It resolves an instance to data and an opaque id, then calls it by method name with one JSON argument.

adapters.resolve #

request: {
  segmentId: string;
  adapterType?: unknown;                       // a JSON value the host keys its registry by; normally text (absent: a value naming none)
  credentialsId?: string;
  constructionArgs?: Record<string, string>;   // non-credential construction args, unquoted (Sheets' `spreadsheet:`)
  role: 'source' | 'target';
  dryRun?: boolean;                            // rehearse writes: capture, never make
}
answer: {
  instance: string;                            // opaque, meaningful only within `segmentId`
  adapterType: string;
  supportedTriggers: TriggerEvent['triggerType'][];
  capabilities: {
    traversal: { incoming: boolean; edgeProperties: boolean };
    resources: boolean;
  };
  methods: Array<AdapterMethod | 'iterateRelated'>;   // what the instance implements
  walksContainers?: boolean;
  webhookEventTypeId?: string;
  metaRecordType?: string;
}

adapters.call #

type AdapterMethod =
  | 'listEntryPoints' | 'describe' | 'edgesFrom' | 'resolveEntity' | 'getFieldValue' | 'getRelated'
  | 'iterateRelatedPage' | 'preprocessInbound' | 'listEventTypes' | 'createRecord' | 'updateRecord'
  | 'deleteRecord' | 'linkRecords' | 'unlinkRecords' | 'ensureEventSubscription' | 'removeEventSubscription'
  | 'resolveAwait' | 'wakeKeys' | 'readRecord' | 'translateFilter' | 'getDedupRules'
  | 'getActorCandidates' | 'extractActor' | 'describeOpaqueId' | 'invokeFieldFunction';

request: { segmentId: string; instance: string; method: AdapterMethod; args: object }
answer:
  | { result: unknown; cost?: CostLine[] }
  | { error: HostCallError & { kind: 'call_ceiling' | 'adapter_error' }; cost?: CostLine[] }

llm.client #

The extraction keyword's and AI()'s model call.

llm.client.call #

request: Priced<{
  segmentId: string;
  system: string;
  userMessage: string;
  label: string;          // the call's purpose, for logs and billing
  model: string;          // a tier alias or a logical model name
  effort?: string;        // one of movement-lang's AI_EFFORTS; absent: the model's default
  maxTokens?: number;
}>
answer: PricedAnswer<{
  parsedJson: unknown;    // the JSON body the engine's prompt asked for
  rawText?: string;       // the reply before repair; kept only on an anomaly
  effortSteppedDown?: { from: string; to: string };
  continuations?: number;
}>

llm.extractCall #

The extraction call's model call (extract(content, Shape)), which sends its prompt as blocks with cache breakpoints.

llm.extractCall.call #

request: Priced<{
  segmentId: string;
  system: string;
  blocks: Array<{ text: string; cacheBreakpoint?: true }>;
  label: string;
  model: string;
  effort?: string;
  maxTokens?: number;
}>
answer: PricedAnswer<{
  parsedJson: unknown;
  rawText?: string;
  effortSteppedDown?: { from: string; to: string };
  continuations?: number;
  usage?: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number };
}>

llm.transforms #

through stages and plugin calls. What the engine knows of a plugin comes from the snapshot; invoking it is the host's.

llm.transforms.invoke #

request: Priced<{
  segmentId: string;
  plugin: string;                              // as registered (dashes allowed)
  config: Record<string, unknown>;             // the author's named arguments, resolved
  extractedContext: Record<string, unknown>;   // the entity's extracted fields so far
}>
answer: PricedAnswer<{
  text?: string;
  data?: Record<string, unknown>;
  records?: Array<Record<string, unknown>>;    // each thing fetched; a stored document is a FileRef under `file`
  outcome?: string;                            // what became of the invocation, for the trace only
}>

llm #

llm.arbitrateEntities #

The judge for identity matches the engine's own rules cannot settle (unique by with candidates the exactness rules leave ambiguous, or a lone fuzzy hit).

request: Priced<{
  segmentId: string;
  asserted: Record<string, unknown>;     // the write's identity fields
  candidates: Array<{ adapterType: string; externalId: string; recordType?: string; url?: string; data: Record<string, unknown> }>;
  recordType: string;
  constraints: unknown;                  // the held uniqueness constraints (translation_graph/uniqueness)
  languageVersion: LanguageVersion;
}>
answer: PricedAnswer<{
  index: number | null;                  // the candidate the write IS, or null to create
  judgeUnavailable?: string;             // the judge's failure message, when it could not be asked
}>

park.sink #

Where a parked flow's state goes, to be persisted and woken later. Every method here is the durable substrate's; an embedder without one leaves the whole family out, and a run that reaches a park fails ("needs the durable trigger-run substrate").

address is a flow's lexical address (s0.b1.s0); a frame's address is a prefix of its children's. state is a ParkedScopeState. Every request names its segment; the park belongs to the segment's run.

park.sink.recordJoin #

request: { segmentId: string; frameAddress: string; parkedChildren: number }
answer:  void

park.sink.commitTimerPark #

request: { segmentId: string; address: string; state: ParkedScopeState; wakeAt: string }   // wakeAt: ISO 8601
answer:  void

park.sink.commitAwaitPark #

request: { segmentId: string; address: string; state: ParkedScopeState; adapterType: string; correlationKey: string }
answer:  void

park.sink.decrementJoin #

request: { segmentId: string; frameAddress: string; branchAddress: string; leafAddress: string }
answer:  { closed: boolean }

park.sink.persistBranchExport #

request: { segmentId: string; frameAddress: string; branchAddress: string; branchIndex: number; exports: unknown }
answer:  void

park.sink.collectBranchExports #

request: { segmentId: string; frameAddress: string }
answer:  Array<{ branchAddress: string; branchIndex: number; exports: unknown }>   // ordered by branchIndex

park.sink.cancelSubtrees #

request: { segmentId: string; subtreeAddresses: string[]; excludeLeaf?: string }
answer:  void

park.sink.commitSuspension #

request: { segmentId: string; address: string; state: ParkedScopeState }
answer:  void

park.sink.commitLimitPause #

request: { segmentId: string; limit: 'cost'; capMicrodollars: number; spentMicrodollars: number }
answer:  void

park #

park.wake #

request: { segmentId: string; adapterType: string; correlationKey: string }
answer:  void

park.callbacks #

Minting callback(…) and reading its call ledger. Separate from park.sink on purpose: a park is a leaf the run is waiting on, a callback is not one. An await cb-[:Called]-> parks through park.sink.commitAwaitPark like any await.

park.callbacks.mint #

request: {
  segmentId: string;
  address: string;
  params: Array<{ name: string; type: 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'json' | 'file' }>;
  state: ParkedScopeState;      // the captured continuation
  singleUse: boolean;
  expiresAt?: string;           // ISO 8601; absent: never
}
answer: { id: string; url: string }

park.callbacks.calls #

request: { segmentId: string; callbackId: string }
answer:  Array<{ at: string; values: Record<string, unknown> }>   // at: ISO 8601, the landing's `At`

park.sources #

The library texts a park pins (park-state.md, version 2's libraries), each kept once by its content. A park names a library by the hash save answers; a resume reads the text back by that hash, whatever the team has saved since. The text is opaque to the host. Only the Rust engine asks (park state version 2).

park.sources.save #

request: { segmentId: string; source: string }
answer:  { hash: string }

park.sources.load #

request: { segmentId: string; hash: string }
answer:  { source: string } | {}   // {}: no text under that hash

park.bindings #

The symmetric correspondence store a write … bind other { … } keeps: "this record IS the counterpart of that one", in the segment's team. The engine skips every binding write on a dry run.

interface BindingEndpoint {
  adapterType: string;                          // any casing; the store normalises it
  credentialId?: string;                        // absent for construction-free instances and the KG
  constructionConfig?: Record<string, unknown>; // the instance's construction args, raw
  typeId: string;                               // the stable internal type id
  recordId: string;                             // the stable external record id
}
interface EncodedEndpoint { adapterType: string; instanceKey: string; typeId: string; recordId: string }

park.bindings.record #

request: { segmentId: string; from: BindingEndpoint; to: BindingEndpoint }
answer:  void

park.bindings.findBoundCounterpart #

request: { segmentId: string; endpoint: BindingEndpoint; counterpart?: Omit<BindingEndpoint, 'recordId'> }
answer:  EncodedEndpoint | null

park.bindings.delete #

request: { segmentId: string; from: BindingEndpoint | EncodedEndpoint; to: BindingEndpoint | EncodedEndpoint }
answer:  void

control.cancel #

The cooperative cancel gate. A user's cancel stops the run at the next statement boundary, and at the boundaries inside a statement too (every model call and plugin invocation of an extraction), never mid-write.

control.cancel.cancelled #

request: { segmentId: string }
answer:  boolean

control.cancel.cancelledNow #

request: { segmentId: string }
answer:  boolean

files #

A file crosses as its descriptor (FileRef), never its bytes, and the descriptor stays on the host. The engine holds an opaque handle with the file's name, contentType and size: it hands a file to files.resolveFileText and gets text back, gets one back from files.render, and passes files through write fields, and the host is handed the descriptor for each. Every byte read is on the host, through a document the run's tenant owns or an adapter instance the tenant holds; any other is refused by name (FileOutsideTeamError). A parked run holds its files where the park format says one is (park-state), and the host makes each a file again on resume.

A descriptor is only ever data; the envelope that carries it is what makes it a file, and the engine marks the file it makes there (fileValue, engine-contracts). The envelopes: an adapter's getFieldValue answer at a field the catalog snapshot types file (or a list, dict, tuple or maybe-absent of them); a resource's fileRef slot; a field a plugin's declared output types file; files.render's answer; a parked file (park-state). The same descriptor anywhere else — a webhook body's field, an answer at a json field, a knowledge-graph property — is data, whatever keys it carries, __brand: "FileRef" included. __brand stays the descriptor's spelling, read by nobody. The handle is opaque to the engine: the Rust engine holds a file as an opaque handle — on the eval wire ["F", id, { handle, name, contentType, size }] from the shim, ["F", id] back — while the shim keeps the descriptor in the segment's file table. The engine compares and carries handle (a FILE resource's externalId, a file provenance origin, the dedupe key) and never parses or reads inside it; a file's source — its credential, construction args, store, URL — never reaches it. The shim makes every file the envelope declares: an answer at a field whose declared type the engine names on the read (filesAt), each resource's fileRef slot in a getRelated answer over the reserved _resources reference the engine names so (filesIn: "resources", withResourceFile; any other filesIn is refused), a plugin output's declared files, files.render's answer, and a resumed park's files. The TypeScript engine, being retired, keeps the descriptor on its file value; the two behave alike. On both engines a file reads and prints as its opaque view — name, contentType, size and handle — and nothing else: a read of source, url or any other descriptor member is absent, and a file written into text (JSON, a prompt, a trace's sample, TEXT.PAIRS) is that view. The host is handed the plain descriptor for files.resolveFileText. A URL handle's owner fetches only a URL its configuration says it issued (email: its provider's storage — Mailgun's attachment URL, Resend's handle; WhatsApp: the configured Twilio account's media, or a URL this deployment exposed for the Meta door's media); any other is refused by name, and WhatsApp with no TWILIO_ACCOUNT_SID issues no Twilio media.

files.resolveFileText #

request: Priced<{ segmentId: string; file: FileRef }>
answer:  PricedAnswer<
  | { text: string; rawTextId?: string }
  | { unreadable: 'unsupported_type' | 'bytes_unavailable' | 'extraction_failed' | 'no_text'; detail?: string }
>

files.render #

request: { segmentId: string; content: string; type: 'pdf' | 'text' }
answer:  FileRef            // a document-store descriptor

lookup #

lookup.resolveActingUser #

request: {
  segmentId: string;
  triggerId?: string;      // the trigger the event dispatched through; absent for events that predate trigger-aware dispatch
  instance: string;        // the source instance, as adapters.resolve answered it
  event: TriggerEvent;
}
answer: { principal: PrincipalReference; email?: string } | null

locks #

Cross-run write locks. A token stands for one acquire.

locks.acquire #

request: { segmentId: string; names: string[] }
answer:  { token: string }

locks.release #

request: { segmentId: string; token: string }
answer:  void

trace #

trace.append #

request: { segmentId: string; entries: MovementTraceEntry[] }
answer:  void

clock #

The engine reads no clock of its own. Every instant it needs that is not the run's firing instant comes from here: a sleep's wake time and an every timer's, a callback's expiry, the pin of a segment handed no firedAt, the instant a WITHIN measures recency from (one clock.now request per row the WITHIN is evaluated on: a hop filter holding one, over N rows, asks N times), and every measured duration on the trace (a plugin invocation's, a model call's).

clock.now #

request: { segmentId: string }
answer:  { nowIso: string }   // ISO 8601

ids #

The engine mints no id of its own. An extraction stamps each source and fetched file it carries as a resource with an id from here.

ids.mint #

request: { segmentId: string; count: number }   // count ≥ 1
answer:  string[]                                // exactly count distinct, non-empty ids

Spend #

The engine keeps the run's ledger (movement_engine/run_context.ts). The budget it sends with each priced call is the run's cap less what it has spent against it (null with no cap), and every answer's cost is charged. A host refuses priced work past the budget by answering a BudgetRefusal carrying what the call spent first; the engine charges that, raises the run's cap (RunCostCapExceeded) from its own figures, and suspends the run there (park.sink.commitLimitPause, then commitSuspension).

The Rust engine's requests #

The Rust engine (crates/movement-engine; the engine for language version 5 and later, by the run's pin alone) is an engine, not a host: it runs a segment the run host opened (inRunContext in movement_engine/run.ts), and its shim (movement_engine/rust_host.ts) forwards each request it makes to the run's EngineHost. Every request that names an EngineHost method is that method's request, in the shape above, with the segment's id; the engine's slice reaches segment only through the run host, and adapters.resolve, adapters.call, llm.client.call, llm.extractCall.call (an extraction call, extract(…), extractOne(…)), llm.transforms.invoke, llm.arbitrateEntities, files.resolveFileText, files.render, lookup.resolveActingUser, park.sink.* (a timer, a suspension, an await's park under its correlation key, a limit pause, and a combinator's join: recordJoin, persistBranchExport, decrementJoin, collectBranchExports, cancelSubtrees), park.wake (a callback fired from inside the run), park.callbacks.mint and park.callbacks.calls, park.sources.save and park.sources.load (a park's library texts), control.cancel.*, locks.*, trace.append, clock.now and ids.mint itself. It asks ids.mint for an extraction's resources (once per batch, in the tree's order); a callback's id is minted by park.callbacks.mint, as the TypeScript engine's is. An await of a landing asks the awaited adapter's resolveAwait through adapters.call. parallel and race arms run as concurrent flows: their requests are in flight together, interleaved as the TypeScript engine's arms interleave them, and effects take the run's in-run locks in the same order.

Four requests are the Rust engine's own: they are not EngineHost methods, and no embedder serves them. The shim answers them in process, and a test holds every request it routes to the manifest above or to this list (ENGINE_OWN_ROUTES):

requestanswerwhy it is not on the host
check.program { source, languageVersion, upgradingFrom? }{ diagnostics, link?, stamps, versionWarning? }, the checker's AST stamps by path (a call nested in an expression, which the checker reads once per place, continues the path through its slot: <slot>/nested/<start>-<end>/value, its extent in the slot's text; program_check.ts), and versionWarning: { code, message } for a deprecated pin as the host's engine reads its release (the warning the run traces); a program that fails its check gets no answer, and the run fails with MOVENG_CHECK (or MOVENG_PARSE, MOVENG_LANGUAGE_VERSION) as the TypeScript engine's does. It names no segment: the in-process run host checks before it opens the segment, as the TypeScript route does, and answers the request from that check, so a program that fails it makes no host request on either enginethe checker is TypeScript until it is ported
check.compatible { source, languageVersion, file?, declaration, schema, recordType, path? }{ fits }, whether the instance schema's recordType carries everything the file's node declaration declaration declares (its extends folded in), by the checker's comparator (surfaceMisfit); { undescribed: true } when the schema enumerates nothing at that type. file names the snapshot's file that declares it (a library's), in place of source; path, the declared nodes walked from the declaration's root, compares the record with the node there, as a landing across an edge is compared (the record a synthesised node's edge holds)an IS <Decl> test of a record a system describes (the event, a handle, a landed record): the verdict is the checker's assignability over its field types, which stays TypeScript with the checker
log.write { lines }undefined; each line { level: 'info' | 'warn', message, fields } written to the platform's log, the run's id added; sent as a statement's trace is handed overthe log lines the TypeScript engine writes (an extraction stage skipped, a plugin's empty argument, a continued answer, a source file truncated for the prompt, a refinement kept)

Changes #