diff --git a/packages/cli/src/repl/model.ts b/packages/cli/src/repl/model.ts new file mode 100644 index 000000000..e64422012 --- /dev/null +++ b/packages/cli/src/repl/model.ts @@ -0,0 +1,865 @@ +/** + * The REPL's view of one execution, projected from its Journal and nothing else. + * + * The Journal is the only durable truth this feature has, so the model is a + * *reading* of it rather than a second copy: `projectRepl` walks the real + * `Yield | Close` events an ordinary execution appends, recognizes the runtime's + * existing vocabulary, and returns immutable structural values. No REPL record + * exists, nothing is cached between processes, and no component ever receives a + * `DurableEvent` — what leaves here is plain frozen data. + * + * Every shape it reads is parsed. A journal is data somebody else may have + * written, so a payload that will not read, a history this slice's one-entry + * invariant forbids, or a source position that cannot be attributed to exactly + * one scope comes back as `Err`. Attaching data to a guessed owner would put a + * binding in the wrong scope and a wrong answer on the screen; refusing says so. + * + * Projection is pure in both directions. Every value the model retains is + * *detached* from the event that carried it — copied, then frozen — so the model + * cannot be changed by whoever still holds the events, and the events are + * neither frozen nor modified by having been read. A projector that froze its + * input would make a caller's own data immutable as a side effect of being + * looked at. + * + * A marker addresses a prefix rather than a stored sequence number. It is + * derived from protocol identity — which coroutine, and how many durable yields + * that coroutine had already settled — so the same event has the same marker in + * every process that reads the file, with nothing extra written down to make + * that true. + */ + +import { Err, Ok } from "effection"; +import type { Result } from "effection"; +import { readElicitationSchema } from "@executablemd/core/host"; +import type { Close, DurableEvent, Json, Yield } from "@executablemd/durable-streams"; + +/** The authored root scope: this slice admits one entry and this is its key. */ +export const ENTRY_SCOPE = "entry-1"; + +/** The durable name of the root document import. */ +const ROOT_IMPORT = "__root__"; + +const SOURCE_POSITION_FIELD = "executablemd.source-position"; + +/** What the REPL could not read, and what it was reading when it stopped. */ +export class ReplProjectionError extends Error { + constructor(message: string) { + super(message); + this.name = "ReplProjectionError"; + } +} + +/** Where an authored element was written, as the journal recorded it. */ +export interface ReplPosition { + readonly path: string | undefined; + readonly offset: number; + readonly line: number; + readonly column: number; +} + +/** One name a durable eval published into its scope. */ +export interface ReplBinding { + readonly name: string; + readonly value: Json; +} + +/** One answered question, as history can still show it. */ +export interface ReplElicitation { + readonly marker: string; + readonly location: string; + readonly schema: Json; + readonly answer: Json; + readonly position: ReplPosition | undefined; +} + +/** One admitted generated fragment. */ +export interface ReplGenerated { + readonly marker: string; + readonly source: string | undefined; + readonly decision: "admitted" | "refused"; + readonly construct: string | undefined; +} + +/** + * One source region the entry admitted, and what that region holds. + * + * `kind` says where the source came from: the submitted entry, a nested + * component occurrence the run retained, or a generated fragment an authored + * invocation produced. A scope exists in a model only where the projected prefix + * admitted it, so a selection earlier than an admission has no scope to show and + * needs no separate flag saying so. + */ +export interface ReplScope { + readonly key: string; + readonly kind: "entry" | "component" | "generated"; + readonly name: string; + readonly path: string; + readonly source: string; + readonly position: ReplPosition | undefined; + readonly marker: string; + readonly bindings: readonly ReplBinding[]; + readonly elicitations: readonly ReplElicitation[]; + readonly generated: readonly ReplGenerated[]; + readonly scopes: readonly ReplScope[]; +} + +/** One offered history position, in append order. */ +export interface ReplCheckpoint { + readonly marker: string; + readonly kind: "entry" | "scope" | "binding" | "generated" | "elicit" | "terminal"; + readonly label: string; +} + +/** + * What the root coroutine settled to. + * + * Three outcomes, because the protocol has three. A document that produced a + * result closes `ok` carrying it, and the result says whether the document + * succeeded. A run that failed before there was a result to produce closes + * `err`, and there is no rendered output to restore. A cancelled run closes + * with neither. + */ +export interface ReplTerminal { + readonly status: "ok" | "err" | "cancelled"; + readonly output: string; + readonly message: string | undefined; +} + +/** One semantic transcript row. Never a record dump. */ +export type ReplRow = + | { + readonly kind: "entry"; + readonly marker: string; + readonly path: string; + readonly source: string; + } + | { + readonly kind: "scope"; + readonly marker: string; + readonly scope: string; + readonly name: string; + readonly path: string; + } + | { + readonly kind: "binding"; + readonly marker: string; + readonly scope: string; + readonly names: readonly string[]; + } + | { + readonly kind: "output"; + readonly marker: string; + readonly scope: string; + readonly text: string; + } + | { + readonly kind: "generated"; + readonly marker: string; + readonly scope: string; + readonly source: string | undefined; + readonly decision: "admitted" | "refused"; + } + | { + readonly kind: "elicit"; + readonly marker: string; + readonly scope: string; + readonly location: string; + readonly answer: Json; + } + | { + readonly kind: "effect"; + readonly marker: string; + readonly type: string; + readonly status: string; + } + | { + readonly kind: "terminal"; + readonly marker: string; + readonly status: "ok" | "err" | "cancelled"; + readonly output: string; + }; + +/** One frozen reading of one validated Journal prefix. */ +export interface ReplModel { + /** The marker this model was projected at, or none for the Journal head. */ + readonly selection: string | undefined; + /** Whether the projected prefix is the whole file. */ + readonly head: boolean; + readonly entry: ReplScope | undefined; + readonly settled: boolean; + readonly terminal: ReplTerminal | undefined; + readonly checkpoints: readonly ReplCheckpoint[]; + readonly transcript: readonly ReplRow[]; +} + +/** + * The marker for one event, given how many yields its coroutine had settled. + * + * Both spellings are total over the protocol's two events, which is what lets a + * selection name any position in the file without a sequence number being + * stored anywhere. + */ +function markerFor(event: DurableEvent, ordinal: number): string { + return event.type === "yield" + ? `yield:${event.coroutineId}:${ordinal}` + : `close:${event.coroutineId}`; +} + +/** + * Project one Journal prefix. + * + * `selection` names the last event the model may see. Absent, the model is the + * whole file. A marker that names no event in this file, or names one twice, + * is refused rather than rounded to the head: a reader looking at history must + * never be shown the present instead. + */ +export function projectRepl( + events: readonly DurableEvent[], + selection?: string, +): Result { + const markers: string[] = []; + const ordinals = new Map(); + for (const event of events) { + if (event.type === "yield") { + const ordinal = ordinals.get(event.coroutineId) ?? 0; + ordinals.set(event.coroutineId, ordinal + 1); + markers.push(markerFor(event, ordinal)); + } else { + markers.push(markerFor(event, 0)); + } + } + + const duplicated = firstDuplicate(markers); + if (duplicated !== undefined) { + return Err( + new ReplProjectionError( + `this journal records ${duplicated} twice, so one position in its history names two ` + + "events. A coroutine closes once.", + ), + ); + } + + let end = events.length; + if (selection !== undefined) { + const at = markers.indexOf(selection); + if (at === -1) { + return Err( + new ReplProjectionError( + "the selected history position is not in this journal. Return to the live head and " + + "choose a checkpoint the history offers.", + ), + ); + } + end = at + 1; + } + + return build(events.slice(0, end), markers.slice(0, end), selection, end === events.length); +} + +/** The first marker that appears twice, if any. */ +function firstDuplicate(markers: readonly string[]): string | undefined { + const seen = new Set(); + for (const marker of markers) { + if (seen.has(marker)) { + return marker; + } + seen.add(marker); + } + return undefined; +} + +/** A scope under construction, before the model is frozen. */ +interface ScopeDraft { + key: string; + kind: "entry" | "component" | "generated"; + name: string; + path: string; + source: string; + position: ReplPosition | undefined; + marker: string; + bindings: ReplBinding[]; + elicitations: ReplElicitation[]; + generated: ReplGenerated[]; + scopes: ScopeDraft[]; +} + +function build( + events: readonly DurableEvent[], + markers: readonly string[], + selection: string | undefined, + head: boolean, +): Result { + const transcript: ReplRow[] = []; + const checkpoints: ReplCheckpoint[] = []; + let entry: ScopeDraft | undefined; + let terminal: ReplTerminal | undefined; + /** Every scope by the source path it was admitted from, for owner lookup. */ + const byPath = new Map(); + const occurrences = new Map(); + + for (let index = 0; index < events.length; index++) { + const event = events[index]; + const marker = markers[index]; + + if (terminal !== undefined) { + return Err( + new ReplProjectionError( + "this journal records work after the entry settled. A settled entry is the end of " + + "its history.", + ), + ); + } + + if (event.type === "close") { + if (event.coroutineId !== "root") { + transcript.push({ kind: "effect", marker, type: "close", status: event.result.status }); + continue; + } + if (entry === undefined) { + return Err( + new ReplProjectionError( + "this journal settles an entry it never admitted. An entry's source is admitted " + + "before anything it does.", + ), + ); + } + const settled = readTerminal(event); + if (settled === undefined) { + return Err( + new ReplProjectionError( + "the recorded outcome of this entry cannot be read by this version of the REPL.", + ), + ); + } + terminal = settled; + transcript.push({ kind: "terminal", marker, status: settled.status, output: settled.output }); + checkpoints.push({ marker, kind: "terminal", label: "Settled" }); + continue; + } + + const description = event.description; + + if (description.type === "import_component" && description.name === ROOT_IMPORT) { + if (entry !== undefined) { + return Err( + new ReplProjectionError( + "this journal admits a second entry. One REPL execution holds one entry.", + ), + ); + } + if (index !== 0) { + return Err( + new ReplProjectionError( + "this journal records work before it admitted its entry. The entry's source is the " + + "first thing an execution decides.", + ), + ); + } + const retained = readRetainedSource(event); + if (retained === undefined) { + return Err( + new ReplProjectionError( + "the entry's recorded source cannot be read by this version of the REPL.", + ), + ); + } + entry = { + key: ENTRY_SCOPE, + kind: "entry", + name: ENTRY_SCOPE, + path: retained.path, + source: retained.content, + position: undefined, + marker, + bindings: [], + elicitations: [], + generated: [], + scopes: [], + }; + register(byPath, entry); + transcript.push({ kind: "entry", marker, path: retained.path, source: retained.content }); + checkpoints.push({ marker, kind: "entry", label: "Entry 1 admitted" }); + continue; + } + + if (entry === undefined) { + return Err( + new ReplProjectionError( + "this journal records work before it admitted its entry. The entry's source is the " + + "first thing an execution decides.", + ), + ); + } + + const position = readPosition(event); + if (position === MALFORMED) { + return Err( + new ReplProjectionError( + "a recorded effect carries a source position this version cannot read, so nothing can " + + "say which part of the entry it belongs to.", + ), + ); + } + + if (description.type === "import_component") { + const retained = readRetainedSource(event); + if (retained === undefined) { + transcript.push({ + kind: "effect", + marker, + type: description.type, + status: event.result.status, + }); + continue; + } + const owner = ownerOf(byPath, position, `<${description.name} />`); + if (!owner.ok) { + return owner; + } + const ordinalKey = `${owner.value.key}/${description.name}`; + const ordinal = (occurrences.get(ordinalKey) ?? 0) + 1; + occurrences.set(ordinalKey, ordinal); + const scope: ScopeDraft = { + key: `${description.name}-${ordinal}`, + kind: "component", + name: description.name, + path: retained.path, + source: retained.content, + position, + marker, + bindings: [], + elicitations: [], + generated: [], + scopes: [], + }; + owner.value.scopes.push(scope); + register(byPath, scope); + transcript.push({ + kind: "scope", + marker, + scope: scope.key, + name: scope.name, + path: scope.path, + }); + checkpoints.push({ marker, kind: "scope", label: `<${description.name} /> admitted` }); + continue; + } + + if (description.type === "eval") { + if (event.result.status !== "ok") { + transcript.push({ kind: "effect", marker, type: "eval", status: event.result.status }); + continue; + } + const published = readExports(event); + if (published === undefined) { + return Err( + new ReplProjectionError( + "a recorded evaluation's published values cannot be read by this version of the REPL.", + ), + ); + } + const owner = ownerOf(byPath, position, "an evaluated block"); + if (!owner.ok) { + return owner; + } + for (const [name, value] of published.bindings) { + bind(owner.value, name, value); + } + if (published.bindings.length > 0) { + transcript.push({ + kind: "binding", + marker, + scope: owner.value.key, + names: Object.freeze(published.bindings.map(([name]) => name)), + }); + checkpoints.push({ + marker, + kind: "binding", + label: published.bindings.map(([name]) => name).join(", "), + }); + } + if (published.output !== undefined) { + transcript.push({ kind: "output", marker, scope: owner.value.key, text: published.output }); + } + continue; + } + + if (description.type === "generated_xmd") { + if (event.result.status !== "ok") { + transcript.push({ + kind: "effect", + marker, + type: "generated_xmd", + status: event.result.status, + }); + continue; + } + const admission = readAdmission(event); + if (admission === undefined) { + return Err( + new ReplProjectionError( + "a recorded generated fragment cannot be read by this version of the REPL.", + ), + ); + } + const owner = ownerOf(byPath, position, "a generated fragment"); + if (!owner.ok) { + return owner; + } + owner.value.generated.push({ marker, ...admission }); + if (admission.decision === "admitted" && admission.source !== undefined) { + const ordinalKey = `${owner.value.key}/generated`; + const ordinal = (occurrences.get(ordinalKey) ?? 0) + 1; + occurrences.set(ordinalKey, ordinal); + owner.value.scopes.push({ + key: `generated-${ordinal}`, + kind: "generated", + name: "generated", + path: owner.value.path, + source: admission.source, + position, + marker, + bindings: [], + elicitations: [], + generated: [], + scopes: [], + }); + } + transcript.push({ + kind: "generated", + marker, + scope: owner.value.key, + source: admission.source, + decision: admission.decision, + }); + checkpoints.push({ marker, kind: "generated", label: "Generated XMD admitted" }); + continue; + } + + if (description.type === "elicit") { + if (event.result.status !== "ok") { + transcript.push({ kind: "effect", marker, type: "elicit", status: event.result.status }); + continue; + } + const schema = readElicitationSchema(description); + if (schema === undefined) { + return Err( + new ReplProjectionError( + "a recorded question does not retain the schema it asked, so its answer cannot be " + + "shown as the question it answered.", + ), + ); + } + const answer = event.result.value; + if (answer === undefined) { + return Err(new ReplProjectionError("a recorded question records no answer at all.")); + } + const owner = ownerOf(byPath, position, "an answered question"); + if (!owner.ok) { + return owner; + } + const location = description.name.startsWith("elicit:") + ? description.name.slice("elicit:".length) + : description.name; + const asked = detach(schema); + const given = detach(answer); + owner.value.elicitations.push({ marker, location, schema: asked, answer: given, position }); + transcript.push({ kind: "elicit", marker, scope: owner.value.key, location, answer: given }); + checkpoints.push({ marker, kind: "elicit", label: `Answered ${location}` }); + continue; + } + + transcript.push({ + kind: "effect", + marker, + type: description.type, + status: event.result.status, + }); + } + + return Ok( + freeze({ + selection, + head, + entry: entry === undefined ? undefined : freezeScope(entry), + settled: terminal !== undefined, + terminal: terminal === undefined ? undefined : freeze({ ...terminal }), + checkpoints: Object.freeze(checkpoints.map((checkpoint) => freeze({ ...checkpoint }))), + transcript: Object.freeze(transcript.map((row) => freeze({ ...row }))), + }), + ); +} + +/** Publish one name into a scope, replacing an earlier value for that name. */ +function bind(scope: ScopeDraft, name: string, value: Json): void { + const at = scope.bindings.findIndex((binding) => binding.name === name); + const binding = { name, value }; + if (at === -1) { + scope.bindings.push(binding); + return; + } + scope.bindings[at] = binding; +} + +function register(index: Map, scope: ScopeDraft): void { + const held = index.get(scope.path); + if (held === undefined) { + index.set(scope.path, [scope]); + return; + } + held.push(scope); +} + +/** + * The one scope an effect's source position belongs to. + * + * Attribution is by the path the position names, which is the path the scope's + * own source was admitted from. A position naming no admitted source, or naming + * one that this prefix admitted more than once, is refused: a binding attached + * to a guessed owner is a value shown in the wrong place, and there is no + * spelling of "probably this one" that a reader could check. + */ +function ownerOf( + index: Map, + position: ReplPosition | undefined, + subject: string, +): Result { + if (position === undefined || position.path === undefined) { + return Err( + new ReplProjectionError( + `${subject} was recorded without the source position that says which part of the entry ` + + "it belongs to.", + ), + ); + } + const held = index.get(position.path) ?? []; + if (held.length === 0) { + return Err( + new ReplProjectionError( + `${subject} names a source this entry never admitted, so nothing owns it.`, + ), + ); + } + if (held.length > 1) { + return Err( + new ReplProjectionError( + `${subject} names a source this entry admitted more than once, so which occurrence owns ` + + "it cannot be decided.", + ), + ); + } + return Ok(held[0]); +} + +/** What a position that will not read is, as distinct from one that is absent. */ +const MALFORMED = Symbol("malformed source position"); + +function readPosition(event: Yield): ReplPosition | undefined | typeof MALFORMED { + const field = event.description[SOURCE_POSITION_FIELD]; + if (field === undefined) { + return undefined; + } + if (!isJsonObject(field)) { + return MALFORMED; + } + const path = field["path"]; + const offset = field["offset"]; + const line = field["line"]; + const column = field["column"]; + if (path !== undefined && typeof path !== "string") { + return MALFORMED; + } + if (!isIndex(offset) || !isOrdinal(line) || !isOrdinal(column)) { + return MALFORMED; + } + return { path, offset, line, column }; +} + +/** The exact source a recorded import retained, or none when it retained none. */ +function readRetainedSource(event: Yield): { path: string; content: string } | undefined { + if (event.result.status !== "ok" || !isJsonObject(event.result.value)) { + return undefined; + } + const record = event.result.value; + const path = record["path"]; + const content = record["content"]; + if (typeof path !== "string" || typeof content !== "string") { + return undefined; + } + return { path, content }; +} + +/** + * The names a recorded evaluation published, and the output it rendered. + * + * Each published value is detached as it is read, so what the model goes on to + * hold is never the object the event holds. + */ +function readExports( + event: Yield, +): { bindings: [string, Json][]; output: string | undefined } | undefined { + if (event.result.status !== "ok" || !isJsonObject(event.result.value)) { + return undefined; + } + const published = event.result.value["value"]; + if (!isJsonObject(published)) { + return undefined; + } + const bindings: [string, Json][] = []; + let output: string | undefined; + for (const [name, value] of Object.entries(published)) { + if (name === "__output") { + if (typeof value !== "string") { + return undefined; + } + output = value; + continue; + } + bindings.push([name, detach(value)]); + } + return { bindings, output }; +} + +/** What a recorded generated fragment decided. */ +function readAdmission(event: Yield): Omit | undefined { + if (event.result.status !== "ok" || !isJsonObject(event.result.value)) { + return undefined; + } + const record = event.result.value; + const decision = record["decision"]; + if (decision === "admitted") { + const source = record["source"]; + if (typeof source !== "string") { + return undefined; + } + return { source, decision, construct: undefined }; + } + if (decision === "refused") { + const construct = record["construct"]; + if (typeof construct !== "string") { + return undefined; + } + return { source: undefined, decision, construct }; + } + return undefined; +} + +/** What the root coroutine's close records, read as the document outcome. */ +function readTerminal(event: Close): ReplTerminal | undefined { + if (event.result.status === "cancelled") { + return { status: "cancelled", output: "", message: undefined }; + } + if (event.result.status === "err") { + // The message alone. A serialized stack names host paths and the engine's + // own frames, and the transcript is a reader's view of their entry. + return { status: "err", output: "", message: event.result.error.message }; + } + if (!isJsonObject(event.result.value)) { + return undefined; + } + const record = event.result.value; + const output = record["output"]; + const status = record["status"]; + if (typeof output !== "string") { + return undefined; + } + if (status === "ok") { + return { status, output, message: undefined }; + } + if (status !== "err") { + return undefined; + } + const failure = record["error"]; + if (!isJsonObject(failure) || typeof failure["message"] !== "string") { + return undefined; + } + return { status, output, message: failure["message"] }; +} + +function isJsonObject(value: Json | undefined): value is { [key: string]: Json } { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function isIndex(value: Json | undefined): value is number { + return typeof value === "number" && Number.isInteger(value) && value >= 0; +} + +function isOrdinal(value: Json | undefined): value is number { + return typeof value === "number" && Number.isInteger(value) && value >= 1; +} + +/** + * Freeze a scope and everything reachable from it. + * + * Everything reachable is already this projection's own: a retained value was + * detached the moment it was read, so freezing here cannot reach an object the + * caller still holds. What the router hands a component is the exact object the + * model holds, which is what makes two readings of one prefix one reading. + */ +function freezeScope(draft: ScopeDraft): ReplScope { + return freeze({ + key: draft.key, + kind: draft.kind, + name: draft.name, + path: draft.path, + source: draft.source, + position: draft.position === undefined ? undefined : freeze({ ...draft.position }), + marker: draft.marker, + bindings: Object.freeze( + draft.bindings.map((binding) => freeze({ name: binding.name, value: binding.value })), + ), + elicitations: Object.freeze( + draft.elicitations.map((elicitation) => + freeze({ + marker: elicitation.marker, + location: elicitation.location, + schema: elicitation.schema, + answer: elicitation.answer, + position: + elicitation.position === undefined ? undefined : freeze({ ...elicitation.position }), + }), + ), + ), + generated: Object.freeze(draft.generated.map((generated) => freeze({ ...generated }))), + scopes: Object.freeze(draft.scopes.map(freezeScope)), + }); +} + +function freeze(value: T): Readonly { + return Object.freeze(value); +} + +/** + * One retained value, copied out of the event graph and frozen. + * + * Copied rather than frozen in place, because the events belong to whoever + * handed them over. Freezing a value inside them would reach back out of this + * function and silently make the caller's own data immutable — a projection + * that changed its input, which is the one thing a projection must not do. The + * copy is what the model retains, so nothing the caller does to its events + * afterwards can change what a reader is looking at either. + * + * `__proto__` is defined rather than assigned, for the same reason + * `parseDurableEvent` defines it: assignment reaches `Object.prototype`'s + * inherited setter and would drop the member while replacing the prototype. + */ +function detach(value: Json): Json { + if (value === null || typeof value !== "object") { + return value; + } + if (Array.isArray(value)) { + const members = value.map(detach); + Object.freeze(members); + return members; + } + const copy: { [key: string]: Json } = {}; + for (const [key, member] of Object.entries(value)) { + Object.defineProperty(copy, key, { + value: detach(member), + enumerable: true, + writable: true, + configurable: true, + }); + } + return Object.freeze(copy); +} diff --git a/packages/cli/src/repl/route.ts b/packages/cli/src/repl/route.ts new file mode 100644 index 000000000..fb4e8a5ef --- /dev/null +++ b/packages/cli/src/repl/route.ts @@ -0,0 +1,467 @@ +/** + * Where the REPL is, as one string a person can copy. + * + * A location names the Journal, the surface, the structure selected inside it, + * the drawers stacked over it and — optionally — the exact history position the + * whole view is frozen at. That is everything a second process needs to + * reconstruct what the first one was showing, which is why nothing about the + * *running* process appears here: pause is expansion control and keyboard focus + * is derived, so neither is a place, and a URL that carried them would describe + * a session instead of a view. + * + * ```text + * xmd://repl//[/][/]*[/+]* + * [?at=][&inspect][&draft=] + * ``` + * + * The codec is pure and has no model. `decodeLocation` decides only what the + * grammar can decide — a malformed escape, a missing segment, an illegal + * combination — and `resolveLocation` decides everything that depends on what a + * Journal actually holds, by looking the exact objects up in a frozen model. The + * split is what keeps a typo in a location from being answered with a guess + * about the history. + */ + +import { Err, Ok } from "effection"; +import type { Result } from "effection"; + +import { ENTRY_SCOPE } from "./model.ts"; +import type { ReplBinding, ReplElicitation, ReplModel, ReplScope } from "./model.ts"; + +/** The one scheme and authority a REPL location has. */ +const PREFIX = "xmd://repl/"; + +/** What a location could not say, and why nothing was selected. */ +export class ReplRouteError extends Error { + constructor(message: string) { + super(message); + this.name = "ReplRouteError"; + } +} + +/** The two surfaces this slice has. */ +export type ReplSurface = "repl" | "sessions"; + +/** One drawer in the stack, as the grammar spells it. */ +export type ReplDrawerRef = + | { readonly kind: "binding"; readonly name: string } + | { readonly kind: "recorded-elicit"; readonly marker: string } + | { readonly kind: "live-elicit" } + | { readonly kind: "history" }; + +/** One decoded location. Every member is what the grammar said, parsed. */ +export interface ReplRoute { + readonly execution: string; + readonly surface: ReplSurface; + /** The entry and then each nested scope key, outermost first. */ + readonly scopes: readonly string[]; + readonly drawers: readonly ReplDrawerRef[]; + /** The history position the whole view is frozen at, or none for the head. */ + readonly at: string | undefined; + /** Whether that frozen position is read-only. Requires `at`. */ + readonly inspect: boolean; + /** Text typed but not yet admitted. Exists only before an entry does. */ + readonly draft: string | undefined; +} + +/** One drawer resolved against a model. */ +export type ReplDrawer = + | { readonly kind: "binding"; readonly name: string; readonly binding: ReplBinding } + | { readonly kind: "recorded-elicit"; readonly elicitation: ReplElicitation } + | { readonly kind: "live-elicit" } + | { readonly kind: "history" }; + +/** What one location selects in one model. */ +export interface ReplSelection { + readonly route: ReplRoute; + readonly surface: ReplSurface; + readonly entry: ReplScope | undefined; + /** The entry and each selected nested scope, outermost first. */ + readonly ancestry: readonly ReplScope[]; + /** The innermost selected scope, which is the entry when only it was named. */ + readonly scope: ReplScope | undefined; + readonly drawers: readonly ReplDrawer[]; +} + +/** + * Decode one location into a typed route. + * + * Equivalent spellings decode to the same route: percent escapes are resolved, + * and query members are read by name rather than by position. What decoding + * refuses is the grammar's own business — an unreadable escape, an absent + * execution or surface, `inspect` with no position to inspect, a draft beside a + * structure or a history position it could not coexist with, a drawer before a + * scope, or a member nothing defines. + */ +export function decodeLocation(location: string): Result { + if (!location.startsWith(PREFIX)) { + return refuse(`a REPL location begins with ${PREFIX}`); + } + const rest = location.slice(PREFIX.length); + if (rest.includes("#")) { + return refuse("a REPL location has no fragment"); + } + const split = rest.indexOf("?"); + const pathText = split === -1 ? rest : rest.slice(0, split); + const queryText = split === -1 ? "" : rest.slice(split + 1); + + const segments = pathText.length === 0 ? [] : pathText.split("/"); + if (segments.length < 2) { + return refuse("a REPL location names an execution and a surface"); + } + const decoded: string[] = []; + for (const segment of segments) { + if (segment.length === 0) { + return refuse("a REPL location has no empty path segment"); + } + const value = decodeSegment(segment); + if (value === undefined) { + return refuse("a REPL location's escapes must be readable"); + } + decoded.push(value); + } + + const execution = decoded[0]; + if (!isOpaqueExecution(execution)) { + return refuse("an execution is named by the opaque identifier the REPL created for it"); + } + const surface = decoded[1]; + if (surface !== "repl" && surface !== "sessions") { + return refuse('a REPL location addresses the "repl" or the "sessions" surface'); + } + + const scopes: string[] = []; + const drawers: ReplDrawerRef[] = []; + for (const segment of decoded.slice(2)) { + if (segment.startsWith("+")) { + const drawer = readDrawer(segment.slice(1)); + if (drawer === undefined) { + return refuse("a drawer segment names a drawer this REPL has"); + } + drawers.push(drawer); + continue; + } + if (drawers.length > 0) { + return refuse("a REPL location's scopes precede its drawers"); + } + scopes.push(segment); + } + if (surface === "sessions" && (scopes.length > 0 || drawers.length > 0)) { + return refuse("the Sessions surface holds no entry, scope or drawer"); + } + + const query = readQuery(queryText); + if (query === undefined) { + return refuse("a REPL location's query must be readable and hold only what it defines"); + } + if (query.inspect && query.at === undefined) { + return refuse("inspecting a history position needs the position"); + } + if (query.draft !== undefined && (query.at !== undefined || scopes.length > 0)) { + return refuse( + "a draft is text no entry has admitted yet, so it cannot accompany an admitted " + + "structure or a history position", + ); + } + + return Ok({ + execution, + surface, + scopes: Object.freeze(scopes), + drawers: Object.freeze(drawers), + at: query.at, + inspect: query.inspect, + draft: query.draft, + }); +} + +/** + * The one canonical spelling of a route. + * + * Canonical means one answer: segments in structural order, drawers after them + * in stack order, and the query members in this fixed order. A route this + * function cannot spell is an impossible route rather than an unusual one, so it + * raises instead of emitting something `decodeLocation` would then refuse. + */ +export function encodeLocation(route: ReplRoute): string { + if (!isOpaqueExecution(route.execution)) { + throw new TypeError("a REPL location names an execution by its opaque identifier"); + } + if (route.inspect && route.at === undefined) { + throw new TypeError("inspecting a history position needs the position"); + } + if (route.draft !== undefined && (route.at !== undefined || route.scopes.length > 0)) { + throw new TypeError("a draft cannot accompany an admitted structure or a history position"); + } + if (route.surface === "sessions" && (route.scopes.length > 0 || route.drawers.length > 0)) { + throw new TypeError("the Sessions surface holds no entry, scope or drawer"); + } + + const path = [route.execution, route.surface, ...route.scopes].map(encodeSegment); + for (const drawer of route.drawers) { + path.push(`+${encodeSegment(spellDrawer(drawer))}`); + } + const query: string[] = []; + if (route.at !== undefined) { + query.push(`at=${encodeValue(route.at)}`); + } + if (route.inspect) { + query.push("inspect"); + } + if (route.draft !== undefined) { + query.push(`draft=${encodeValue(route.draft)}`); + } + const search = query.length === 0 ? "" : `?${query.join("&")}`; + return `${PREFIX}${path.join("/")}${search}`; +} + +/** + * Resolve one route against one frozen model. + * + * Every answer is an object the model already holds, so two readings of one + * prefix are the same reading and nothing here can invent a value the Journal + * did not record. A target that is absent refuses; so does a drawer stack whose + * earlier members were left out, because a stack with a hole in it does not + * describe a surface anybody saw. + */ +export function resolveLocation(model: ReplModel, route: ReplRoute): Result { + if (route.at !== model.selection) { + return Err( + new ReplRouteError( + "this route selects a history position the model was not projected at. Project the " + + "prefix the route names before resolving against it.", + ), + ); + } + if (route.surface === "sessions") { + return Ok({ + route, + surface: "sessions", + entry: undefined, + ancestry: Object.freeze([]), + scope: undefined, + drawers: Object.freeze([]), + }); + } + if (route.draft !== undefined && model.entry !== undefined) { + return Err( + new ReplRouteError( + "this route carries a draft, and this execution has already admitted its entry. An " + + "admitted entry is immutable.", + ), + ); + } + + const ancestry: ReplScope[] = []; + let scope: ReplScope | undefined; + for (const key of route.scopes) { + if (scope === undefined) { + if (key !== ENTRY_SCOPE) { + return Err(new ReplRouteError(`this execution has no ${key}.`)); + } + if (model.entry === undefined) { + return Err( + new ReplRouteError( + "this route selects an entry, and this execution has not admitted one yet.", + ), + ); + } + scope = model.entry; + ancestry.push(scope); + continue; + } + const child = scope.scopes.find((candidate) => candidate.key === key); + if (child === undefined) { + return Err(new ReplRouteError(`${scope.key} holds no ${key} at this history position.`)); + } + scope = child; + ancestry.push(child); + } + + const drawers: ReplDrawer[] = []; + for (const reference of route.drawers) { + const drawer = openDrawer(model, route, scope, reference); + if (!drawer.ok) { + return drawer; + } + drawers.push(drawer.value); + } + + return Ok({ + route, + surface: "repl", + entry: model.entry, + ancestry: Object.freeze(ancestry), + scope, + drawers: Object.freeze(drawers), + }); +} + +function openDrawer( + model: ReplModel, + route: ReplRoute, + scope: ReplScope | undefined, + reference: ReplDrawerRef, +): Result { + if (reference.kind === "history") { + return Ok({ kind: "history" }); + } + if (reference.kind === "binding") { + if (scope === undefined) { + return Err( + new ReplRouteError("a binding drawer opens over the scope that published the name."), + ); + } + const binding = scope.bindings.find((candidate) => candidate.name === reference.name); + if (binding === undefined) { + return Err( + new ReplRouteError(`${scope.key} publishes no ${reference.name} at this history position.`), + ); + } + return Ok({ kind: "binding", name: reference.name, binding }); + } + if (reference.kind === "live-elicit") { + if (route.at !== undefined) { + return Err( + new ReplRouteError( + "a live question belongs to the running expansion, and this view is frozen at an " + + "earlier position. Return to the live head to answer it.", + ), + ); + } + return Ok({ kind: "live-elicit" }); + } + if (scope === undefined) { + return Err(new ReplRouteError("an answered question is inspected in the scope that asked it.")); + } + const elicitation = scope.elicitations.find((candidate) => candidate.marker === reference.marker); + if (elicitation === undefined) { + if (model.entry === undefined) { + return Err(new ReplRouteError("this execution has recorded no question.")); + } + return Err(new ReplRouteError(`${scope.key} recorded no question at ${reference.marker}.`)); + } + return Ok({ kind: "recorded-elicit", elicitation }); +} + +/** The drawer one segment names, or none when nothing names it. */ +function readDrawer(text: string): ReplDrawerRef | undefined { + const at = text.indexOf(":"); + const kind = at === -1 ? text : text.slice(0, at); + const argument = at === -1 ? undefined : text.slice(at + 1); + + if (kind === "history") { + return argument === undefined ? { kind: "history" } : undefined; + } + if (kind === "binding") { + return argument === undefined || argument.length === 0 + ? undefined + : { kind: "binding", name: argument }; + } + if (kind === "elicit") { + if (argument === undefined) { + return { kind: "live-elicit" }; + } + return argument.length === 0 ? undefined : { kind: "recorded-elicit", marker: argument }; + } + return undefined; +} + +function spellDrawer(drawer: ReplDrawerRef): string { + if (drawer.kind === "history") { + return "history"; + } + if (drawer.kind === "binding") { + return `binding:${drawer.name}`; + } + return drawer.kind === "recorded-elicit" ? `elicit:${drawer.marker}` : "elicit"; +} + +/** What the query said, or none when it said something it does not define. */ +function readQuery( + text: string, +): { at: string | undefined; inspect: boolean; draft: string | undefined } | undefined { + let at: string | undefined; + let inspect = false; + let draft: string | undefined; + if (text.length === 0) { + return { at, inspect, draft }; + } + for (const member of text.split("&")) { + if (member.length === 0) { + return undefined; + } + const equals = member.indexOf("="); + const name = decodeSegment(equals === -1 ? member : member.slice(0, equals)); + const value = equals === -1 ? undefined : decodeSegment(member.slice(equals + 1)); + if (name === undefined || (equals !== -1 && value === undefined)) { + return undefined; + } + if (name === "at" && value !== undefined && value.length > 0 && at === undefined) { + at = value; + continue; + } + if (name === "inspect" && value === undefined && !inspect) { + inspect = true; + continue; + } + if (name === "draft" && value !== undefined && draft === undefined) { + draft = value; + continue; + } + return undefined; + } + return { at, inspect, draft }; +} + +/** + * Whether this is an identifier the REPL could have created. + * + * Narrow on purpose: the execution segment becomes a file name, so the grammar + * admits only characters that cannot address a directory, a parent, or a hidden + * name. Checked in the codec rather than at the filesystem, so an impossible + * identifier never reaches a path at all. + */ +export function isOpaqueExecution(execution: string): boolean { + return /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(execution); +} + +/** Every character a segment or value keeps as written. */ +const LITERAL = /^[A-Za-z0-9\-._~:@]$/; + +function encodeSegment(text: string): string { + let encoded = ""; + for (const character of text) { + encoded += LITERAL.test(character) ? character : percent(character); + } + return encoded; +} + +function encodeValue(text: string): string { + let encoded = ""; + for (const character of text) { + encoded += LITERAL.test(character) || character === "/" ? character : percent(character); + } + return encoded; +} + +function percent(character: string): string { + let encoded = ""; + for (const byte of new TextEncoder().encode(character)) { + encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`; + } + return encoded; +} + +/** One segment's text, or none when its escapes will not read. */ +function decodeSegment(text: string): string | undefined { + try { + return decodeURIComponent(text); + } catch { + return undefined; + } +} + +function refuse(message: string): Result { + return Err(new ReplRouteError(message)); +} diff --git a/packages/cli/tests/fixtures/repl/Checklist.md b/packages/cli/tests/fixtures/repl/Checklist.md new file mode 100644 index 000000000..a81579b4a --- /dev/null +++ b/packages/cli/tests/fixtures/repl/Checklist.md @@ -0,0 +1,13 @@ +--- +title: Remaining work on one plan +required: [title, steps] +props: + title: + type: string + steps: + type: number +--- + +### {props.title} + +{props.steps} steps remain. diff --git a/packages/cli/tests/fixtures/repl/entry.md b/packages/cli/tests/fixtures/repl/entry.md new file mode 100644 index 000000000..fd33d00dc --- /dev/null +++ b/packages/cli/tests/fixtures/repl/entry.md @@ -0,0 +1,18 @@ +```js eval +const plan = { title: "Ship the REPL", steps: 2 }; +const summarySource = ``; +const responseSchema = { + type: "object", + properties: { decision: { type: "string", enum: ["approve", "decline"] } }, + required: ["decision"], + additionalProperties: false, +}; +``` + + + + + +Approve {plan.title}? + +Decision: {response.decision} diff --git a/packages/cli/tests/fixtures/repl/reference.ts b/packages/cli/tests/fixtures/repl/reference.ts new file mode 100644 index 000000000..f5c338edc --- /dev/null +++ b/packages/cli/tests/fixtures/repl/reference.ts @@ -0,0 +1,104 @@ +/** + * The reference entry, executed for real. + * + * Every REPL test that needs a Journal gets one from here rather than from a + * recorded file, because the projector's whole claim is that it reads what the + * *current* runtime writes. A frozen event array would keep passing on the day + * core changed a record's shape, which is the day the claim stopped being true. + * + * The entry is one document deliberately holding one of each thing this slice + * projects: a durable evaluation that publishes JSON, one nested component + * occurrence whose source the run retains, one generated fragment admitted from + * a value that evaluation published, and one validated question whose answer + * changes what the document renders after it. + */ + +import { readTextFile } from "@effectionx/fs"; +import { fileURLToPath } from "node:url"; +import { InMemoryStream } from "@executablemd/durable-streams"; +import type { DurableEvent, Json } from "@executablemd/durable-streams"; +import { collect, Elicitation } from "@executablemd/core"; +import type { ElicitationRequest } from "@executablemd/core"; +import { executeInstalled } from "@executablemd/core/host"; +import { inlineSource } from "@executablemd/core"; +import type { Operation } from "effection"; +import { scoped } from "effection"; + +import { ordinaryEvaluationProfile } from "../../../src/evaluation-profile.ts"; + +/** Where the entry and its nested component live. */ +export const REFERENCE_DIRECTORY = fileURLToPath(new URL("./", import.meta.url)); + +/** The exact entry text this slice's evidence submits. */ +export function referenceSource(): Operation { + return readTextFile(fileURLToPath(new URL("./entry.md", import.meta.url))); +} + +/** What one reference execution produced. */ +export interface ReferenceRun { + readonly output: string; + readonly events: DurableEvent[]; + readonly asked: ElicitationRequest[]; + readonly failure: Error | undefined; +} + +/** + * Run the reference entry once, over `stream`. + * + * `answer` decides what the installed provider hands back, so a test can answer, + * refuse, or count. Failures are captured rather than raised: how far a run got + * is most of what the negative controls are about. + */ +export function runReference( + answer: (request: ElicitationRequest) => Operation, + stream: InMemoryStream = new InMemoryStream(), +): Operation { + return scoped(function* () { + const asked: ElicitationRequest[] = []; + yield* Elicitation.around( + { + *elicit([request]) { + asked.push(request); + return yield* answer(request); + }, + }, + { at: "min" }, + ); + const source = yield* referenceSource(); + try { + const execution = yield* executeInstalled( + { ...inlineSource(source), stream, includes: [REFERENCE_DIRECTORY] }, + [{ evaluation: ordinaryEvaluationProfile() }], + ); + const output = yield* collect(execution); + return { output: String(output), events: yield* stream.readAll(), asked, failure: undefined }; + } catch (error) { + return { + output: "", + events: yield* stream.readAll(), + asked, + failure: error instanceof Error ? error : new Error(String(error)), + }; + } + }); +} + +/** The answer the reference journey submits. */ +export const REFERENCE_ANSWER: Json = { decision: "approve" }; + +/** A provider that always answers the same way. */ +export function answering(value: Json): (request: ElicitationRequest) => Operation { + // deno-lint-ignore require-yield + return function* () { + return value; + }; +} + +/** The complete reference Journal of one settled run. */ +export function* referenceEvents(): Operation { + const run = yield* runReference(answering(REFERENCE_ANSWER)); + if (run.failure !== undefined) { + throw run.failure; + } + return run.events; +} diff --git a/packages/cli/tests/repl-model.test.ts b/packages/cli/tests/repl-model.test.ts new file mode 100644 index 000000000..b7f0e0d17 --- /dev/null +++ b/packages/cli/tests/repl-model.test.ts @@ -0,0 +1,438 @@ +/** + * What the REPL knows, and where it learned it (#848 M1, M2). + * + * Every journal here is produced by really executing the reference entry, so + * what is under test is the projector against the vocabulary the current runtime + * writes. The negative controls doctor that real journal one member at a time: + * a history this slice forbids, a payload that will not read, and an owning + * source position removed or pointed somewhere else. Each has to come back as + * `Err` with no model at all, because a model built from a guess would show a + * binding in a scope that never published it. + */ + +import { beforeAll, describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { useTempFileCompiler } from "@executablemd/core"; +import { parseDurableEvent, serializeDurableEvent } from "@executablemd/durable-streams"; +import type { DurableEvent, Json } from "@executablemd/durable-streams"; + +import { ENTRY_SCOPE, projectRepl } from "../src/repl/model.ts"; +import type { ReplModel, ReplScope } from "../src/repl/model.ts"; +import { + answering, + REFERENCE_ANSWER, + referenceEvents, + runReference, +} from "./fixtures/repl/reference.ts"; + +/** The projected model, or a failure the test reports instead of asserting past. */ +function projected(events: readonly DurableEvent[], selection?: string): ReplModel { + const result = projectRepl(events, selection); + if (!result.ok) { + throw result.error; + } + return result.value; +} + +/** The refusal a doctored journal produced, or a failure naming what passed. */ +function refusal(events: readonly DurableEvent[], selection?: string): string { + const result = projectRepl(events, selection); + if (result.ok) { + throw new Error("the projection accepted a journal it must refuse"); + } + return result.error.message; +} + +/** The same journal, through the protocol's own text form. */ +function copied(events: readonly DurableEvent[]): DurableEvent[] { + return events.map((event) => { + const parsed = parseDurableEvent(serializeDurableEvent(event)); + if (!parsed.ok) { + throw parsed.error; + } + return parsed.value; + }); +} + +/** + * Every object and array reachable from a value, itself included. + * + * The whole graph rather than the top level, because the value a projection is + * tempted to freeze in place is nested several members down inside a recorded + * result. + */ +function reachable(value: unknown, seen: Set = new Set()): object[] { + if (value === null || typeof value !== "object" || seen.has(value)) { + return []; + } + seen.add(value); + const found: object[] = [value]; + for (const member of Object.values(value)) { + found.push(...reachable(member, seen)); + } + return found; +} + +/** The mutable `plan` object one recorded evaluation published. */ +function publishedPlan(events: readonly DurableEvent[]): { [key: string]: Json } { + const event = events[indexOfType(events, "eval")]; + if (event.type !== "yield" || event.result.status !== "ok") { + throw new Error("the reference journal records one successful evaluation"); + } + const holder = asObject(event.result.value); + return asObject(asObject(holder["value"])["plan"]); +} + +function asObject(value: Json | undefined): { [key: string]: Json } { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + throw new Error("the reference journal records this member as an object"); + } + return value; +} + +/** One scope's child by key, or a failure naming what the scope holds instead. */ +function child(scope: ReplScope, key: string): ReplScope { + const found = scope.scopes.find((candidate) => candidate.key === key); + if (found === undefined) { + throw new Error(`${scope.key} holds ${scope.scopes.map((one) => one.key).join(", ")}`); + } + return found; +} + +function binding(scope: ReplScope, name: string): Json { + const found = scope.bindings.find((candidate) => candidate.name === name); + if (found === undefined) { + throw new Error(`${scope.key} publishes ${scope.bindings.map((one) => one.name).join(", ")}`); + } + return found.value; +} + +/** Every event's marker, in append order, as the projector derives them. */ +function markers(events: readonly DurableEvent[]): string[] { + const ordinals = new Map(); + return events.map((event) => { + if (event.type === "close") { + return `close:${event.coroutineId}`; + } + const ordinal = ordinals.get(event.coroutineId) ?? 0; + ordinals.set(event.coroutineId, ordinal + 1); + return `yield:${event.coroutineId}:${ordinal}`; + }); +} + +/** The index of the first event whose description has this type. */ +function indexOfType(events: readonly DurableEvent[], type: string): number { + const at = events.findIndex((event) => event.type === "yield" && event.description.type === type); + if (at === -1) { + throw new Error(`the reference journal records no ${type}`); + } + return at; +} + +/** The same journal with one yield's description changed. */ +function withDescription( + events: readonly DurableEvent[], + at: number, + change: (description: { [key: string]: Json }) => { [key: string]: Json }, +): DurableEvent[] { + return events.map((event, index) => { + if (index !== at || event.type !== "yield") { + return event; + } + const changed = change({ ...event.description }); + if (typeof changed.type !== "string" || typeof changed.name !== "string") { + throw new Error("a doctored description keeps its identity"); + } + return { ...event, description: { ...changed, type: changed.type, name: changed.name } }; + }); +} + +/** The same journal with one yield's recorded value replaced. */ +function withValue(events: readonly DurableEvent[], at: number, value: Json): DurableEvent[] { + return events.map((event, index) => + index === at && event.type === "yield" ? { ...event, result: { status: "ok", value } } : event, + ); +} + +describe("REPL model: what one journal projects", () => { + beforeAll(() => useTempFileCompiler()); + + it("M1: projects an empty journal as an execution with no entry", function* () { + const model = projected([]); + + expect(model.entry).toBe(undefined); + expect(model.settled).toBe(false); + expect(model.terminal).toBe(undefined); + expect(model.transcript).toEqual([]); + expect(model.checkpoints).toEqual([]); + expect(model.head).toBe(true); + }); + + it("M2: projects the reference entry into the documented scopes", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + const entry = model.entry; + if (entry === undefined) { + throw new Error("the reference journal admits an entry"); + } + + expect(entry.key).toBe(ENTRY_SCOPE); + expect(entry.kind).toBe("entry"); + expect(entry.path).toBe(""); + expect(entry.source).toContain("', + ); + + const nested = child(entry, "Checklist-1"); + expect(nested.kind).toBe("component"); + expect(nested.name).toBe("Checklist"); + expect(nested.path.endsWith("Checklist.md")).toBe(true); + expect(nested.source).toContain("{props.steps} steps remain."); + expect(nested.position?.line).toBe(12); + + const generated = child(entry, "generated-1"); + expect(generated.kind).toBe("generated"); + expect(generated.source).toBe(''); + expect(generated.position?.line).toBe(14); + + expect(entry.generated).toHaveLength(1); + expect(entry.generated[0].decision).toBe("admitted"); + + expect(entry.elicitations).toHaveLength(1); + expect(entry.elicitations[0].answer).toEqual(REFERENCE_ANSWER); + expect(entry.elicitations[0].location).toBe(":16:1"); + expect(entry.elicitations[0].schema).toEqual({ + type: "object", + properties: { decision: { type: "string", enum: ["approve", "decline"] } }, + required: ["decision"], + additionalProperties: false, + }); + + expect(model.settled).toBe(true); + expect(model.terminal?.status).toBe("ok"); + expect(model.terminal?.output).toContain("Decision: approve"); + }); + + it("M1: deep-freezes the model and reprojects the same journal identically", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + const entry = model.entry; + if (entry === undefined) { + throw new Error("the reference journal admits an entry"); + } + + expect(Object.isFrozen(model)).toBe(true); + expect(Object.isFrozen(entry)).toBe(true); + expect(Object.isFrozen(entry.bindings)).toBe(true); + expect(Object.isFrozen(entry.scopes)).toBe(true); + expect(Object.isFrozen(model.transcript)).toBe(true); + expect(Object.isFrozen(binding(entry, "plan"))).toBe(true); + expect(() => { + Object.assign(entry, { source: "" }); + }).toThrow(); + + // Reprojected from the journal's own text form, so nothing a first reading + // computed can be what the second one is agreeing with. + expect(projected(copied(events))).toEqual(model); + }); + + it("M3: holds only frozen copies, and leaves the events exactly as they were", function* () { + const events = yield* referenceEvents(); + const written = events.map((event) => serializeDurableEvent(event)); + const graph = reachable(events); + + // The events start out ordinary mutable data, which is what makes the + // assertions after the projection mean anything at all. + expect(graph.length).toBeGreaterThan(20); + expect(graph.filter((member) => Object.isFrozen(member))).toEqual([]); + + const model = projected(events); + const entry = model.entry; + if (entry === undefined) { + throw new Error("the reference journal admits an entry"); + } + + // Everything the model reaches is frozen, all the way down — a component + // that is handed a value cannot change what every other reader sees. + const held = reachable(model); + expect(held.length).toBeGreaterThan(20); + expect(held.filter((member) => !Object.isFrozen(member))).toEqual([]); + + // And none of it is an object the events still hold. Sharing one would make + // the two claims either side of this a single claim about one graph. + const borrowed = new Set(graph); + expect(held.filter((member) => borrowed.has(member))).toEqual([]); + + // Reading a history is not a way of changing it. Freezing a value in place + // would make the caller's own events immutable as a side effect of having + // been looked at. + expect(graph.filter((member) => Object.isFrozen(member))).toEqual([]); + expect(events.map((event) => serializeDurableEvent(event))).toEqual(written); + + // The copy is what the model retains, so what the caller does to its events + // afterwards cannot change what a reader is looking at. + const plan = publishedPlan(events); + expect(binding(entry, "plan")).not.toBe(plan); + plan.title = "Something else"; + expect(binding(entry, "plan")).toEqual({ title: "Ship the REPL", steps: 2 }); + expect(Object.isFrozen(plan)).toBe(false); + }); + + it("M1: a selected prefix shows only what that prefix admitted", function* () { + const events = yield* referenceEvents(); + const spelling = markers(events); + const admission = indexOfType(events, "generated_xmd"); + + const before = projected(events, spelling[admission - 1]); + const after = projected(events, spelling[admission]); + const entryBefore = before.entry; + const entryAfter = after.entry; + if (entryBefore === undefined || entryAfter === undefined) { + throw new Error("both prefixes admit the entry"); + } + + expect(before.head).toBe(false); + expect(before.settled).toBe(false); + expect(before.terminal).toBe(undefined); + expect(entryBefore.generated).toEqual([]); + expect(entryBefore.scopes.map((scope) => scope.key)).toEqual(["Checklist-1"]); + expect(entryBefore.elicitations).toEqual([]); + + expect(entryAfter.scopes.map((scope) => scope.key)).toEqual(["Checklist-1", "generated-1"]); + expect(entryAfter.elicitations).toEqual([]); + expect(after.settled).toBe(false); + + // The head is still the head: selecting a prefix reads the file, it does not + // shorten it. + expect(projected(events).settled).toBe(true); + }); + + it("M1: offers a checkpoint for each selectable position, in append order", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + + expect(model.checkpoints.map((checkpoint) => checkpoint.kind)).toEqual([ + "entry", + "binding", + "scope", + "generated", + "elicit", + "terminal", + ]); + for (const checkpoint of model.checkpoints) { + expect(projected(events, checkpoint.marker).selection).toBe(checkpoint.marker); + } + }); +}); + +describe("REPL model: the histories it refuses", () => { + beforeAll(() => useTempFileCompiler()); + + it("M1: refuses a marker this journal does not hold, rather than showing the head", function* () { + const events = yield* referenceEvents(); + + expect(refusal(events, "yield:root:99")).toContain("not in this journal"); + expect(refusal(events, "close:child")).toContain("not in this journal"); + expect(refusal(events, "")).toContain("not in this journal"); + expect(refusal([], "yield:root:0")).toContain("not in this journal"); + }); + + it("M1: refuses a second entry, work after settlement, and a repeated close", function* () { + const events = yield* referenceEvents(); + + expect(refusal([...events, events[0]])).toContain("after the entry settled"); + expect(refusal([events[0], ...events])).toContain("a second entry"); + expect(refusal([...events, events[events.length - 1]])).toContain("records"); + expect(refusal(events.slice(1))).toContain("before it admitted its entry"); + }); + + it("M1: refuses a payload it cannot read, and exposes no partial model", function* () { + const events = yield* referenceEvents(); + + expect(refusal(withValue(events, 0, "not a selection"))).toContain( + "entry's recorded source cannot be read", + ); + expect(refusal(withValue(events, indexOfType(events, "eval"), { value: 3 }))).toContain( + "published values cannot be read", + ); + expect( + refusal(withValue(events, indexOfType(events, "generated_xmd"), { decision: "maybe" })), + ).toContain("generated fragment cannot be read"); + expect( + refusal( + events.map((event) => + event.type === "close" && event.coroutineId === "root" + ? { ...event, result: { status: "ok", value: { status: "ok" } } } + : event, + ), + ), + ).toContain("recorded outcome of this entry cannot be read"); + + const projection = projectRepl(withValue(events, 0, "not a selection")); + expect(projection.ok).toBe(false); + expect("value" in projection).toBe(false); + }); + + it("M2: refuses an owning source position that was removed or points elsewhere", function* () { + const events = yield* referenceEvents(); + + for (const type of ["eval", "import_component", "generated_xmd", "elicit"]) { + const at = + type === "import_component" + ? indexOfType(events.slice(1), type) + 1 + : indexOfType(events, type); + const removed = withDescription(events, at, (description) => { + delete description["executablemd.source-position"]; + return description; + }); + expect(refusal(removed)).toContain("recorded without the source position"); + + const elsewhere = withDescription(events, at, (description) => ({ + ...description, + "executablemd.source-position": { + path: "somewhere-else.md", + offset: 0, + line: 1, + column: 1, + }, + })); + expect(refusal(elsewhere)).toContain("never admitted"); + } + }); + + it("M2: refuses a source position that will not read at all", function* () { + const events = yield* referenceEvents(); + const at = indexOfType(events, "elicit"); + + const corrupted = withDescription(events, at, (description) => ({ + ...description, + "executablemd.source-position": { path: "", offset: -1, line: 0, column: 0 }, + })); + + expect(refusal(corrupted)).toContain("cannot read"); + }); + + it("E1: refuses an answered question that retains no schema", function* () { + const events = yield* referenceEvents(); + const at = indexOfType(events, "elicit"); + + const stripped = withDescription(events, at, (description) => { + delete description["executablemd.elicitation-schema"]; + return description; + }); + + expect(refusal(stripped)).toContain("does not retain the schema"); + }); + + it("E1: a refused answer leaves no answered question in the projection", function* () { + const run = yield* runReference(answering({ decision: "maybe" })); + const model = projected(run.events); + + expect(run.asked).toHaveLength(1); + expect(model.entry?.elicitations).toEqual([]); + expect(model.transcript.some((row) => row.kind === "elicit")).toBe(false); + }); +}); diff --git a/packages/cli/tests/repl-route.test.ts b/packages/cli/tests/repl-route.test.ts new file mode 100644 index 000000000..bc7329fec --- /dev/null +++ b/packages/cli/tests/repl-route.test.ts @@ -0,0 +1,325 @@ +/** + * Where the REPL is, decoded and resolved (#848 R1, R2, M3). + * + * Two halves and they are tested as two. The codec is pure, so its tests need no + * journal at all: equivalent spellings have to decode to one route, one canonical + * spelling has to come back out, and every illegal combination has to refuse. + * Resolution is where a Journal comes in, and what it must return is the *exact* + * objects the projection holds — not copies, and never a `DurableEvent`. + */ + +import { beforeAll, describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { useTempFileCompiler } from "@executablemd/core"; +import { parseDurableEvent, serializeDurableEvent } from "@executablemd/durable-streams"; +import type { DurableEvent } from "@executablemd/durable-streams"; + +import { projectRepl } from "../src/repl/model.ts"; +import type { ReplModel } from "../src/repl/model.ts"; +import { decodeLocation, encodeLocation, resolveLocation } from "../src/repl/route.ts"; +import type { ReplRoute, ReplSelection } from "../src/repl/route.ts"; +import { referenceEvents } from "./fixtures/repl/reference.ts"; + +function decoded(location: string): ReplRoute { + const result = decodeLocation(location); + if (!result.ok) { + throw result.error; + } + return result.value; +} + +function refused(location: string): string { + const result = decodeLocation(location); + if (result.ok) { + throw new Error(`${location} decoded, and this grammar must refuse it`); + } + return result.error.message; +} + +function projected(events: readonly DurableEvent[], selection?: string): ReplModel { + const result = projectRepl(events, selection); + if (!result.ok) { + throw result.error; + } + return result.value; +} + +function resolved(model: ReplModel, location: string): ReplSelection { + const result = resolveLocation(model, decoded(location)); + if (!result.ok) { + throw result.error; + } + return result.value; +} + +function unresolved(model: ReplModel, location: string): string { + const result = resolveLocation(model, decoded(location)); + if (result.ok) { + throw new Error(`${location} resolved, and this model cannot answer it`); + } + return result.error.message; +} + +/** The journal through its own text form, so no first reading is reused. */ +function copied(events: readonly DurableEvent[]): DurableEvent[] { + return events.map((event) => { + const parsed = parseDurableEvent(serializeDurableEvent(event)); + if (!parsed.ok) { + throw parsed.error; + } + return parsed.value; + }); +} + +/** + * Whether anything reachable from a value is shaped like a journal record. + * + * Structural rather than nominal on purpose: what must not escape the model is + * the *event*, and an event is recognizable by carrying a coroutine and a + * description together however it was constructed. + */ +function holdsARecord(value: unknown, seen: Set = new Set()): boolean { + if (value === null || typeof value !== "object" || seen.has(value)) { + return false; + } + seen.add(value); + if ("coroutineId" in value && ("description" in value || "result" in value)) { + return true; + } + for (const member of Object.values(value)) { + if (holdsARecord(member, seen)) { + return true; + } + } + return false; +} + +const EXECUTION = "kf39sla2"; + +describe("REPL route: the grammar", () => { + it("R1: decodes equivalent spellings and query orders into one route", function* () { + const canonical = decoded( + `xmd://repl/${EXECUTION}/repl/entry-1/+elicit:yield:root:6?at=yield:root:6&inspect`, + ); + + expect(canonical).toEqual( + decoded( + `xmd://repl/${EXECUTION}/repl/entry%2D1/+elicit:yield%3Aroot%3A6?inspect&at=yield%3Aroot%3A6`, + ), + ); + expect(canonical.execution).toBe(EXECUTION); + expect(canonical.surface).toBe("repl"); + expect(canonical.scopes).toEqual(["entry-1"]); + expect(canonical.drawers).toEqual([{ kind: "recorded-elicit", marker: "yield:root:6" }]); + expect(canonical.at).toBe("yield:root:6"); + expect(canonical.inspect).toBe(true); + expect(canonical.draft).toBe(undefined); + }); + + it("R1: round-trips every valid surface through one canonical spelling", function* () { + const locations = [ + `xmd://repl/${EXECUTION}/sessions`, + `xmd://repl/${EXECUTION}/repl`, + `xmd://repl/${EXECUTION}/repl/entry-1`, + `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-1`, + `xmd://repl/${EXECUTION}/repl/entry-1/generated-1`, + `xmd://repl/${EXECUTION}/repl/entry-1/+history`, + `xmd://repl/${EXECUTION}/repl/entry-1/+binding:plan`, + `xmd://repl/${EXECUTION}/repl/entry-1/+elicit`, + `xmd://repl/${EXECUTION}/repl/entry-1/+history/+binding:plan`, + `xmd://repl/${EXECUTION}/repl/entry-1?at=yield:root:1`, + `xmd://repl/${EXECUTION}/repl/entry-1?at=yield:root:1&inspect`, + `xmd://repl/${EXECUTION}/repl?draft=one%20more%20line`, + ]; + + for (const location of locations) { + expect(encodeLocation(decoded(location))).toBe(location); + expect(decoded(encodeLocation(decoded(location)))).toEqual(decoded(location)); + } + }); + + it("R1: refuses what the grammar cannot say", function* () { + expect(refused("https://example.com/repl")).toContain("begins with"); + expect(refused(`xmd://repl/${EXECUTION}`)).toContain("names an execution and a surface"); + expect(refused("xmd://repl/")).toContain("names an execution and a surface"); + expect(refused(`xmd://repl/${EXECUTION}/notes`)).toContain("surface"); + expect(refused(`xmd://repl/${EXECUTION}/repl/%zz`)).toContain("escapes"); + expect(refused(`xmd://repl/${EXECUTION}/repl//entry-1`)).toContain("empty path segment"); + expect(refused(`xmd://repl/${EXECUTION}/repl/entry-1?inspect`)).toContain("needs the position"); + expect(refused(`xmd://repl/${EXECUTION}/repl/entry-1/+history/Checklist-1`)).toContain( + "scopes precede its drawers", + ); + expect(refused(`xmd://repl/${EXECUTION}/repl/entry-1/+notes`)).toContain("names a drawer"); + expect(refused(`xmd://repl/${EXECUTION}/repl/entry-1?draft=text`)).toContain("a draft is text"); + expect(refused(`xmd://repl/${EXECUTION}/repl?draft=text&at=yield:root:0`)).toContain( + "a draft is text", + ); + expect(refused(`xmd://repl/${EXECUTION}/sessions/entry-1`)).toContain("Sessions surface"); + expect(refused(`xmd://repl/${EXECUTION}/repl?pause=1`)).toContain("query"); + expect(refused(`xmd://repl/${EXECUTION}/repl#top`)).toContain("no fragment"); + expect(refused("xmd://repl/../repl")).toContain("opaque identifier"); + expect(refused("xmd://repl/%2E%2E/repl")).toContain("opaque identifier"); + }); + + it("R1: cannot spell a route the grammar would refuse", function* () { + const route = decoded(`xmd://repl/${EXECUTION}/repl/entry-1?at=yield:root:1&inspect`); + + expect(() => encodeLocation({ ...route, at: undefined })).toThrow(); + expect(() => encodeLocation({ ...route, execution: "../escape" })).toThrow(); + expect(() => + encodeLocation({ ...route, at: undefined, inspect: false, draft: "typing" }), + ).toThrow(); + expect(() => + encodeLocation({ ...route, surface: "sessions", at: undefined, inspect: false }), + ).toThrow(); + }); +}); + +describe("REPL route: resolving against one model", () => { + beforeAll(() => useTempFileCompiler()); + + it("M3: returns the exact objects the model holds", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + const entry = model.entry; + if (entry === undefined) { + throw new Error("the reference journal admits an entry"); + } + const nested = entry.scopes.find((scope) => scope.key === "Checklist-1"); + const plan = entry.bindings.find((candidate) => candidate.name === "plan"); + + const head = resolved(model, `xmd://repl/${EXECUTION}/repl/entry-1`); + expect(head.entry).toBe(entry); + expect(head.scope).toBe(entry); + expect(head.ancestry).toEqual([entry]); + + const inner = resolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-1`); + expect(inner.scope).toBe(nested); + expect(inner.ancestry.length).toBe(2); + expect(inner.ancestry[0]).toBe(entry); + + const drawer = resolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/+binding:plan`); + expect(drawer.drawers).toHaveLength(1); + const opened = drawer.drawers[0]; + if (opened.kind !== "binding") { + throw new Error("the binding drawer opens a binding"); + } + expect(opened.binding).toBe(plan); + + expect(holdsARecord(inner)).toBe(false); + expect(holdsARecord(drawer)).toBe(false); + }); + + it("M3: the same view results from a journal read a second time", function* () { + const events = yield* referenceEvents(); + const location = `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-1`; + + const first = resolved(projected(events), location); + const second = resolved(projected(copied(events)), location); + + expect(second).toEqual(first); + // The same reading, and not the same objects: two projections are two + // readings of one file, which is what makes the model discardable. + expect(second.scope).not.toBe(first.scope); + }); + + it("R2: resolves a historical prefix and refuses a live question inside it", function* () { + const events = yield* referenceEvents(); + const head = projected(events); + const answered = head.entry?.elicitations[0]; + if (answered === undefined) { + throw new Error("the reference journal records one answered question"); + } + const historical = projected(events, answered.marker); + + const inspecting = resolved( + historical, + `xmd://repl/${EXECUTION}/repl/entry-1/+elicit:${answered.marker}` + + `?at=${answered.marker}&inspect`, + ); + const drawer = inspecting.drawers[0]; + if (drawer.kind !== "recorded-elicit") { + throw new Error("an inspected question opens its recorded answer"); + } + expect(drawer.elicitation).toBe(historical.entry?.elicitations[0]); + + expect( + unresolved( + historical, + `xmd://repl/${EXECUTION}/repl/entry-1/+elicit?at=${answered.marker}&inspect`, + ), + ).toContain("live question"); + expect(resolved(head, `xmd://repl/${EXECUTION}/repl/entry-1/+elicit`).drawers[0].kind).toBe( + "live-elicit", + ); + }); + + it("R2: refuses a missing entry, scope, binding or incomplete path", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-2`)).toContain("no entry-2"); + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/Checklist-1`)).toContain( + "no Checklist-1", + ); + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-2`)).toContain( + "holds no Checklist-2", + ); + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/+binding:absent`)).toContain( + "publishes no absent", + ); + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/+binding:plan`)).toContain( + "scope that published the name", + ); + expect( + unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/+elicit:yield:root:1`), + ).toContain("recorded no question at"); + }); + + it("R2: a route selecting another prefix than the model refuses rather than answering", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-1?at=yield:root:1`)).toContain( + "was not projected at", + ); + }); + + it("R2: a refused navigation leaves the resolved view it was asked from untouched", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + const standing = resolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-1`); + const before = { ...standing, ancestry: [...standing.ancestry] }; + + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl/entry-1/Checklist-9`)).toContain( + "holds no Checklist-9", + ); + + expect(standing.scope).toBe(before.scope); + expect([...standing.ancestry]).toEqual(before.ancestry); + expect(Object.isFrozen(model)).toBe(true); + }); + + it("R2: the empty Sessions surface selects nothing at all", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + + const sessions = resolved(model, `xmd://repl/${EXECUTION}/sessions`); + expect(sessions.surface).toBe("sessions"); + expect(sessions.entry).toBe(undefined); + expect(sessions.scope).toBe(undefined); + expect(sessions.drawers).toEqual([]); + }); + + it("R2: refuses a draft against an execution that has admitted its entry", function* () { + const events = yield* referenceEvents(); + const model = projected(events); + + expect(unresolved(model, `xmd://repl/${EXECUTION}/repl?draft=another%20entry`)).toContain( + "already admitted its entry", + ); + expect(resolved(projected([]), `xmd://repl/${EXECUTION}/repl?draft=first%20entry`).entry).toBe( + undefined, + ); + }); +}); diff --git a/packages/core/host.ts b/packages/core/host.ts index 738e5a22b..7aa337320 100644 --- a/packages/core/host.ts +++ b/packages/core/host.ts @@ -206,6 +206,16 @@ export type { IdentityClaimant, IdentityComponent } from "./src/invocation-ident * the way the pinned generated identities take core's definitions. */ export { props as elicitProps, returns as elicitReturns } from "./src/components/Elicit.ts"; +/** + * What a recorded elicitation retains beside its answer, for a host that reads + * history back. + * + * The field name and the reader travel together so that a host displaying an + * answered question reads the same shape core wrote, and reads it by parsing: + * the description is journal data, and a schema that merely looked plausible + * would reach a form as one. + */ +export { ELICITATION_SCHEMA_FIELD, readElicitationSchema } from "./src/elicit-journal.ts"; export { WorkflowBundleError } from "./src/components/bundle.ts"; export type { WorkflowBundleComponent, WorkflowComponentBundle } from "./src/components/bundle.ts"; diff --git a/packages/core/src/components/Elicit.ts b/packages/core/src/components/Elicit.ts index b3906a39c..8f190e74d 100644 --- a/packages/core/src/components/Elicit.ts +++ b/packages/core/src/components/Elicit.ts @@ -81,6 +81,7 @@ export default function* Elicit(props: Record): Operation { const identity = { location: formatLocation(expansion), fingerprint: questionFingerprint({ schema: prepared.schema, message }), + schema: prepared.schema, position: expansion.position, }; diff --git a/packages/core/src/elicit-journal.ts b/packages/core/src/elicit-journal.ts index 383dd22ee..668384c92 100644 --- a/packages/core/src/elicit-journal.ts +++ b/packages/core/src/elicit-journal.ts @@ -1,15 +1,18 @@ /** * What an elicitation leaves behind. * - * Only the validated response is journaled. Which provider was installed, what - * it opened, how long a person took — all of it belongs to the run that happened - * to ask, and a replay that restored any of it would describe an interaction - * that is over. + * The validated response is the answer, and it is the record's result. Beside it + * the description retains the question's normalized schema — descriptive input, + * never identity — so a reader of the history knows which fields the person was + * shown without asking a provider that is no longer running. Which provider was + * installed, what it opened, how long a person took, and what the form looked + * like all belong to the run that happened to ask, and a replay that restored + * any of it would describe an interaction that is over. * - * The description carries a fingerprint of what the person was actually asked: - * the normalized schema and the rendered message. This module decides what a - * mismatch means, because nothing else does — only `type` and `name` decide - * whether a journal entry matches, and neither of those changes when the + * The description also carries a fingerprint of what the person was actually + * asked: the normalized schema and the rendered message. This module decides + * what a mismatch means, because nothing else does — only `type` and `name` + * decide whether a journal entry matches, and neither of those changes when the * question does. A recorded answer whose fingerprint is not this run's is * refused with a `StaleInputError` rather than bound: binding an answer to a * question nobody was given is the failure a human-decision component must not @@ -26,16 +29,32 @@ import { ReplayGuard, StaleInputError, } from "@executablemd/durable-streams"; -import type { Json as DurableJson, Workflow } from "@executablemd/durable-streams"; +import type { + EffectDescription, + Json as DurableJson, + Workflow, +} from "@executablemd/durable-streams"; import type { Operation } from "effection"; import { canonicalFingerprint } from "./canonical.ts"; -import { parseJson } from "./json.ts"; +import { isJsonObject, parseJson } from "./json.ts"; import { sourceDescription } from "./source-position.ts"; import type { Json, JsonObject, SourcePosition } from "./types.ts"; const ELICIT = "elicit"; +/** + * The description field the question's normalized schema occupies. + * + * Beside `type` and `name` rather than in either, under one stable namespaced + * field, because a durable effect's identity is its type and its name and + * nothing else: a schema that travelled in one would change what a replay + * matches. It is stored descriptive input — never compared during divergence + * detection, never part of admission — and the fingerprint over schema and + * message remains the guard. + */ +export const ELICITATION_SCHEMA_FIELD = "executablemd.elicitation-schema"; + export interface ElicitationQuestion { schema: JsonObject; message: string; @@ -46,6 +65,8 @@ export interface ElicitationIdentity { /** `path:line:column` — the durable name. */ location: string; fingerprint: string; + /** The normalized schema, retained so history can show what was asked. */ + schema: JsonObject; /** Where the element was written, for history. Never read back as identity. */ position?: Readonly; } @@ -59,6 +80,19 @@ export function questionFingerprint(question: ElicitationQuestion): string { return canonicalFingerprint({ schema: question.schema, message: question.message }); } +/** + * The normalized schema a recorded elicitation retains, or none. + * + * Parsed rather than asserted: a description is journal data, so a field that + * is absent or is not a JSON object answers "none" instead of reaching a reader + * as a schema it is not. A record written before the field existed has none, + * and that is the same answer. + */ +export function readElicitationSchema(description: EffectDescription): JsonObject | undefined { + const field = description[ELICITATION_SCHEMA_FIELD]; + return isJsonObject(field) ? field : undefined; +} + /** * Refuse to replay an answer to a different question. * @@ -119,6 +153,7 @@ export function* persistElicitation( type: ELICIT, name: recordName(identity.location), input: identity.fingerprint, + [ELICITATION_SCHEMA_FIELD]: identity.schema, ...sourceDescription(identity.position), }, function* (): Operation { diff --git a/packages/core/tests/elicit-component.test.ts b/packages/core/tests/elicit-component.test.ts index 74e8c8557..23df48b2b 100644 --- a/packages/core/tests/elicit-component.test.ts +++ b/packages/core/tests/elicit-component.test.ts @@ -16,11 +16,13 @@ import { ensure, race, resource, scoped, sleep, suspend, until } from "effection import type { Operation } from "effection"; import { exists, rm, writeTextFile } from "@effectionx/fs"; import { InMemoryStream } from "@executablemd/durable-streams"; +import type { Json, Yield } from "@executablemd/durable-streams"; import { mkdtemp, realpath } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { collect } from "../src/collect.ts"; +import { ELICITATION_SCHEMA_FIELD, readElicitationSchema } from "../src/elicit-journal.ts"; import { prepareElicitation, runPreparedElicitation } from "../src/elicit.ts"; import { Elicitation } from "../src/elicitation-api.ts"; import type { ElicitationRequest } from "../src/elicitation-api.ts"; @@ -563,8 +565,227 @@ describe("Elicit: durability", () => { expect(again.failure).toBe(undefined); expect(again.output).toContain("Decision: approve"); }); + + /** + * The answer is the result; the compiled schema is description beside it. + * + * A host reading the history back — the REPL's read-only drawer is the one + * this exists for (#848) — has to know which fields the person was shown, and + * the provider that could have said so is not running any more. So the schema + * is retained where a position is retained: in the description, under its own + * namespaced field, where it cannot change what a replay matches. + */ + it("retains the compiled schema in the description and the answer as the result", function* () { + const workspace = yield* useWorkspace(); + const stream = new InMemoryStream(); + + const first = yield* run( + workspace, + document("Approve?"), + constant({ decision: "approve" }), + stream, + ); + + expect(first.failure).toBe(undefined); + const records = yield* elicitRecords(stream); + expect(records).toHaveLength(1); + expect(records[0].description.type).toBe("elicit"); + expect(records[0].description[ELICITATION_SCHEMA_FIELD]).toEqual(JSON.parse(DECISION_SCHEMA)); + expect(readElicitationSchema(records[0].description)).toEqual(JSON.parse(DECISION_SCHEMA)); + expect(records[0].result).toEqual({ status: "ok", value: { decision: "approve" } }); + }); + + /** + * Retaining the schema does not make it identity. + * + * `type` and `name` still decide whether an entry matches, so the guard is + * still what stands between a resumed run and an answer to a question nobody + * was asked. Reached here by changing the schema the recorded eval block + * published, which is the one way this run asks a different question at the + * same position: the root source replays from the record, so editing the file + * cannot. + */ + it("refuses a recorded answer when the schema changed at the same position", function* () { + const workspace = yield* useWorkspace(); + const stream = new InMemoryStream(); + const source = document("Approve?"); + + yield* run(workspace, source, constant({ decision: "approve" }), stream); + + const doctored = yield* run( + workspace, + source, + // deno-lint-ignore require-yield + function* () { + throw new Error("the provider was contacted"); + }, + yield* withRecordedSchema(stream, DECIDE_SCHEMA), + ); + + expect(doctored.failure?.message).toContain("given to a different question"); + }); + + /** + * The same guard, reached from the other half of the fingerprint. + * + * The rendered message is what the person read, and the recorded root source + * is where it comes from on a resumed run. The replacement keeps the byte + * count, so every source position in the document is the one that was + * recorded and only the question changed. + */ + it("refuses a recorded answer when the message changed at the same position", function* () { + const workspace = yield* useWorkspace(); + const stream = new InMemoryStream(); + + yield* run(workspace, document("Approve?"), constant({ decision: "approve" }), stream); + + const doctored = yield* run( + workspace, + document("Approve?"), + // deno-lint-ignore require-yield + function* () { + throw new Error("the provider was contacted"); + }, + yield* withRecordedSource(stream, "Approve?", "Reject!!"), + ); + + expect(doctored.failure?.message).toContain("given to a different question"); + }); + + /** + * An answer the schema rejects binds nothing and is never restorable. + * + * What the record holds is the failure, not a value: the validation runs + * inside the durable executor, so the entry settles `err` and there is no + * answer in the journal for a later run to find. The document fails with it + * and nothing downstream of the question renders. + */ + it("records no answer when the provider's answer fails its schema", function* () { + const workspace = yield* useWorkspace(); + const stream = new InMemoryStream(); + + const rejected = yield* run( + workspace, + document("Approve?"), + constant({ decision: "maybe" }), + stream, + ); + + expect(rejected.output).not.toContain("Decision:"); + const records = yield* elicitRecords(stream); + expect(records).toHaveLength(1); + expect(records[0].result.status).toBe("err"); + expect("value" in records[0].result).toBe(false); + }); }); +/** The recorded elicitations, in append order. */ +function* elicitRecords(stream: InMemoryStream): Operation { + const records: Yield[] = []; + for (const event of yield* stream.readAll()) { + if (event.type === "yield" && event.description.type === "elicit") { + records.push(event); + } + } + return records; +} + +/** A second schema for the same answer, so the question changes and the shape does not. */ +const DECIDE_SCHEMA = + '{"type":"object","properties":{"decision":{"type":"string","enum":["approve","decline"]}},' + + '"required":["decision"],"additionalProperties":false}'; + +/** + * The same journal, with the schema the recorded eval block published replaced. + * + * A resumed run restores the eval block's exports rather than running it, so + * this is what "the author changed the schema" looks like from the inside — and + * unlike rewriting the fingerprint, the run really does compute a different + * question at the same position. + */ +function* withRecordedSchema(stream: InMemoryStream, schema: string): Operation { + const events = yield* stream.readAll(); + return new InMemoryStream( + events + .filter((event) => !(event.type === "close" && event.coroutineId === "root")) + .map((event) => { + if (event.type !== "yield" || event.description.type !== "eval") { + return event; + } + if (event.result.status !== "ok" || !isRecordedExports(event.result.value)) { + return event; + } + return { + ...event, + result: { + status: "ok", + value: { value: { ...event.result.value.value, responseSchema: JSON.parse(schema) } }, + }, + }; + }), + ); +} + +/** Whether a recorded eval result carries the exports object this document publishes. */ +function isRecordedExports(value: Json | undefined): value is { value: { [key: string]: Json } } { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return false; + } + const exports = value["value"]; + return exports !== null && typeof exports === "object" && !Array.isArray(exports); +} + +/** + * The same journal, with one equal-length substitution inside the recorded root + * source. + * + * Equal length on purpose: every offset, line and column in the document stays + * what was recorded, so the only thing this run computes differently is the + * rendered message. + */ +function* withRecordedSource( + stream: InMemoryStream, + from: string, + to: string, +): Operation { + if (from.length !== to.length) { + throw new Error("a source substitution must preserve every position it is not about"); + } + const events = yield* stream.readAll(); + return new InMemoryStream( + events + .filter((event) => !(event.type === "close" && event.coroutineId === "root")) + .map((event) => { + if (event.type !== "yield" || event.description.type !== "import_component") { + return event; + } + if (event.result.status !== "ok" || !isRecordedContent(event.result.value)) { + return event; + } + return { + ...event, + result: { + status: "ok", + value: { + ...event.result.value, + content: event.result.value.content.split(from).join(to), + }, + }, + }; + }), + ); +} + +/** Whether a recorded import retained the source it read. */ +function isRecordedContent( + value: Json | undefined, +): value is { [key: string]: Json; content: string } { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + return false; + } + return typeof value["content"] === "string"; +} + /** * The same journal, with the elicitation's recorded question replaced. * diff --git a/specs/executable-mdx-spec.md b/specs/executable-mdx-spec.md index 5554b6079..df5e786b0 100644 --- a/specs/executable-mdx-spec.md +++ b/specs/executable-mdx-spec.md @@ -8795,13 +8795,18 @@ validated answer binds into the evaluation environment. A `$ref` that leaves the supplied schema is refused with its position; only self-contained references resolve today (#192). -**Durability.** Only the validated answer is journaled, keyed by a fingerprint of -the compiled schema and the rendered message. Preflight is not what replay -skips: compiling and expanding the content run on every execution, replay -included, because they are how a run knows which recorded answer it is looking -for. What replay never repeats is the provider call and the interaction it -stands for. A recorded answer whose question does not match the one this run -computed is refused rather than bound. +**Durability.** The validated answer is the `elicit` record's result, keyed by a +fingerprint of the compiled schema and the rendered message. The compiled schema +travels beside it in the same description, under the namespaced +`executablemd.elicitation-schema` field, so a host reading the history back knows +which fields the person was shown without a provider still being installed. It is +descriptive input: only `type` and `name` identify the record, the fingerprint +stays the guard, and no provider, form, or presentation state is journaled at +all. Preflight is not what replay skips: compiling and expanding the content run +on every execution, replay included, because they are how a run knows which +recorded answer it is looking for. What replay never repeats is the provider call +and the interaction it stands for. A recorded answer whose question does not match +the one this run computed is refused rather than bound. **Under a workflow run the durability is different, and the Markdown is not.** `xmd workflow` resolves `Elicit` to a registration of its own, installed by the diff --git a/test-weights.json b/test-weights.json index ea0914639..bf6588e8c 100644 --- a/test-weights.json +++ b/test-weights.json @@ -1,8 +1,8 @@ { "version": 1, "source": { - "commit": "752e8c28d68fd47cbc7fa5cb959b4e8a9c07a223", - "runUrl": "https://github.com/taras/executable.md/actions/runs/35671233627", + "commit": "970bc0db65af9d151c3e94eea06dadcc4661294f", + "runUrl": "https://github.com/taras/executable.md/actions/runs/36270414251", "attempt": 1, "runner": "ubuntu-latest", "deno": "deno 2.9.5 (stable, release, x86_64-unknown-linux-gnu)", @@ -11,1069 +11,1079 @@ }, "runtimes": { "deno": { - "packages/acp/tests/acceptance.test.ts": 5131, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 7963, - "packages/acp/tests/acpx-materialization.test.ts": 8155, - "packages/acp/tests/acpx-transient-env.test.ts": 1789, - "packages/acp/tests/adapter-materialization.test.ts": 74800, - "packages/acp/tests/adapter-protocol.test.ts": 57937, - "packages/acp/tests/codex-surfaces.test.ts": 1770, - "packages/acp/tests/loaded-copy.test.ts": 4463, - "packages/acp/tests/native-launch.test.ts": 5125, - "packages/acp/tests/permission-bridge.test.ts": 3950, - "packages/acp/tests/provider.test.ts": 5062, - "packages/acp/tests/serial-queue.test.ts": 1519, - "packages/acp/tests/session-key.test.ts": 3793, - "packages/acp/tests/session-route.test.ts": 3941, - "packages/acp/tests/terminal-screen.test.ts": 1685, - "packages/cli/tests/agent-adapters.test.ts": 5375, - "packages/cli/tests/agent-cli.test.ts": 38007, - "packages/cli/tests/agent-config.test.ts": 3564, - "packages/cli/tests/agent-options-cli.test.ts": 15152, - "packages/cli/tests/agent-options.test.ts": 3665, - "packages/cli/tests/agent-session-coordinator.test.ts": 4359, - "packages/cli/tests/cli-help.test.ts": 16664, - "packages/cli/tests/command.test.ts": 10352, - "packages/cli/tests/compiled-upgrade.test.ts": 10838, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 8617, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 118410, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 7801, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 11249, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 9498, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 14446, - "packages/cli/tests/evaluate-component.test.ts": 11295, - "packages/cli/tests/evaluate-workflow.test.ts": 5245, - "packages/cli/tests/fetch-cli.test.ts": 3735, - "packages/cli/tests/file-stream.test.ts": 1746, - "packages/cli/tests/github-zero-effect.test.ts": 7007, - "packages/cli/tests/inline-cli.test.ts": 32889, - "packages/cli/tests/launch-timeout.test.ts": 11621, - "packages/cli/tests/packaged-document.test.ts": 4837, - "packages/cli/tests/plan-args.test.ts": 1534, - "packages/cli/tests/plan-cli.test.ts": 97842, - "packages/cli/tests/plan-command-document.test.ts": 9924, - "packages/cli/tests/plan-component.test.ts": 11952, - "packages/cli/tests/plan-host-acts.test.ts": 5458, - "packages/cli/tests/plan.test.ts": 15985, - "packages/cli/tests/plugin-cli.test.ts": 40293, - "packages/cli/tests/plugin-host.test.ts": 5870, - "packages/cli/tests/plugin-modules.test.ts": 1521, - "packages/cli/tests/plugin-selection.test.ts": 1542, - "packages/cli/tests/process-retention.test.ts": 5312, - "packages/cli/tests/props-cli.test.ts": 29856, - "packages/cli/tests/props-schema.test.ts": 3942, - "packages/cli/tests/props-sources.test.ts": 2163, - "packages/cli/tests/pull-request-read-fork.test.ts": 6961, - "packages/cli/tests/run-composition-deno.test.ts": 7705, - "packages/cli/tests/run-composition-nested.test.ts": 20540, - "packages/cli/tests/run-composition.test.ts": 6754, - "packages/cli/tests/run-deadline.test.ts": 8160, - "packages/cli/tests/run-profile.test.ts": 4163, - "packages/cli/tests/run-timeouts.test.ts": 13112, - "packages/cli/tests/secret-detection-cli.test.ts": 18965, - "packages/cli/tests/service-document.test.ts": 5106, - "packages/cli/tests/service-host.test.ts": 5019, - "packages/cli/tests/session-launch-cli.test.ts": 10389, - "packages/cli/tests/stdin-cli.test.ts": 35329, - "packages/cli/tests/stdout-delivery.test.ts": 1494, - "packages/cli/tests/syntax-cli.test.ts": 34725, - "packages/cli/tests/targets-cli.test.ts": 27379, - "packages/cli/tests/test-root-profile.test.ts": 7087, - "packages/cli/tests/test-target.test.ts": 35593, - "packages/cli/tests/testing-activation.test.ts": 4325, - "packages/cli/tests/testing-execution-host.test.ts": 53886, - "packages/cli/tests/upgrade-cli.test.ts": 20246, - "packages/cli/tests/upgrade-command-document.test.ts": 6895, - "packages/cli/tests/upgrade-output.test.ts": 4274, - "packages/cli/tests/value-root.test.ts": 16920, - "packages/cli/tests/verbose-component.test.ts": 4755, - "packages/cli/tests/workflow-agent-sessions.test.ts": 18641, - "packages/cli/tests/workflow-agent.test.ts": 15264, - "packages/cli/tests/workflow-cli.test.ts": 105300, - "packages/cli/tests/workflow-crash.test.ts": 80047, - "packages/cli/tests/workflow-declaration.test.ts": 4428, - "packages/cli/tests/workflow-export-publication.test.ts": 17287, - "packages/cli/tests/workflow-fetch.test.ts": 8042, - "packages/cli/tests/workflow-fork.test.ts": 83994, - "packages/cli/tests/workflow-host.test.ts": 14083, - "packages/cli/tests/workflow-inspection.test.ts": 114635, - "packages/cli/tests/workflow-installation.test.ts": 6000, - "packages/cli/tests/workflow-lifecycle-control.test.ts": 20318, - "packages/cli/tests/workflow-retention.test.ts": 22951, - "packages/cli/tests/workflow-suspension.test.ts": 7949, - "packages/code-review-agent/tests/doctor.test.ts": 1517, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1605, - "packages/code-review-agent/tests/parse-diff.test.ts": 1544, - "packages/code-review-agent/tests/parse-doctor.test.ts": 3980, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 1513, - "packages/code-review-agent/tests/policy.test.ts": 1547, - "packages/core/tests/agent-api.test.ts": 1560, - "packages/core/tests/agent-components.test.ts": 4469, - "packages/core/tests/agent-function-components.test.ts": 5709, - "packages/core/tests/agent-session-launch.test.ts": 6308, - "packages/core/tests/agent-session-placement.test.ts": 1570, - "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1623, - "packages/core/tests/agent-session-use.test.ts": 1551, - "packages/core/tests/agent-stack.test.ts": 4022, - "packages/core/tests/answer-identity.test.ts": 3424, - "packages/core/tests/answers-component.test.ts": 4669, - "packages/core/tests/answers-expansion-recursion.test.ts": 3693, - "packages/core/tests/capture-errors.test.ts": 3986, - "packages/core/tests/capture-props.test.ts": 3682, - "packages/core/tests/cli-journal.test.ts": 11916, - "packages/core/tests/code-block-component.test.ts": 5331, - "packages/core/tests/compiler-boundary.test.ts": 3842, - "packages/core/tests/component-api.test.ts": 3512, - "packages/core/tests/component-registration.test.ts": 4565, - "packages/core/tests/component-returns.test.ts": 5057, - "packages/core/tests/component-schema-conformance.test.ts": 3845, - "packages/core/tests/config-api.test.ts": 3931, - "packages/core/tests/construct-error-observation.test.ts": 3967, - "packages/core/tests/contextual-cwd.test.ts": 5048, - "packages/core/tests/daemon-integration.test.ts": 4559, - "packages/core/tests/daemon.test.ts": 3623, - "packages/core/tests/declared-markdown-component.test.ts": 5408, - "packages/core/tests/document-output-api.test.ts": 1483, - "packages/core/tests/document-target-execution.test.ts": 6730, - "packages/core/tests/document-targets.test.ts": 3963, - "packages/core/tests/document-validation.test.ts": 5240, - "packages/core/tests/documentation-index.test.ts": 3670, - "packages/core/tests/each.test.ts": 3921, - "packages/core/tests/elicit-component.test.ts": 4384, - "packages/core/tests/ephemeral-service.test.ts": 4355, - "packages/core/tests/eval-bindings.test.ts": 3925, - "packages/core/tests/eval-context.test.ts": 2192, - "packages/core/tests/eval-durable.test.ts": 3895, - "packages/core/tests/eval-error-mode.test.ts": 4201, - "packages/core/tests/eval-interpolate.test.ts": 1508, - "packages/core/tests/eval-middleware.test.ts": 3571, - "packages/core/tests/eval-persist.test.ts": 4034, - "packages/core/tests/eval-return.test.ts": 3968, - "packages/core/tests/eval-scope.test.ts": 3790, - "packages/core/tests/eval-timeout.test.ts": 3896, - "packages/core/tests/eval-transform.test.ts": 3533, - "packages/core/tests/evaluate-component.test.ts": 5874, - "packages/core/tests/evaluate-loaded-copy.test.ts": 4052, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 4032, - "packages/core/tests/evaluation-profile.test.ts": 3659, - "packages/core/tests/exec-timeout.test.ts": 3913, - "packages/core/tests/execute.test.ts": 4419, - "packages/core/tests/execution-protocol.test.ts": 5233, - "packages/core/tests/expand.test.ts": 4451, - "packages/core/tests/expansion-identity.test.ts": 4427, - "packages/core/tests/expansion-metadata.test.ts": 3763, - "packages/core/tests/expression-props.test.ts": 4423, - "packages/core/tests/fail-component.test.ts": 4118, - "packages/core/tests/failure-printing.test.ts": 4100, - "packages/core/tests/fatal-cause.test.ts": 3714, - "packages/core/tests/fetch-component.test.ts": 5257, - "packages/core/tests/file-component.test.ts": 5729, - "packages/core/tests/file-delete-component.test.ts": 3996, - "packages/core/tests/files-fatal.test.ts": 4727, - "packages/core/tests/foreground-exec.test.ts": 4759, - "packages/core/tests/frontmatter.test.ts": 3476, - "packages/core/tests/function-components.test.ts": 4406, - "packages/core/tests/generated-composition.test.ts": 4356, - "packages/core/tests/generated-xmd.test.ts": 5679, - "packages/core/tests/glob-api.test.ts": 1854, - "packages/core/tests/glob-component.test.ts": 4516, - "packages/core/tests/guarded-journal.test.ts": 3915, - "packages/core/tests/has-content.test.ts": 3678, - "packages/core/tests/heal.test.ts": 3775, + "packages/acp/tests/acceptance.test.ts": 5839, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 8224, + "packages/acp/tests/acpx-materialization.test.ts": 8230, + "packages/acp/tests/acpx-transient-env.test.ts": 1903, + "packages/acp/tests/adapter-materialization.test.ts": 78341, + "packages/acp/tests/adapter-protocol.test.ts": 61385, + "packages/acp/tests/codex-surfaces.test.ts": 1845, + "packages/acp/tests/loaded-copy.test.ts": 4644, + "packages/acp/tests/native-launch.test.ts": 5341, + "packages/acp/tests/permission-bridge.test.ts": 4144, + "packages/acp/tests/provider.test.ts": 5302, + "packages/acp/tests/serial-queue.test.ts": 1591, + "packages/acp/tests/session-key.test.ts": 4024, + "packages/acp/tests/session-route.test.ts": 4229, + "packages/acp/tests/terminal-screen.test.ts": 1739, + "packages/cli/tests/agent-adapters.test.ts": 5691, + "packages/cli/tests/agent-cli.test.ts": 37037, + "packages/cli/tests/agent-config.test.ts": 3652, + "packages/cli/tests/agent-options-cli.test.ts": 14922, + "packages/cli/tests/agent-options.test.ts": 3690, + "packages/cli/tests/agent-session-coordinator.test.ts": 4421, + "packages/cli/tests/cli-help.test.ts": 16547, + "packages/cli/tests/command.test.ts": 10499, + "packages/cli/tests/compiled-upgrade.test.ts": 10895, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 8984, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 120796, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 8171, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 11879, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 10382, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 15516, + "packages/cli/tests/evaluate-component.test.ts": 11990, + "packages/cli/tests/evaluate-workflow.test.ts": 5745, + "packages/cli/tests/fetch-cli.test.ts": 4077, + "packages/cli/tests/file-stream.test.ts": 1875, + "packages/cli/tests/github-zero-effect.test.ts": 7823, + "packages/cli/tests/inline-cli.test.ts": 34217, + "packages/cli/tests/launch-timeout.test.ts": 11635, + "packages/cli/tests/packaged-document.test.ts": 4927, + "packages/cli/tests/plan-args.test.ts": 1601, + "packages/cli/tests/plan-cli.test.ts": 97642, + "packages/cli/tests/plan-command-document.test.ts": 10256, + "packages/cli/tests/plan-component.test.ts": 13156, + "packages/cli/tests/plan-host-acts.test.ts": 5855, + "packages/cli/tests/plan.test.ts": 18001, + "packages/cli/tests/plugin-cli.test.ts": 42399, + "packages/cli/tests/plugin-host.test.ts": 6473, + "packages/cli/tests/plugin-modules.test.ts": 1594, + "packages/cli/tests/plugin-selection.test.ts": 1591, + "packages/cli/tests/process-retention.test.ts": 5438, + "packages/cli/tests/props-cli.test.ts": 31284, + "packages/cli/tests/props-schema.test.ts": 4205, + "packages/cli/tests/props-sources.test.ts": 2386, + "packages/cli/tests/pull-request-read-fork.test.ts": 7395, + "packages/cli/tests/repl-model.test.ts": 4828, + "packages/cli/tests/repl-route.test.ts": 4551, + "packages/cli/tests/run-composition-deno.test.ts": 8218, + "packages/cli/tests/run-composition-nested.test.ts": 21153, + "packages/cli/tests/run-composition.test.ts": 7248, + "packages/cli/tests/run-deadline.test.ts": 8863, + "packages/cli/tests/run-profile.test.ts": 4477, + "packages/cli/tests/run-timeouts.test.ts": 13214, + "packages/cli/tests/secret-detection-cli.test.ts": 19517, + "packages/cli/tests/service-document.test.ts": 5444, + "packages/cli/tests/service-host.test.ts": 5339, + "packages/cli/tests/session-launch-cli.test.ts": 11002, + "packages/cli/tests/stdin-cli.test.ts": 38205, + "packages/cli/tests/stdout-delivery.test.ts": 1606, + "packages/cli/tests/syntax-cli.test.ts": 37659, + "packages/cli/tests/targets-cli.test.ts": 29397, + "packages/cli/tests/test-root-profile.test.ts": 7384, + "packages/cli/tests/test-target.test.ts": 38304, + "packages/cli/tests/testing-activation.test.ts": 4625, + "packages/cli/tests/testing-execution-host.test.ts": 56411, + "packages/cli/tests/upgrade-cli.test.ts": 20868, + "packages/cli/tests/upgrade-command-document.test.ts": 7369, + "packages/cli/tests/upgrade-output.test.ts": 4579, + "packages/cli/tests/value-root.test.ts": 17528, + "packages/cli/tests/verbose-component.test.ts": 5099, + "packages/cli/tests/workflow-agent-sessions.test.ts": 20047, + "packages/cli/tests/workflow-agent.test.ts": 15992, + "packages/cli/tests/workflow-cli.test.ts": 107664, + "packages/cli/tests/workflow-crash.test.ts": 81624, + "packages/cli/tests/workflow-declaration.test.ts": 4630, + "packages/cli/tests/workflow-export-publication.test.ts": 17345, + "packages/cli/tests/workflow-fetch.test.ts": 8141, + "packages/cli/tests/workflow-fork.test.ts": 84509, + "packages/cli/tests/workflow-host.test.ts": 14571, + "packages/cli/tests/workflow-inspection.test.ts": 114038, + "packages/cli/tests/workflow-installation.test.ts": 6108, + "packages/cli/tests/workflow-lifecycle-control.test.ts": 20507, + "packages/cli/tests/workflow-retention.test.ts": 22970, + "packages/cli/tests/workflow-suspension.test.ts": 8096, + "packages/code-review-agent/tests/doctor.test.ts": 1572, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1615, + "packages/code-review-agent/tests/parse-diff.test.ts": 1591, + "packages/code-review-agent/tests/parse-doctor.test.ts": 4079, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 1549, + "packages/code-review-agent/tests/policy.test.ts": 1610, + "packages/core/tests/agent-api.test.ts": 1583, + "packages/core/tests/agent-components.test.ts": 4712, + "packages/core/tests/agent-function-components.test.ts": 6006, + "packages/core/tests/agent-session-launch.test.ts": 6693, + "packages/core/tests/agent-session-placement.test.ts": 1626, + "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1677, + "packages/core/tests/agent-session-use.test.ts": 1601, + "packages/core/tests/agent-stack.test.ts": 4277, + "packages/core/tests/answer-identity.test.ts": 3624, + "packages/core/tests/answers-component.test.ts": 4853, + "packages/core/tests/answers-expansion-recursion.test.ts": 3822, + "packages/core/tests/capture-errors.test.ts": 4145, + "packages/core/tests/capture-props.test.ts": 3822, + "packages/core/tests/cli-journal.test.ts": 12147, + "packages/core/tests/code-block-component.test.ts": 5427, + "packages/core/tests/compiler-boundary.test.ts": 3840, + "packages/core/tests/component-api.test.ts": 3689, + "packages/core/tests/component-registration.test.ts": 4654, + "packages/core/tests/component-returns.test.ts": 5160, + "packages/core/tests/component-schema-conformance.test.ts": 3997, + "packages/core/tests/config-api.test.ts": 4021, + "packages/core/tests/construct-error-observation.test.ts": 4136, + "packages/core/tests/contextual-cwd.test.ts": 5188, + "packages/core/tests/daemon-integration.test.ts": 4697, + "packages/core/tests/daemon.test.ts": 3677, + "packages/core/tests/declared-markdown-component.test.ts": 5376, + "packages/core/tests/document-output-api.test.ts": 1574, + "packages/core/tests/document-target-execution.test.ts": 6751, + "packages/core/tests/document-targets.test.ts": 4045, + "packages/core/tests/document-validation.test.ts": 5442, + "packages/core/tests/documentation-index.test.ts": 3742, + "packages/core/tests/each.test.ts": 4021, + "packages/core/tests/elicit-component.test.ts": 4582, + "packages/core/tests/ephemeral-service.test.ts": 4374, + "packages/core/tests/eval-bindings.test.ts": 4050, + "packages/core/tests/eval-context.test.ts": 2189, + "packages/core/tests/eval-durable.test.ts": 4021, + "packages/core/tests/eval-error-mode.test.ts": 4235, + "packages/core/tests/eval-interpolate.test.ts": 1567, + "packages/core/tests/eval-middleware.test.ts": 3633, + "packages/core/tests/eval-persist.test.ts": 4068, + "packages/core/tests/eval-return.test.ts": 3995, + "packages/core/tests/eval-scope.test.ts": 3961, + "packages/core/tests/eval-timeout.test.ts": 4170, + "packages/core/tests/eval-transform.test.ts": 3791, + "packages/core/tests/evaluate-component.test.ts": 5976, + "packages/core/tests/evaluate-loaded-copy.test.ts": 4060, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 4167, + "packages/core/tests/evaluation-profile.test.ts": 3801, + "packages/core/tests/exec-timeout.test.ts": 4042, + "packages/core/tests/execute.test.ts": 4541, + "packages/core/tests/execution-protocol.test.ts": 5428, + "packages/core/tests/expand.test.ts": 4718, + "packages/core/tests/expansion-identity.test.ts": 4642, + "packages/core/tests/expansion-metadata.test.ts": 3913, + "packages/core/tests/expression-props.test.ts": 4498, + "packages/core/tests/fail-component.test.ts": 4219, + "packages/core/tests/failure-printing.test.ts": 4219, + "packages/core/tests/fatal-cause.test.ts": 3810, + "packages/core/tests/fetch-component.test.ts": 5429, + "packages/core/tests/file-component.test.ts": 5905, + "packages/core/tests/file-delete-component.test.ts": 4197, + "packages/core/tests/files-fatal.test.ts": 4941, + "packages/core/tests/foreground-exec.test.ts": 4975, + "packages/core/tests/frontmatter.test.ts": 3604, + "packages/core/tests/function-components.test.ts": 4628, + "packages/core/tests/generated-composition.test.ts": 4548, + "packages/core/tests/generated-xmd.test.ts": 5884, + "packages/core/tests/glob-api.test.ts": 1921, + "packages/core/tests/glob-component.test.ts": 4651, + "packages/core/tests/guarded-journal.test.ts": 3978, + "packages/core/tests/has-content.test.ts": 3804, + "packages/core/tests/heal.test.ts": 3748, "packages/core/tests/if.test.ts": 4441, - "packages/core/tests/inline-root.test.ts": 3995, - "packages/core/tests/invocation-failures.test.ts": 3634, - "packages/core/tests/invocation-identity.test.ts": 4404, - "packages/core/tests/invocation-scope.test.ts": 4044, - "packages/core/tests/journal-source-position.test.ts": 4177, - "packages/core/tests/json-component.test.ts": 4152, - "packages/core/tests/json.test.ts": 3598, - "packages/core/tests/let.test.ts": 3966, - "packages/core/tests/loaded-copy-files.test.ts": 3923, - "packages/core/tests/loop.test.ts": 4857, - "packages/core/tests/named-slots.test.ts": 4671, - "packages/core/tests/output-error-mode.test.ts": 5016, - "packages/core/tests/output-normalize.test.ts": 1536, - "packages/core/tests/output-terminal.test.ts": 1683, - "packages/core/tests/parse-components.test.ts": 4045, - "packages/core/tests/plan-response.test.ts": 1561, - "packages/core/tests/plugin-api.test.ts": 3551, - "packages/core/tests/plugin.test.ts": 3742, - "packages/core/tests/props-binding.test.ts": 4055, - "packages/core/tests/protected-content.test.ts": 3505, - "packages/core/tests/registered-printed-errors.test.ts": 3695, - "packages/core/tests/replay-stream.test.ts": 1513, - "packages/core/tests/retain.test.ts": 3953, - "packages/core/tests/root-composition.test.ts": 4111, - "packages/core/tests/root-props.test.ts": 4132, - "packages/core/tests/root-provider.test.ts": 4077, - "packages/core/tests/sample-component.test.ts": 4824, - "packages/core/tests/scanner.test.ts": 3606, - "packages/core/tests/scope-local.test.ts": 1519, - "packages/core/tests/secret-detection.test.ts": 4836, - "packages/core/tests/secret-files.test.ts": 2068, - "packages/core/tests/secret-rules.test.ts": 2069, - "packages/core/tests/secret-scanner-baseline.test.ts": 1719, - "packages/core/tests/secret-scanner.test.ts": 1928, - "packages/core/tests/source-position.test.ts": 3855, - "packages/core/tests/state-ownership.test.ts": 3830, - "packages/core/tests/streaming-emission.test.ts": 3834, - "packages/core/tests/switch.test.ts": 4456, - "packages/core/tests/syntax-catalog.test.ts": 4484, - "packages/core/tests/syntax-component.test.ts": 5289, - "packages/core/tests/syntax-loaded-copy.test.ts": 3988, - "packages/core/tests/temp-dir.test.ts": 6377, - "packages/core/tests/temp-file-compiler.test.ts": 2295, - "packages/core/tests/text-interpolation.test.ts": 3713, - "packages/core/tests/try-content.test.ts": 3786, - "packages/core/tests/unused-in-diff.test.ts": 4050, - "packages/core/tests/validate.test.ts": 3771, - "packages/core/tests/validation-integration.test.ts": 3855, - "packages/core/tests/workflow-component-bundle.test.ts": 4188, - "packages/durable-streams/tests/context.test.ts": 1807, - "packages/durable-streams/tests/deterministic-id.test.ts": 1844, - "packages/durable-streams/tests/divergence-api.test.ts": 1773, - "packages/durable-streams/tests/divergence.test.ts": 1871, - "packages/durable-streams/tests/durable-each.test.ts": 1844, - "packages/durable-streams/tests/durable-position.test.ts": 1839, - "packages/durable-streams/tests/durable-run.test.ts": 1871, - "packages/durable-streams/tests/ephemeral.test.ts": 1856, - "packages/durable-streams/tests/fail-stop.test.ts": 1898, - "packages/durable-streams/tests/guard-stream.test.ts": 1925, - "packages/durable-streams/tests/live-coordinator.test.ts": 1836, - "packages/durable-streams/tests/parse.test.ts": 1838, - "packages/durable-streams/tests/replay-guard.test.ts": 1919, - "packages/durable-streams/tests/replay-index.test.ts": 1647, - "packages/durable-streams/tests/retained.test.ts": 1669, - "packages/durable-streams/tests/serialize-event.test.ts": 1806, - "packages/durable-streams/tests/smoke.test.ts": 1721, - "packages/durable-streams/tests/structured-concurrency.test.ts": 1891, - "packages/durable-streams/tests/terminal-boundary.test.ts": 1842, - "packages/durable-streams/tests/types.test.ts": 1517, - "packages/git/tests/ambient-authentication.test.ts": 26902, - "packages/git/tests/api-entrypoint.test.ts": 4984, - "packages/git/tests/credential-helper.test.ts": 5871, - "packages/git/tests/git-add-crash.test.ts": 7063, - "packages/git/tests/git-add-durability.test.ts": 26562, - "packages/git/tests/git-add.test.ts": 35769, - "packages/git/tests/git-commit-crash.test.ts": 7108, - "packages/git/tests/git-commit-durability.test.ts": 33902, - "packages/git/tests/git-commit.test.ts": 39545, - "packages/git/tests/git-host-effect.test.ts": 8560, - "packages/git/tests/git-push-crash.test.ts": 9631, - "packages/git/tests/git-push-durability.test.ts": 43030, - "packages/git/tests/git-push.test.ts": 51881, - "packages/git/tests/git-switch-crash.test.ts": 6770, - "packages/git/tests/git-switch-durability.test.ts": 21383, - "packages/git/tests/git-switch.test.ts": 22868, - "packages/git/tests/git.test.ts": 4206, - "packages/git/tests/github-activation.test.ts": 5322, - "packages/git/tests/github-issues.test.ts": 4189, - "packages/git/tests/github-pull-requests.test.ts": 3789, - "packages/git/tests/github-workflow-activation.test.ts": 5683, - "packages/git/tests/issue-markdown.test.ts": 10698, - "packages/git/tests/issue-records.test.ts": 4102, - "packages/git/tests/materialization.test.ts": 4771, - "packages/git/tests/module-partition.test.ts": 4969, - "packages/git/tests/plugin.test.ts": 4658, - "packages/git/tests/provider-neutrality.test.ts": 3245, - "packages/git/tests/public-entrypoint.test.ts": 9622, - "packages/git/tests/pull-request-crash.test.ts": 8022, - "packages/git/tests/pull-request-durability.test.ts": 42471, - "packages/git/tests/pull-request-read.test.ts": 7756, - "packages/git/tests/pull-request-records.test.ts": 4078, - "packages/git/tests/pull-request.test.ts": 76124, - "packages/git/tests/repository-components.test.ts": 12298, - "packages/git/tests/repository-control-plane.test.ts": 10942, - "packages/git/tests/repository-materialization.test.ts": 8067, - "packages/git/tests/repository-replay.test.ts": 15211, - "packages/git/tests/repository-storage.test.ts": 18609, - "packages/git/tests/run-composition-ambient.test.ts": 12602, - "packages/git/tests/run-composition-lazy.test.ts": 5362, - "packages/git/tests/run-composition-managed.test.ts": 14415, - "packages/git/tests/run-composition-remote.test.ts": 14940, - "packages/git/tests/selection-authentication.test.ts": 1764, - "packages/git/tests/worktree-replay.test.ts": 6373, - "packages/runtime/tests/agent-session-coordinator.test.ts": 2058, - "packages/runtime/tests/duration.test.ts": 1459, - "packages/runtime/tests/executable-observer.test.ts": 1786, - "packages/runtime/tests/fetch.test.ts": 2283, - "packages/runtime/tests/fs.test.ts": 1651, - "packages/runtime/tests/host-files.test.ts": 2489, - "packages/runtime/tests/native-launcher.test.ts": 3884, - "packages/runtime/tests/process.test.ts": 1666, - "packages/runtime/tests/service.test.ts": 1789, - "packages/test-agent/tests/acp-server.test.ts": 1601, - "packages/test-agent/tests/behavior-engine.test.ts": 4009, - "packages/test-agent/tests/bridge.test.ts": 3467, - "packages/test-agent/tests/command-lazy.test.ts": 7508, - "packages/test-agent/tests/components.test.ts": 59614, - "packages/test-agent/tests/controller.test.ts": 2731, - "packages/test-agent/tests/cross-package-resolution.test.ts": 5724, - "packages/test-agent/tests/native-launch.test.ts": 29668, - "packages/test-agent/tests/net.test.ts": 1588, - "packages/test-agent/tests/profile.test.ts": 3784, - "packages/test-agent/tests/protocol.test.ts": 2316, - "packages/test-agent/tests/provider.test.ts": 4056, - "packages/test-agent/tests/public-api.test.ts": 4768, - "packages/test-agent/tests/route-slot.test.ts": 1486, - "packages/test-agent/tests/smoke.test.ts": 20423, - "packages/test-agent/tests/template.test.ts": 3696, - "packages/test-agent/tests/worker-lifecycle.test.ts": 12841, - "packages/test-support/tests/journal.test.ts": 1767, - "packages/testing/tests/agent-composition.test.ts": 4066, - "packages/testing/tests/assert-throws.test.ts": 5215, - "packages/testing/tests/assert.test.ts": 1508, - "packages/testing/tests/assertions.test.ts": 5717, - "packages/testing/tests/boundary-contract.test.ts": 4790, - "packages/testing/tests/cli.test.ts": 6456, - "packages/testing/tests/execution-harness.test.ts": 8329, - "packages/testing/tests/replay.test.ts": 5248, - "packages/testing/tests/smoke.test.ts": 5371, - "packages/testing/tests/test-component.test.ts": 5881, - "packages/testing/tests/testing-activation.test.ts": 5474, - "packages/testing/tests/testing-boundary.test.ts": 4452, - "packages/testing/tests/testing-mode.test.ts": 5935, - "packages/testing/tests/use-testing.test.ts": 4957, - "packages/web/tests/assets.test.ts": 1549, - "packages/web/tests/client-logic.test.ts": 1612, - "packages/web/tests/compile.test.ts": 4497, - "packages/web/tests/component.test.ts": 4879, - "packages/web/tests/declaration.test.ts": 4303, - "packages/web/tests/document.test.ts": 4449, - "packages/web/tests/elicitation.test.ts": 4229, - "packages/web/tests/live-form.test.ts": 4083, - "packages/web/tests/markdown.test.ts": 1699, - "packages/web/tests/opener.test.ts": 4098, - "packages/web/tests/page.test.ts": 1525, - "packages/web/tests/responder.test.ts": 4440, - "packages/web/tests/server-lifecycle.test.ts": 4243, - "packages/web/tests/server.test.ts": 4569, - "packages/workflow/tests/generated-agent-component.test.ts": 6405, - "packages/workflow/tests/generated-observations.test.ts": 3969, - "packages/workflow/tests/public-entrypoint.test.ts": 5711, - "packages/workflow/tests/retained-run.test.ts": 4837, - "packages/workflow/tests/service-denial.test.ts": 1742, - "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 5711, - "packages/workflow/tests/workflow-agent-sessions.test.ts": 4796, - "packages/workflow/tests/workflow-bundle.test.ts": 4300, - "packages/workflow/tests/workflow-checkpoint.test.ts": 5875, - "packages/workflow/tests/workflow-definition.test.ts": 4045, - "packages/workflow/tests/workflow-export.test.ts": 7684, - "packages/workflow/tests/workflow-fork-source.test.ts": 4811, - "packages/workflow/tests/workflow-fork.test.ts": 3921, - "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 5792, - "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6396, - "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 11010, - "packages/workflow/tests/workflow-run-journal.test.ts": 7009, - "packages/workflow/tests/workflow-run-storage.test.ts": 7854, - "packages/workflow/tests/workflow-run.test.ts": 4739, - "packages/workflow/tests/workflow-suspension-answer.test.ts": 5609, - "packages/workflow/tests/workflow-suspension.test.ts": 5301, - "packages/workflow/tests/workspace-crash-recovery.test.ts": 7544, - "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 4793, - "packages/workflow/tests/workspace-effect-transaction.test.ts": 5608, - "packages/workflow/tests/workspace-effect.test.ts": 5613, - "packages/workflow/tests/workspace-files.test.ts": 12010, - "packages/workflow/tests/workspace-root-restoration.test.ts": 6383, - "packages/workflow/tests/workspace-root.test.ts": 4779, - "packages/workflow/tests/workspace-transaction.test.ts": 4752, - "packages/workflow/tests/xmd-artifact.test.ts": 6487, - "scripts/tests/acpx-vendor.test.ts": 2074, - "scripts/tests/adapter-distribution.test.ts": 4305, - "scripts/tests/adapter-npm-package.test.ts": 29336, - "scripts/tests/adapter-vendor.test.ts": 3841, - "scripts/tests/bootstrap-npm-package.test.ts": 10917, - "scripts/tests/build-npm.test.ts": 7754, - "scripts/tests/build-web-client.test.ts": 28267, - "scripts/tests/bump-version.test.ts": 1619, - "scripts/tests/changed-paths.test.ts": 1500, - "scripts/tests/ci-workflow.test.ts": 4758, - "scripts/tests/cli-npm-bin.test.ts": 184383, - "scripts/tests/cloudflare-dofs-vendor.test.ts": 4194, - "scripts/tests/consumer-cycle.test.ts": 1598, - "scripts/tests/documentation-validation.test.ts": 6039, - "scripts/tests/filesystem-contract-workflow.test.ts": 1472, - "scripts/tests/frozen-entry.test.ts": 7834, - "scripts/tests/jsr-consumer-documentation.test.ts": 17292, - "scripts/tests/main-green.test.ts": 1862, - "scripts/tests/main-health.test.ts": 1620, - "scripts/tests/measure-test-weights.test.ts": 1914, - "scripts/tests/no-module-scoped-registry.test.ts": 2288, - "scripts/tests/no-redundant-test-scope.test.ts": 6405, - "scripts/tests/no-sync-filesystem.test.ts": 6250, - "scripts/tests/no-yield-in-finally.test.ts": 2359, - "scripts/tests/oxlint-policy.test.ts": 12076, - "scripts/tests/packaged-document.test.ts": 1589, - "scripts/tests/prefer-effection-operation.test.ts": 7485, - "scripts/tests/prefer-effection-result.test.ts": 5725, - "scripts/tests/prepared-state.test.ts": 2637, - "scripts/tests/publish-workflow-generator.test.ts": 5943, - "scripts/tests/publish-workflow-membership.test.ts": 1528, - "scripts/tests/readme-targets.test.ts": 18716, - "scripts/tests/release-targets.test.ts": 1635, - "scripts/tests/review-infrastructure.test.ts": 6072, - "scripts/tests/runtime-exclusions.test.ts": 2050, - "scripts/tests/runtime-tests.test.ts": 1811, - "scripts/tests/scope-bound-event-registration.test.ts": 6377, - "scripts/tests/shard-execution.test.ts": 11391, - "scripts/tests/staged-write.test.ts": 1656, - "scripts/tests/test-file-discovery.test.ts": 1791, - "scripts/tests/test-shards.test.ts": 1608, - "scripts/tests/test-weights.test.ts": 1908, - "scripts/tests/tracked.test.ts": 1570, - "scripts/tests/verify-adapter.test.ts": 7993, - "scripts/tests/verify-clean.test.ts": 1818, - "scripts/tests/verify-coordinator.test.ts": 1975, - "scripts/tests/web-client-module.test.ts": 1596, - "scripts/tests/workspace.test.ts": 1571 + "packages/core/tests/inline-root.test.ts": 4045, + "packages/core/tests/invocation-failures.test.ts": 3770, + "packages/core/tests/invocation-identity.test.ts": 4485, + "packages/core/tests/invocation-scope.test.ts": 4060, + "packages/core/tests/journal-source-position.test.ts": 4189, + "packages/core/tests/json-component.test.ts": 4223, + "packages/core/tests/json.test.ts": 3613, + "packages/core/tests/let.test.ts": 4058, + "packages/core/tests/loaded-copy-files.test.ts": 3871, + "packages/core/tests/loop.test.ts": 4825, + "packages/core/tests/named-slots.test.ts": 4817, + "packages/core/tests/output-error-mode.test.ts": 5057, + "packages/core/tests/output-normalize.test.ts": 1563, + "packages/core/tests/output-terminal.test.ts": 1687, + "packages/core/tests/parse-components.test.ts": 4110, + "packages/core/tests/plan-response.test.ts": 1584, + "packages/core/tests/plugin-api.test.ts": 3617, + "packages/core/tests/plugin.test.ts": 3744, + "packages/core/tests/props-binding.test.ts": 4149, + "packages/core/tests/protected-content.test.ts": 3583, + "packages/core/tests/registered-printed-errors.test.ts": 3775, + "packages/core/tests/replay-stream.test.ts": 1540, + "packages/core/tests/retain.test.ts": 4019, + "packages/core/tests/root-composition.test.ts": 4119, + "packages/core/tests/root-props.test.ts": 4114, + "packages/core/tests/root-provider.test.ts": 4111, + "packages/core/tests/sample-component.test.ts": 4837, + "packages/core/tests/scanner.test.ts": 3658, + "packages/core/tests/scope-local.test.ts": 1558, + "packages/core/tests/secret-detection.test.ts": 4794, + "packages/core/tests/secret-files.test.ts": 2081, + "packages/core/tests/secret-rules.test.ts": 2080, + "packages/core/tests/secret-scanner-baseline.test.ts": 1768, + "packages/core/tests/secret-scanner.test.ts": 1954, + "packages/core/tests/source-position.test.ts": 3845, + "packages/core/tests/state-ownership.test.ts": 3842, + "packages/core/tests/streaming-emission.test.ts": 3902, + "packages/core/tests/switch.test.ts": 4479, + "packages/core/tests/syntax-catalog.test.ts": 4531, + "packages/core/tests/syntax-component.test.ts": 5259, + "packages/core/tests/syntax-loaded-copy.test.ts": 4124, + "packages/core/tests/temp-dir.test.ts": 6461, + "packages/core/tests/temp-file-compiler.test.ts": 2237, + "packages/core/tests/text-interpolation.test.ts": 3805, + "packages/core/tests/try-content.test.ts": 3795, + "packages/core/tests/unused-in-diff.test.ts": 4085, + "packages/core/tests/validate.test.ts": 3831, + "packages/core/tests/validation-integration.test.ts": 3972, + "packages/core/tests/workflow-component-bundle.test.ts": 4193, + "packages/durable-streams/tests/context.test.ts": 1789, + "packages/durable-streams/tests/deterministic-id.test.ts": 1853, + "packages/durable-streams/tests/divergence-api.test.ts": 1844, + "packages/durable-streams/tests/divergence.test.ts": 1909, + "packages/durable-streams/tests/durable-each.test.ts": 1864, + "packages/durable-streams/tests/durable-position.test.ts": 1853, + "packages/durable-streams/tests/durable-run.test.ts": 1885, + "packages/durable-streams/tests/ephemeral.test.ts": 1854, + "packages/durable-streams/tests/fail-stop.test.ts": 1913, + "packages/durable-streams/tests/guard-stream.test.ts": 1935, + "packages/durable-streams/tests/live-coordinator.test.ts": 1871, + "packages/durable-streams/tests/parse.test.ts": 1844, + "packages/durable-streams/tests/replay-guard.test.ts": 1957, + "packages/durable-streams/tests/replay-index.test.ts": 1663, + "packages/durable-streams/tests/retained.test.ts": 1689, + "packages/durable-streams/tests/serialize-event.test.ts": 1840, + "packages/durable-streams/tests/smoke.test.ts": 1790, + "packages/durable-streams/tests/structured-concurrency.test.ts": 2001, + "packages/durable-streams/tests/terminal-boundary.test.ts": 1921, + "packages/durable-streams/tests/types.test.ts": 1508, + "packages/git/tests/ambient-authentication.test.ts": 26947, + "packages/git/tests/api-entrypoint.test.ts": 5108, + "packages/git/tests/credential-helper.test.ts": 6154, + "packages/git/tests/git-add-crash.test.ts": 7273, + "packages/git/tests/git-add-durability.test.ts": 27230, + "packages/git/tests/git-add.test.ts": 35677, + "packages/git/tests/git-commit-crash.test.ts": 7534, + "packages/git/tests/git-commit-durability.test.ts": 34414, + "packages/git/tests/git-commit.test.ts": 40611, + "packages/git/tests/git-host-effect.test.ts": 8963, + "packages/git/tests/git-push-crash.test.ts": 10239, + "packages/git/tests/git-push-durability.test.ts": 44279, + "packages/git/tests/git-push.test.ts": 52785, + "packages/git/tests/git-switch-crash.test.ts": 7196, + "packages/git/tests/git-switch-durability.test.ts": 22187, + "packages/git/tests/git-switch.test.ts": 23303, + "packages/git/tests/git.test.ts": 4452, + "packages/git/tests/github-activation.test.ts": 5712, + "packages/git/tests/github-issues.test.ts": 4428, + "packages/git/tests/github-pull-requests.test.ts": 4122, + "packages/git/tests/github-workflow-activation.test.ts": 6022, + "packages/git/tests/issue-markdown.test.ts": 11529, + "packages/git/tests/issue-records.test.ts": 4269, + "packages/git/tests/materialization.test.ts": 5013, + "packages/git/tests/module-partition.test.ts": 5346, + "packages/git/tests/plugin.test.ts": 4736, + "packages/git/tests/provider-neutrality.test.ts": 3622, + "packages/git/tests/public-entrypoint.test.ts": 9665, + "packages/git/tests/pull-request-crash.test.ts": 8435, + "packages/git/tests/pull-request-durability.test.ts": 43277, + "packages/git/tests/pull-request-read.test.ts": 8225, + "packages/git/tests/pull-request-records.test.ts": 4348, + "packages/git/tests/pull-request.test.ts": 73384, + "packages/git/tests/repository-components.test.ts": 12922, + "packages/git/tests/repository-control-plane.test.ts": 11670, + "packages/git/tests/repository-materialization.test.ts": 8545, + "packages/git/tests/repository-replay.test.ts": 16220, + "packages/git/tests/repository-storage.test.ts": 19719, + "packages/git/tests/run-composition-ambient.test.ts": 13320, + "packages/git/tests/run-composition-lazy.test.ts": 5773, + "packages/git/tests/run-composition-managed.test.ts": 15261, + "packages/git/tests/run-composition-remote.test.ts": 15652, + "packages/git/tests/selection-authentication.test.ts": 1828, + "packages/git/tests/worktree-replay.test.ts": 6941, + "packages/runtime/tests/agent-session-coordinator.test.ts": 2196, + "packages/runtime/tests/duration.test.ts": 1542, + "packages/runtime/tests/executable-observer.test.ts": 1933, + "packages/runtime/tests/fetch.test.ts": 2363, + "packages/runtime/tests/fs.test.ts": 1754, + "packages/runtime/tests/host-files.test.ts": 2596, + "packages/runtime/tests/native-launcher.test.ts": 3964, + "packages/runtime/tests/process.test.ts": 1740, + "packages/runtime/tests/service.test.ts": 1843, + "packages/test-agent/tests/acp-server.test.ts": 1690, + "packages/test-agent/tests/behavior-engine.test.ts": 4264, + "packages/test-agent/tests/bridge.test.ts": 3681, + "packages/test-agent/tests/command-lazy.test.ts": 7899, + "packages/test-agent/tests/components.test.ts": 60531, + "packages/test-agent/tests/controller.test.ts": 2909, + "packages/test-agent/tests/cross-package-resolution.test.ts": 6102, + "packages/test-agent/tests/native-launch.test.ts": 30610, + "packages/test-agent/tests/net.test.ts": 1637, + "packages/test-agent/tests/profile.test.ts": 4035, + "packages/test-agent/tests/protocol.test.ts": 2514, + "packages/test-agent/tests/provider.test.ts": 4403, + "packages/test-agent/tests/public-api.test.ts": 5087, + "packages/test-agent/tests/route-slot.test.ts": 1559, + "packages/test-agent/tests/smoke.test.ts": 21149, + "packages/test-agent/tests/template.test.ts": 4018, + "packages/test-agent/tests/worker-lifecycle.test.ts": 13520, + "packages/test-support/tests/journal.test.ts": 1896, + "packages/testing/tests/agent-composition.test.ts": 4416, + "packages/testing/tests/assert-throws.test.ts": 5834, + "packages/testing/tests/assert.test.ts": 1577, + "packages/testing/tests/assertions.test.ts": 6311, + "packages/testing/tests/boundary-contract.test.ts": 5119, + "packages/testing/tests/cli.test.ts": 6982, + "packages/testing/tests/execution-harness.test.ts": 9215, + "packages/testing/tests/replay.test.ts": 5628, + "packages/testing/tests/smoke.test.ts": 5831, + "packages/testing/tests/test-component.test.ts": 6340, + "packages/testing/tests/testing-activation.test.ts": 5834, + "packages/testing/tests/testing-boundary.test.ts": 4823, + "packages/testing/tests/testing-mode.test.ts": 6371, + "packages/testing/tests/use-testing.test.ts": 5313, + "packages/web/tests/assets.test.ts": 1625, + "packages/web/tests/client-logic.test.ts": 1667, + "packages/web/tests/compile.test.ts": 4827, + "packages/web/tests/component.test.ts": 5383, + "packages/web/tests/declaration.test.ts": 4596, + "packages/web/tests/document.test.ts": 4913, + "packages/web/tests/elicitation.test.ts": 4591, + "packages/web/tests/live-form.test.ts": 4479, + "packages/web/tests/markdown.test.ts": 1785, + "packages/web/tests/opener.test.ts": 4568, + "packages/web/tests/page.test.ts": 1642, + "packages/web/tests/responder.test.ts": 4784, + "packages/web/tests/server-lifecycle.test.ts": 4605, + "packages/web/tests/server.test.ts": 5000, + "packages/workflow/tests/generated-agent-component.test.ts": 6984, + "packages/workflow/tests/generated-observations.test.ts": 4238, + "packages/workflow/tests/public-entrypoint.test.ts": 6052, + "packages/workflow/tests/retained-run.test.ts": 5273, + "packages/workflow/tests/service-denial.test.ts": 1891, + "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 6548, + "packages/workflow/tests/workflow-agent-sessions.test.ts": 5433, + "packages/workflow/tests/workflow-bundle.test.ts": 4684, + "packages/workflow/tests/workflow-checkpoint.test.ts": 6611, + "packages/workflow/tests/workflow-definition.test.ts": 4536, + "packages/workflow/tests/workflow-export.test.ts": 8627, + "packages/workflow/tests/workflow-fork-source.test.ts": 5234, + "packages/workflow/tests/workflow-fork.test.ts": 4253, + "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 6255, + "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6773, + "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 11504, + "packages/workflow/tests/workflow-run-journal.test.ts": 7801, + "packages/workflow/tests/workflow-run-storage.test.ts": 8630, + "packages/workflow/tests/workflow-run.test.ts": 5394, + "packages/workflow/tests/workflow-suspension-answer.test.ts": 6429, + "packages/workflow/tests/workflow-suspension.test.ts": 6048, + "packages/workflow/tests/workspace-crash-recovery.test.ts": 8100, + "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 5321, + "packages/workflow/tests/workspace-effect-transaction.test.ts": 6160, + "packages/workflow/tests/workspace-effect.test.ts": 6070, + "packages/workflow/tests/workspace-files.test.ts": 13939, + "packages/workflow/tests/workspace-root-restoration.test.ts": 6922, + "packages/workflow/tests/workspace-root.test.ts": 5323, + "packages/workflow/tests/workspace-transaction.test.ts": 5292, + "packages/workflow/tests/xmd-artifact.test.ts": 7149, + "scripts/tests/acpx-vendor.test.ts": 2214, + "scripts/tests/adapter-distribution.test.ts": 4618, + "scripts/tests/adapter-npm-package.test.ts": 30510, + "scripts/tests/adapter-vendor.test.ts": 4367, + "scripts/tests/bootstrap-npm-package.test.ts": 11501, + "scripts/tests/build-npm.test.ts": 41068, + "scripts/tests/build-web-client.test.ts": 31267, + "scripts/tests/bump-version.test.ts": 1703, + "scripts/tests/changed-paths.test.ts": 1562, + "scripts/tests/ci-workflow.test.ts": 5124, + "scripts/tests/cli-npm-bin.test.ts": 258452, + "scripts/tests/cloudflare-dofs-vendor.test.ts": 4506, + "scripts/tests/consumer-cycle.test.ts": 1725, + "scripts/tests/documentation-validation.test.ts": 6323, + "scripts/tests/filesystem-contract-workflow.test.ts": 1590, + "scripts/tests/frozen-entry.test.ts": 7819, + "scripts/tests/jsr-consumer-documentation.test.ts": 18572, + "scripts/tests/main-green.test.ts": 1974, + "scripts/tests/main-health.test.ts": 1813, + "scripts/tests/measure-test-weights.test.ts": 2105, + "scripts/tests/no-module-scoped-registry.test.ts": 2569, + "scripts/tests/no-redundant-test-scope.test.ts": 6774, + "scripts/tests/no-sync-filesystem.test.ts": 6442, + "scripts/tests/no-yield-in-finally.test.ts": 2403, + "scripts/tests/oxlint-policy.test.ts": 13026, + "scripts/tests/packaged-document.test.ts": 1699, + "scripts/tests/prefer-effection-operation.test.ts": 7989, + "scripts/tests/prefer-effection-result.test.ts": 5960, + "scripts/tests/prepared-state.test.ts": 2811, + "scripts/tests/publish-workflow-generator.test.ts": 6290, + "scripts/tests/publish-workflow-membership.test.ts": 1754, + "scripts/tests/publishable-membership-agreement.test.ts": 4179, + "scripts/tests/readme-targets.test.ts": 19325, + "scripts/tests/release-targets.test.ts": 1719, + "scripts/tests/review-infrastructure.test.ts": 6433, + "scripts/tests/runtime-exclusions.test.ts": 2152, + "scripts/tests/runtime-tests.test.ts": 1789, + "scripts/tests/scope-bound-event-registration.test.ts": 6529, + "scripts/tests/shard-execution.test.ts": 11804, + "scripts/tests/staged-write.test.ts": 1611, + "scripts/tests/test-file-discovery.test.ts": 1843, + "scripts/tests/test-shards.test.ts": 1629, + "scripts/tests/test-weights.test.ts": 1920, + "scripts/tests/tracked.test.ts": 1636, + "scripts/tests/verify-adapter.test.ts": 7922, + "scripts/tests/verify-clean.test.ts": 1851, + "scripts/tests/verify-coordinator.test.ts": 1998, + "scripts/tests/version-lockstep.test.ts": 1783, + "scripts/tests/web-client-module.test.ts": 1584, + "scripts/tests/workspace.test.ts": 1601 }, "node": { - "packages/acp/tests/acceptance.test.ts": 2243, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4928, - "packages/acp/tests/acpx-materialization.test.ts": 7189, - "packages/acp/tests/acpx-transient-env.test.ts": 831, - "packages/acp/tests/adapter-materialization.test.ts": 72915, - "packages/acp/tests/adapter-protocol.test.ts": 53069, - "packages/acp/tests/codex-surfaces.test.ts": 692, - "packages/acp/tests/native-launch.test.ts": 2205, - "packages/acp/tests/permission-bridge.test.ts": 1222, - "packages/acp/tests/provider.test.ts": 2207, - "packages/acp/tests/serial-queue.test.ts": 526, - "packages/acp/tests/session-key.test.ts": 553, - "packages/acp/tests/terminal-screen.test.ts": 641, - "packages/cli/tests/agent-adapters.test.ts": 2482, - "packages/cli/tests/agent-cli.test.ts": 42254, - "packages/cli/tests/agent-config.test.ts": 497, - "packages/cli/tests/agent-options-cli.test.ts": 15834, - "packages/cli/tests/agent-options.test.ts": 511, - "packages/cli/tests/agent-session-coordinator.test.ts": 1381, - "packages/cli/tests/cli-help.test.ts": 30180, - "packages/cli/tests/command.test.ts": 14344, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4067, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 165677, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3508, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 9510, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 5767, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 17214, - "packages/cli/tests/evaluate-component.test.ts": 14747, - "packages/cli/tests/fetch-cli.test.ts": 4700, - "packages/cli/tests/file-stream.test.ts": 531, - "packages/cli/tests/inline-cli.test.ts": 64824, - "packages/cli/tests/launch-timeout.test.ts": 20550, - "packages/cli/tests/packaged-document.test.ts": 1600, - "packages/cli/tests/plan-args.test.ts": 512, - "packages/cli/tests/plan-cli.test.ts": 111033, - "packages/cli/tests/plan-command-document.test.ts": 7663, - "packages/cli/tests/plan-component.test.ts": 10478, - "packages/cli/tests/plan-host-acts.test.ts": 2316, - "packages/cli/tests/plan.test.ts": 15352, - "packages/cli/tests/plugin-cli.test.ts": 77975, - "packages/cli/tests/plugin-host.test.ts": 3127, - "packages/cli/tests/plugin-modules.test.ts": 502, - "packages/cli/tests/plugin-selection.test.ts": 506, - "packages/cli/tests/process-retention.test.ts": 4647, - "packages/cli/tests/props-cli.test.ts": 60510, - "packages/cli/tests/props-schema.test.ts": 1310, - "packages/cli/tests/props-sources.test.ts": 672, - "packages/cli/tests/run-composition.test.ts": 3919, - "packages/cli/tests/run-deadline.test.ts": 3596, - "packages/cli/tests/run-profile.test.ts": 1248, - "packages/cli/tests/run-timeouts.test.ts": 19546, - "packages/cli/tests/secret-detection-cli.test.ts": 36150, - "packages/cli/tests/service-document.test.ts": 2627, - "packages/cli/tests/service-host.test.ts": 2380, - "packages/cli/tests/session-launch-cli.test.ts": 10433, - "packages/cli/tests/stdin-cli.test.ts": 60086, - "packages/cli/tests/stdout-delivery.test.ts": 485, - "packages/cli/tests/syntax-cli.test.ts": 59621, - "packages/cli/tests/targets-cli.test.ts": 48239, - "packages/cli/tests/test-root-profile.test.ts": 6703, - "packages/cli/tests/test-target.test.ts": 69730, - "packages/cli/tests/testing-activation.test.ts": 1444, - "packages/cli/tests/testing-execution-host.test.ts": 86190, - "packages/cli/tests/upgrade-cli.test.ts": 43450, - "packages/cli/tests/upgrade-command-document.test.ts": 5155, - "packages/cli/tests/upgrade-output.test.ts": 1792, - "packages/cli/tests/value-root.test.ts": 29418, - "packages/cli/tests/verbose-component.test.ts": 1861, - "packages/cli/tests/workflow-declaration.test.ts": 1395, - "packages/cli/tests/workflow-host.test.ts": 10050, - "packages/code-review-agent/tests/doctor.test.ts": 481, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 525, - "packages/code-review-agent/tests/parse-diff.test.ts": 492, - "packages/code-review-agent/tests/parse-doctor.test.ts": 1157, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 478, - "packages/code-review-agent/tests/policy.test.ts": 529, - "packages/core/tests/agent-api.test.ts": 487, - "packages/core/tests/agent-components.test.ts": 1905, - "packages/core/tests/agent-function-components.test.ts": 3549, - "packages/core/tests/agent-session-launch.test.ts": 4396, - "packages/core/tests/agent-session-placement.test.ts": 504, - "packages/core/tests/agent-session-use.test.ts": 482, - "packages/core/tests/agent-stack.test.ts": 1464, - "packages/core/tests/answer-identity.test.ts": 510, - "packages/core/tests/answers-component.test.ts": 2250, - "packages/core/tests/answers-expansion-recursion.test.ts": 958, - "packages/core/tests/capture-errors.test.ts": 1214, - "packages/core/tests/capture-props.test.ts": 966, - "packages/core/tests/cli-journal.test.ts": 20872, - "packages/core/tests/code-block-component.test.ts": 2915, - "packages/core/tests/compiler-boundary.test.ts": 1106, - "packages/core/tests/component-api.test.ts": 544, - "packages/core/tests/component-registration.test.ts": 2009, - "packages/core/tests/component-returns.test.ts": 2685, - "packages/core/tests/component-schema-conformance.test.ts": 1083, - "packages/core/tests/config-api.test.ts": 1231, - "packages/core/tests/construct-error-observation.test.ts": 1303, - "packages/core/tests/contextual-cwd.test.ts": 2403, - "packages/core/tests/daemon-integration.test.ts": 1856, - "packages/core/tests/daemon.test.ts": 563, - "packages/core/tests/declared-markdown-component.test.ts": 3101, - "packages/core/tests/document-output-api.test.ts": 504, - "packages/core/tests/document-target-execution.test.ts": 4499, - "packages/core/tests/document-targets.test.ts": 1135, - "packages/core/tests/document-validation.test.ts": 2870, - "packages/core/tests/documentation-index.test.ts": 835, - "packages/core/tests/each.test.ts": 1276, - "packages/core/tests/elicit-component.test.ts": 1801, - "packages/core/tests/ephemeral-service.test.ts": 1820, - "packages/core/tests/eval-bindings.test.ts": 1238, - "packages/core/tests/eval-context.test.ts": 591, - "packages/core/tests/eval-durable.test.ts": 1270, - "packages/core/tests/eval-error-mode.test.ts": 1627, - "packages/core/tests/eval-interpolate.test.ts": 494, - "packages/core/tests/eval-middleware.test.ts": 585, - "packages/core/tests/eval-persist.test.ts": 1365, - "packages/core/tests/eval-return.test.ts": 1255, - "packages/core/tests/eval-scope.test.ts": 1148, - "packages/core/tests/eval-timeout.test.ts": 1254, - "packages/core/tests/eval-transform.test.ts": 576, - "packages/core/tests/evaluate-component.test.ts": 3516, - "packages/core/tests/evaluate-loaded-copy.test.ts": 1339, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 1378, - "packages/core/tests/evaluation-profile.test.ts": 875, - "packages/core/tests/exec-timeout.test.ts": 1324, - "packages/core/tests/execute.test.ts": 1979, - "packages/core/tests/execution-protocol.test.ts": 2808, - "packages/core/tests/expand.test.ts": 1931, - "packages/core/tests/expansion-identity.test.ts": 1911, - "packages/core/tests/expansion-metadata.test.ts": 1019, - "packages/core/tests/expression-props.test.ts": 1966, - "packages/core/tests/fail-component.test.ts": 1584, - "packages/core/tests/failure-printing.test.ts": 1422, - "packages/core/tests/fatal-cause.test.ts": 877, - "packages/core/tests/fetch-component.test.ts": 2960, - "packages/core/tests/file-component.test.ts": 3440, - "packages/core/tests/file-delete-component.test.ts": 1510, - "packages/core/tests/files-fatal.test.ts": 2501, - "packages/core/tests/foreground-exec.test.ts": 2295, - "packages/core/tests/frontmatter.test.ts": 563, - "packages/core/tests/function-components.test.ts": 2088, - "packages/core/tests/generated-composition.test.ts": 2071, - "packages/core/tests/generated-xmd.test.ts": 3808, - "packages/core/tests/glob-api.test.ts": 604, - "packages/core/tests/glob-component.test.ts": 2070, - "packages/core/tests/guarded-journal.test.ts": 1349, - "packages/core/tests/has-content.test.ts": 915, - "packages/core/tests/heal.test.ts": 928, - "packages/core/tests/if.test.ts": 1852, - "packages/core/tests/inline-root.test.ts": 1350, - "packages/core/tests/invocation-failures.test.ts": 596, - "packages/core/tests/invocation-identity.test.ts": 1697, - "packages/core/tests/invocation-scope.test.ts": 1169, - "packages/core/tests/journal-source-position.test.ts": 1431, - "packages/core/tests/json-component.test.ts": 1570, - "packages/core/tests/json.test.ts": 500, - "packages/core/tests/let.test.ts": 1263, - "packages/core/tests/loop.test.ts": 2339, - "packages/core/tests/named-slots.test.ts": 2260, - "packages/core/tests/output-error-mode.test.ts": 2577, - "packages/core/tests/output-normalize.test.ts": 525, - "packages/core/tests/output-terminal.test.ts": 703, - "packages/core/tests/parse-components.test.ts": 1426, - "packages/core/tests/plan-response.test.ts": 590, - "packages/core/tests/plugin-api.test.ts": 509, - "packages/core/tests/plugin.test.ts": 994, - "packages/core/tests/props-binding.test.ts": 1427, - "packages/core/tests/protected-content.test.ts": 541, - "packages/core/tests/registered-printed-errors.test.ts": 858, - "packages/core/tests/replay-stream.test.ts": 508, - "packages/core/tests/retain.test.ts": 1142, - "packages/core/tests/root-composition.test.ts": 1395, - "packages/core/tests/root-props.test.ts": 1489, - "packages/core/tests/root-provider.test.ts": 1357, - "packages/core/tests/sample-component.test.ts": 2436, - "packages/core/tests/scanner.test.ts": 637, - "packages/core/tests/scope-local.test.ts": 517, - "packages/core/tests/secret-detection.test.ts": 2081, - "packages/core/tests/secret-files.test.ts": 765, - "packages/core/tests/secret-rules.test.ts": 849, - "packages/core/tests/secret-scanner-baseline.test.ts": 728, - "packages/core/tests/secret-scanner.test.ts": 690, - "packages/core/tests/source-position.test.ts": 1142, - "packages/core/tests/state-ownership.test.ts": 1191, - "packages/core/tests/streaming-emission.test.ts": 1141, - "packages/core/tests/switch.test.ts": 1837, - "packages/core/tests/syntax-catalog.test.ts": 1849, - "packages/core/tests/syntax-component.test.ts": 2778, - "packages/core/tests/temp-dir.test.ts": 3732, - "packages/core/tests/temp-file-compiler.test.ts": 571, - "packages/core/tests/text-interpolation.test.ts": 902, - "packages/core/tests/try-content.test.ts": 888, - "packages/core/tests/unused-in-diff.test.ts": 1327, - "packages/core/tests/validate.test.ts": 805, - "packages/core/tests/validation-integration.test.ts": 1158, - "packages/core/tests/workflow-component-bundle.test.ts": 1582, - "packages/durable-streams/tests/context.test.ts": 534, - "packages/durable-streams/tests/deterministic-id.test.ts": 573, - "packages/durable-streams/tests/divergence-api.test.ts": 607, - "packages/durable-streams/tests/divergence.test.ts": 570, - "packages/durable-streams/tests/durable-each.test.ts": 559, - "packages/durable-streams/tests/durable-position.test.ts": 587, - "packages/durable-streams/tests/durable-run.test.ts": 561, - "packages/durable-streams/tests/ephemeral.test.ts": 550, - "packages/durable-streams/tests/fail-stop.test.ts": 597, - "packages/durable-streams/tests/guard-stream.test.ts": 611, - "packages/durable-streams/tests/live-coordinator.test.ts": 532, - "packages/durable-streams/tests/parse.test.ts": 587, - "packages/durable-streams/tests/replay-guard.test.ts": 583, - "packages/durable-streams/tests/replay-index.test.ts": 546, - "packages/durable-streams/tests/retained.test.ts": 548, - "packages/durable-streams/tests/serialize-event.test.ts": 537, - "packages/durable-streams/tests/smoke.test.ts": 522, - "packages/durable-streams/tests/structured-concurrency.test.ts": 602, - "packages/durable-streams/tests/terminal-boundary.test.ts": 570, - "packages/durable-streams/tests/types.test.ts": 485, - "packages/git/tests/api-entrypoint.test.ts": 1359, - "packages/git/tests/git-host-effect.test.ts": 6896, - "packages/git/tests/git.test.ts": 1288, - "packages/git/tests/github-activation.test.ts": 1653, - "packages/git/tests/github-issues.test.ts": 1203, - "packages/git/tests/github-pull-requests.test.ts": 604, - "packages/git/tests/issue-markdown.test.ts": 9724, - "packages/git/tests/issue-records.test.ts": 1203, - "packages/git/tests/module-partition.test.ts": 2970, - "packages/git/tests/plugin.test.ts": 1757, - "packages/git/tests/provider-neutrality.test.ts": 2251, - "packages/git/tests/pull-request-records.test.ts": 1267, - "packages/git/tests/run-composition-lazy.test.ts": 1929, - "packages/git/tests/selection-authentication.test.ts": 480, - "packages/runtime/tests/duration.test.ts": 485, - "packages/runtime/tests/fetch.test.ts": 1126, - "packages/runtime/tests/fs.test.ts": 551, - "packages/runtime/tests/host-files.test.ts": 1280, - "packages/runtime/tests/native-launcher.test.ts": 2894, - "packages/runtime/tests/process.test.ts": 558, - "packages/runtime/tests/service.test.ts": 532, - "packages/test-agent/tests/acp-server.test.ts": 660, - "packages/test-agent/tests/behavior-engine.test.ts": 1565, - "packages/test-agent/tests/bridge.test.ts": 516, - "packages/test-agent/tests/command-lazy.test.ts": 6319, - "packages/test-agent/tests/components.test.ts": 103148, - "packages/test-agent/tests/controller.test.ts": 808, - "packages/test-agent/tests/cross-package-resolution.test.ts": 2868, - "packages/test-agent/tests/native-launch.test.ts": 52790, - "packages/test-agent/tests/net.test.ts": 617, - "packages/test-agent/tests/profile.test.ts": 1217, - "packages/test-agent/tests/protocol.test.ts": 576, - "packages/test-agent/tests/provider.test.ts": 1372, - "packages/test-agent/tests/public-api.test.ts": 1554, - "packages/test-agent/tests/route-slot.test.ts": 511, - "packages/test-agent/tests/smoke.test.ts": 27887, - "packages/test-agent/tests/template.test.ts": 1198, - "packages/test-agent/tests/worker-lifecycle.test.ts": 23229, - "packages/test-support/tests/journal.test.ts": 532, - "packages/testing/tests/agent-composition.test.ts": 1510, - "packages/testing/tests/assert-throws.test.ts": 3097, - "packages/testing/tests/assert.test.ts": 504, - "packages/testing/tests/assertions.test.ts": 3664, - "packages/testing/tests/boundary-contract.test.ts": 2441, - "packages/testing/tests/cli.test.ts": 10475, - "packages/testing/tests/execution-harness.test.ts": 7076, - "packages/testing/tests/replay.test.ts": 2926, - "packages/testing/tests/smoke.test.ts": 3117, - "packages/testing/tests/test-component.test.ts": 3737, - "packages/testing/tests/testing-activation.test.ts": 3272, - "packages/testing/tests/testing-boundary.test.ts": 1977, - "packages/testing/tests/testing-mode.test.ts": 3893, - "packages/testing/tests/use-testing.test.ts": 2663, - "packages/web/tests/assets.test.ts": 497, - "packages/web/tests/client-logic.test.ts": 545, - "packages/web/tests/compile.test.ts": 1909, - "packages/web/tests/component.test.ts": 2427, - "packages/web/tests/declaration.test.ts": 1648, - "packages/web/tests/document.test.ts": 1803, - "packages/web/tests/elicitation.test.ts": 1560, - "packages/web/tests/live-form.test.ts": 1512, - "packages/web/tests/markdown.test.ts": 709, - "packages/web/tests/opener.test.ts": 1481, - "packages/web/tests/page.test.ts": 499, - "packages/web/tests/responder.test.ts": 1876, - "packages/web/tests/server-lifecycle.test.ts": 1620, - "packages/web/tests/server.test.ts": 2053, - "packages/workflow/tests/generated-observations.test.ts": 1434, - "packages/workflow/tests/retained-run.test.ts": 2161, - "packages/workflow/tests/service-denial.test.ts": 525, - "packages/workflow/tests/workflow-bundle.test.ts": 1640, - "packages/workflow/tests/workflow-definition.test.ts": 1344, - "packages/workflow/tests/workflow-fork.test.ts": 1210, - "packages/workflow/tests/workflow-run.test.ts": 2063, - "packages/workflow/tests/workspace-effect.test.ts": 2960, - "packages/workflow/tests/xmd-artifact.test.ts": 3872, - "scripts/tests/adapter-vendor.test.ts": 733, - "scripts/tests/bootstrap-npm-package.test.ts": 8822, - "scripts/tests/bump-version.test.ts": 637, - "scripts/tests/changed-paths.test.ts": 526, - "scripts/tests/ci-workflow.test.ts": 3729, - "scripts/tests/consumer-cycle.test.ts": 685, - "scripts/tests/filesystem-contract-workflow.test.ts": 629, - "scripts/tests/main-green.test.ts": 774, - "scripts/tests/main-health.test.ts": 614, - "scripts/tests/no-module-scoped-registry.test.ts": 1354, - "scripts/tests/no-redundant-test-scope.test.ts": 5415, - "scripts/tests/no-sync-filesystem.test.ts": 5546, - "scripts/tests/no-yield-in-finally.test.ts": 1332, - "scripts/tests/oxlint-policy.test.ts": 11070, - "scripts/tests/packaged-document.test.ts": 585, - "scripts/tests/prefer-effection-operation.test.ts": 6366, - "scripts/tests/prefer-effection-result.test.ts": 4553, - "scripts/tests/publish-workflow-membership.test.ts": 574, - "scripts/tests/review-infrastructure.test.ts": 3213, - "scripts/tests/runtime-exclusions.test.ts": 991, - "scripts/tests/runtime-tests.test.ts": 559, - "scripts/tests/scope-bound-event-registration.test.ts": 5325, - "scripts/tests/staged-write.test.ts": 544, - "scripts/tests/test-file-discovery.test.ts": 790, - "scripts/tests/test-shards.test.ts": 539, - "scripts/tests/test-weights.test.ts": 711, - "scripts/tests/tracked.test.ts": 519, - "scripts/tests/verify-coordinator.test.ts": 729, - "scripts/tests/web-client-module.test.ts": 529, - "scripts/tests/workspace.test.ts": 508 + "packages/acp/tests/acceptance.test.ts": 2252, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4912, + "packages/acp/tests/acpx-materialization.test.ts": 7110, + "packages/acp/tests/acpx-transient-env.test.ts": 833, + "packages/acp/tests/adapter-materialization.test.ts": 75930, + "packages/acp/tests/adapter-protocol.test.ts": 55960, + "packages/acp/tests/codex-surfaces.test.ts": 672, + "packages/acp/tests/native-launch.test.ts": 2228, + "packages/acp/tests/permission-bridge.test.ts": 1263, + "packages/acp/tests/provider.test.ts": 2201, + "packages/acp/tests/serial-queue.test.ts": 523, + "packages/acp/tests/session-key.test.ts": 551, + "packages/acp/tests/terminal-screen.test.ts": 631, + "packages/cli/tests/agent-adapters.test.ts": 2670, + "packages/cli/tests/agent-cli.test.ts": 43979, + "packages/cli/tests/agent-config.test.ts": 517, + "packages/cli/tests/agent-options-cli.test.ts": 16174, + "packages/cli/tests/agent-options.test.ts": 501, + "packages/cli/tests/agent-session-coordinator.test.ts": 1387, + "packages/cli/tests/cli-help.test.ts": 32104, + "packages/cli/tests/command.test.ts": 15186, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4415, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 173470, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3841, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 10176, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 6313, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 18507, + "packages/cli/tests/evaluate-component.test.ts": 15891, + "packages/cli/tests/fetch-cli.test.ts": 5238, + "packages/cli/tests/file-stream.test.ts": 565, + "packages/cli/tests/inline-cli.test.ts": 68905, + "packages/cli/tests/launch-timeout.test.ts": 20589, + "packages/cli/tests/packaged-document.test.ts": 1650, + "packages/cli/tests/plan-args.test.ts": 527, + "packages/cli/tests/plan-cli.test.ts": 119896, + "packages/cli/tests/plan-command-document.test.ts": 8523, + "packages/cli/tests/plan-component.test.ts": 11377, + "packages/cli/tests/plan-host-acts.test.ts": 2491, + "packages/cli/tests/plan.test.ts": 17496, + "packages/cli/tests/plugin-cli.test.ts": 84298, + "packages/cli/tests/plugin-host.test.ts": 3336, + "packages/cli/tests/plugin-modules.test.ts": 518, + "packages/cli/tests/plugin-selection.test.ts": 532, + "packages/cli/tests/process-retention.test.ts": 4989, + "packages/cli/tests/props-cli.test.ts": 64805, + "packages/cli/tests/props-schema.test.ts": 1407, + "packages/cli/tests/props-sources.test.ts": 756, + "packages/cli/tests/repl-model.test.ts": 2051, + "packages/cli/tests/repl-route.test.ts": 1776, + "packages/cli/tests/run-composition.test.ts": 4239, + "packages/cli/tests/run-deadline.test.ts": 3739, + "packages/cli/tests/run-profile.test.ts": 1295, + "packages/cli/tests/run-timeouts.test.ts": 20391, + "packages/cli/tests/secret-detection-cli.test.ts": 38934, + "packages/cli/tests/service-document.test.ts": 2716, + "packages/cli/tests/service-host.test.ts": 2416, + "packages/cli/tests/session-launch-cli.test.ts": 11239, + "packages/cli/tests/stdin-cli.test.ts": 64805, + "packages/cli/tests/stdout-delivery.test.ts": 552, + "packages/cli/tests/syntax-cli.test.ts": 64723, + "packages/cli/tests/targets-cli.test.ts": 52116, + "packages/cli/tests/test-root-profile.test.ts": 7226, + "packages/cli/tests/test-target.test.ts": 75459, + "packages/cli/tests/testing-activation.test.ts": 1566, + "packages/cli/tests/testing-execution-host.test.ts": 92896, + "packages/cli/tests/upgrade-cli.test.ts": 46862, + "packages/cli/tests/upgrade-command-document.test.ts": 5553, + "packages/cli/tests/upgrade-output.test.ts": 1917, + "packages/cli/tests/value-root.test.ts": 32112, + "packages/cli/tests/verbose-component.test.ts": 2099, + "packages/cli/tests/workflow-declaration.test.ts": 1555, + "packages/cli/tests/workflow-host.test.ts": 11000, + "packages/code-review-agent/tests/doctor.test.ts": 514, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 579, + "packages/code-review-agent/tests/parse-diff.test.ts": 542, + "packages/code-review-agent/tests/parse-doctor.test.ts": 1223, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 507, + "packages/code-review-agent/tests/policy.test.ts": 536, + "packages/core/tests/agent-api.test.ts": 509, + "packages/core/tests/agent-components.test.ts": 2131, + "packages/core/tests/agent-function-components.test.ts": 4027, + "packages/core/tests/agent-session-launch.test.ts": 4993, + "packages/core/tests/agent-session-placement.test.ts": 522, + "packages/core/tests/agent-session-use.test.ts": 502, + "packages/core/tests/agent-stack.test.ts": 1572, + "packages/core/tests/answer-identity.test.ts": 540, + "packages/core/tests/answers-component.test.ts": 2485, + "packages/core/tests/answers-expansion-recursion.test.ts": 1024, + "packages/core/tests/capture-errors.test.ts": 1308, + "packages/core/tests/capture-props.test.ts": 1055, + "packages/core/tests/cli-journal.test.ts": 22539, + "packages/core/tests/code-block-component.test.ts": 3478, + "packages/core/tests/compiler-boundary.test.ts": 1234, + "packages/core/tests/component-api.test.ts": 597, + "packages/core/tests/component-registration.test.ts": 2309, + "packages/core/tests/component-returns.test.ts": 3124, + "packages/core/tests/component-schema-conformance.test.ts": 1182, + "packages/core/tests/config-api.test.ts": 1317, + "packages/core/tests/construct-error-observation.test.ts": 1379, + "packages/core/tests/contextual-cwd.test.ts": 2521, + "packages/core/tests/daemon-integration.test.ts": 1970, + "packages/core/tests/daemon.test.ts": 625, + "packages/core/tests/declared-markdown-component.test.ts": 3351, + "packages/core/tests/document-output-api.test.ts": 534, + "packages/core/tests/document-target-execution.test.ts": 4962, + "packages/core/tests/document-targets.test.ts": 1241, + "packages/core/tests/document-validation.test.ts": 3243, + "packages/core/tests/documentation-index.test.ts": 867, + "packages/core/tests/each.test.ts": 1309, + "packages/core/tests/elicit-component.test.ts": 2090, + "packages/core/tests/ephemeral-service.test.ts": 1853, + "packages/core/tests/eval-bindings.test.ts": 1295, + "packages/core/tests/eval-context.test.ts": 622, + "packages/core/tests/eval-durable.test.ts": 1347, + "packages/core/tests/eval-error-mode.test.ts": 1701, + "packages/core/tests/eval-interpolate.test.ts": 540, + "packages/core/tests/eval-middleware.test.ts": 633, + "packages/core/tests/eval-persist.test.ts": 1469, + "packages/core/tests/eval-return.test.ts": 1315, + "packages/core/tests/eval-scope.test.ts": 1183, + "packages/core/tests/eval-timeout.test.ts": 1284, + "packages/core/tests/eval-transform.test.ts": 585, + "packages/core/tests/evaluate-component.test.ts": 3901, + "packages/core/tests/evaluate-loaded-copy.test.ts": 1459, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 1526, + "packages/core/tests/evaluation-profile.test.ts": 961, + "packages/core/tests/exec-timeout.test.ts": 1416, + "packages/core/tests/execute.test.ts": 2147, + "packages/core/tests/execution-protocol.test.ts": 3102, + "packages/core/tests/expand.test.ts": 2233, + "packages/core/tests/expansion-identity.test.ts": 2077, + "packages/core/tests/expansion-metadata.test.ts": 1053, + "packages/core/tests/expression-props.test.ts": 2047, + "packages/core/tests/fail-component.test.ts": 1746, + "packages/core/tests/failure-printing.test.ts": 1588, + "packages/core/tests/fatal-cause.test.ts": 944, + "packages/core/tests/fetch-component.test.ts": 3151, + "packages/core/tests/file-component.test.ts": 3546, + "packages/core/tests/file-delete-component.test.ts": 1559, + "packages/core/tests/files-fatal.test.ts": 2620, + "packages/core/tests/foreground-exec.test.ts": 2344, + "packages/core/tests/frontmatter.test.ts": 556, + "packages/core/tests/function-components.test.ts": 2038, + "packages/core/tests/generated-composition.test.ts": 2063, + "packages/core/tests/generated-xmd.test.ts": 3739, + "packages/core/tests/glob-api.test.ts": 593, + "packages/core/tests/glob-component.test.ts": 2083, + "packages/core/tests/guarded-journal.test.ts": 1342, + "packages/core/tests/has-content.test.ts": 968, + "packages/core/tests/heal.test.ts": 938, + "packages/core/tests/if.test.ts": 1843, + "packages/core/tests/inline-root.test.ts": 1330, + "packages/core/tests/invocation-failures.test.ts": 605, + "packages/core/tests/invocation-identity.test.ts": 1749, + "packages/core/tests/invocation-scope.test.ts": 1205, + "packages/core/tests/journal-source-position.test.ts": 1462, + "packages/core/tests/json-component.test.ts": 1571, + "packages/core/tests/json.test.ts": 502, + "packages/core/tests/let.test.ts": 1315, + "packages/core/tests/loop.test.ts": 2324, + "packages/core/tests/named-slots.test.ts": 2326, + "packages/core/tests/output-error-mode.test.ts": 2734, + "packages/core/tests/output-normalize.test.ts": 526, + "packages/core/tests/output-terminal.test.ts": 714, + "packages/core/tests/parse-components.test.ts": 1453, + "packages/core/tests/plan-response.test.ts": 617, + "packages/core/tests/plugin-api.test.ts": 538, + "packages/core/tests/plugin.test.ts": 1018, + "packages/core/tests/props-binding.test.ts": 1539, + "packages/core/tests/protected-content.test.ts": 560, + "packages/core/tests/registered-printed-errors.test.ts": 837, + "packages/core/tests/replay-stream.test.ts": 500, + "packages/core/tests/retain.test.ts": 1172, + "packages/core/tests/root-composition.test.ts": 1425, + "packages/core/tests/root-props.test.ts": 1523, + "packages/core/tests/root-provider.test.ts": 1369, + "packages/core/tests/sample-component.test.ts": 2479, + "packages/core/tests/scanner.test.ts": 617, + "packages/core/tests/scope-local.test.ts": 509, + "packages/core/tests/secret-detection.test.ts": 2164, + "packages/core/tests/secret-files.test.ts": 772, + "packages/core/tests/secret-rules.test.ts": 860, + "packages/core/tests/secret-scanner-baseline.test.ts": 763, + "packages/core/tests/secret-scanner.test.ts": 714, + "packages/core/tests/source-position.test.ts": 1147, + "packages/core/tests/state-ownership.test.ts": 1227, + "packages/core/tests/streaming-emission.test.ts": 1200, + "packages/core/tests/switch.test.ts": 1961, + "packages/core/tests/syntax-catalog.test.ts": 1919, + "packages/core/tests/syntax-component.test.ts": 2793, + "packages/core/tests/temp-dir.test.ts": 3811, + "packages/core/tests/temp-file-compiler.test.ts": 565, + "packages/core/tests/text-interpolation.test.ts": 922, + "packages/core/tests/try-content.test.ts": 991, + "packages/core/tests/unused-in-diff.test.ts": 1466, + "packages/core/tests/validate.test.ts": 867, + "packages/core/tests/validation-integration.test.ts": 1231, + "packages/core/tests/workflow-component-bundle.test.ts": 1608, + "packages/durable-streams/tests/context.test.ts": 541, + "packages/durable-streams/tests/deterministic-id.test.ts": 550, + "packages/durable-streams/tests/divergence-api.test.ts": 554, + "packages/durable-streams/tests/divergence.test.ts": 579, + "packages/durable-streams/tests/durable-each.test.ts": 573, + "packages/durable-streams/tests/durable-position.test.ts": 638, + "packages/durable-streams/tests/durable-run.test.ts": 587, + "packages/durable-streams/tests/ephemeral.test.ts": 588, + "packages/durable-streams/tests/fail-stop.test.ts": 602, + "packages/durable-streams/tests/guard-stream.test.ts": 665, + "packages/durable-streams/tests/live-coordinator.test.ts": 600, + "packages/durable-streams/tests/parse.test.ts": 624, + "packages/durable-streams/tests/replay-guard.test.ts": 618, + "packages/durable-streams/tests/replay-index.test.ts": 598, + "packages/durable-streams/tests/retained.test.ts": 584, + "packages/durable-streams/tests/serialize-event.test.ts": 598, + "packages/durable-streams/tests/smoke.test.ts": 569, + "packages/durable-streams/tests/structured-concurrency.test.ts": 649, + "packages/durable-streams/tests/terminal-boundary.test.ts": 636, + "packages/durable-streams/tests/types.test.ts": 565, + "packages/git/tests/api-entrypoint.test.ts": 1373, + "packages/git/tests/git-host-effect.test.ts": 8061, + "packages/git/tests/git.test.ts": 1410, + "packages/git/tests/github-activation.test.ts": 1760, + "packages/git/tests/github-issues.test.ts": 1292, + "packages/git/tests/github-pull-requests.test.ts": 654, + "packages/git/tests/issue-markdown.test.ts": 11317, + "packages/git/tests/issue-records.test.ts": 1251, + "packages/git/tests/module-partition.test.ts": 3350, + "packages/git/tests/plugin.test.ts": 1761, + "packages/git/tests/provider-neutrality.test.ts": 2408, + "packages/git/tests/pull-request-records.test.ts": 1324, + "packages/git/tests/run-composition-lazy.test.ts": 2112, + "packages/git/tests/selection-authentication.test.ts": 513, + "packages/runtime/tests/duration.test.ts": 511, + "packages/runtime/tests/fetch.test.ts": 1132, + "packages/runtime/tests/fs.test.ts": 576, + "packages/runtime/tests/host-files.test.ts": 1330, + "packages/runtime/tests/native-launcher.test.ts": 2888, + "packages/runtime/tests/process.test.ts": 563, + "packages/runtime/tests/service.test.ts": 548, + "packages/test-agent/tests/acp-server.test.ts": 701, + "packages/test-agent/tests/behavior-engine.test.ts": 1604, + "packages/test-agent/tests/bridge.test.ts": 552, + "packages/test-agent/tests/command-lazy.test.ts": 6773, + "packages/test-agent/tests/components.test.ts": 108515, + "packages/test-agent/tests/controller.test.ts": 855, + "packages/test-agent/tests/cross-package-resolution.test.ts": 3059, + "packages/test-agent/tests/native-launch.test.ts": 54782, + "packages/test-agent/tests/net.test.ts": 618, + "packages/test-agent/tests/profile.test.ts": 1220, + "packages/test-agent/tests/protocol.test.ts": 588, + "packages/test-agent/tests/provider.test.ts": 1397, + "packages/test-agent/tests/public-api.test.ts": 1594, + "packages/test-agent/tests/route-slot.test.ts": 507, + "packages/test-agent/tests/smoke.test.ts": 28493, + "packages/test-agent/tests/template.test.ts": 1188, + "packages/test-agent/tests/worker-lifecycle.test.ts": 23244, + "packages/test-support/tests/journal.test.ts": 497, + "packages/testing/tests/agent-composition.test.ts": 1482, + "packages/testing/tests/assert-throws.test.ts": 3162, + "packages/testing/tests/assert.test.ts": 535, + "packages/testing/tests/assertions.test.ts": 3869, + "packages/testing/tests/boundary-contract.test.ts": 2501, + "packages/testing/tests/cli.test.ts": 10766, + "packages/testing/tests/execution-harness.test.ts": 7696, + "packages/testing/tests/replay.test.ts": 3028, + "packages/testing/tests/smoke.test.ts": 3214, + "packages/testing/tests/test-component.test.ts": 3887, + "packages/testing/tests/testing-activation.test.ts": 3459, + "packages/testing/tests/testing-boundary.test.ts": 1997, + "packages/testing/tests/testing-mode.test.ts": 4063, + "packages/testing/tests/use-testing.test.ts": 2763, + "packages/web/tests/assets.test.ts": 489, + "packages/web/tests/client-logic.test.ts": 598, + "packages/web/tests/compile.test.ts": 2023, + "packages/web/tests/component.test.ts": 2477, + "packages/web/tests/declaration.test.ts": 1686, + "packages/web/tests/document.test.ts": 1934, + "packages/web/tests/elicitation.test.ts": 1592, + "packages/web/tests/live-form.test.ts": 1513, + "packages/web/tests/markdown.test.ts": 714, + "packages/web/tests/opener.test.ts": 1546, + "packages/web/tests/page.test.ts": 511, + "packages/web/tests/responder.test.ts": 1867, + "packages/web/tests/server-lifecycle.test.ts": 1721, + "packages/web/tests/server.test.ts": 2090, + "packages/workflow/tests/generated-observations.test.ts": 1461, + "packages/workflow/tests/retained-run.test.ts": 2253, + "packages/workflow/tests/service-denial.test.ts": 531, + "packages/workflow/tests/workflow-bundle.test.ts": 1718, + "packages/workflow/tests/workflow-definition.test.ts": 1394, + "packages/workflow/tests/workflow-fork.test.ts": 1300, + "packages/workflow/tests/workflow-run.test.ts": 2198, + "packages/workflow/tests/workspace-effect.test.ts": 3153, + "packages/workflow/tests/xmd-artifact.test.ts": 3548, + "scripts/tests/adapter-vendor.test.ts": 739, + "scripts/tests/bootstrap-npm-package.test.ts": 8625, + "scripts/tests/bump-version.test.ts": 629, + "scripts/tests/changed-paths.test.ts": 522, + "scripts/tests/ci-workflow.test.ts": 3800, + "scripts/tests/consumer-cycle.test.ts": 708, + "scripts/tests/filesystem-contract-workflow.test.ts": 577, + "scripts/tests/main-green.test.ts": 791, + "scripts/tests/main-health.test.ts": 633, + "scripts/tests/no-module-scoped-registry.test.ts": 1344, + "scripts/tests/no-redundant-test-scope.test.ts": 5543, + "scripts/tests/no-sync-filesystem.test.ts": 5841, + "scripts/tests/no-yield-in-finally.test.ts": 1350, + "scripts/tests/oxlint-policy.test.ts": 11045, + "scripts/tests/packaged-document.test.ts": 600, + "scripts/tests/prefer-effection-operation.test.ts": 6508, + "scripts/tests/prefer-effection-result.test.ts": 4627, + "scripts/tests/publish-workflow-membership.test.ts": 673, + "scripts/tests/review-infrastructure.test.ts": 3425, + "scripts/tests/runtime-exclusions.test.ts": 1033, + "scripts/tests/runtime-tests.test.ts": 548, + "scripts/tests/scope-bound-event-registration.test.ts": 5453, + "scripts/tests/staged-write.test.ts": 539, + "scripts/tests/test-file-discovery.test.ts": 818, + "scripts/tests/test-shards.test.ts": 551, + "scripts/tests/test-weights.test.ts": 713, + "scripts/tests/tracked.test.ts": 524, + "scripts/tests/verify-coordinator.test.ts": 740, + "scripts/tests/version-lockstep.test.ts": 737, + "scripts/tests/web-client-module.test.ts": 548, + "scripts/tests/workspace.test.ts": 520 }, "bun": { - "packages/acp/tests/acceptance.test.ts": 800, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4336, - "packages/acp/tests/acpx-materialization.test.ts": 6545, - "packages/acp/tests/acpx-transient-env.test.ts": 275, - "packages/acp/tests/adapter-materialization.test.ts": 68502, - "packages/acp/tests/adapter-protocol.test.ts": 51467, - "packages/acp/tests/codex-surfaces.test.ts": 129, - "packages/acp/tests/native-launch.test.ts": 1033, - "packages/acp/tests/permission-bridge.test.ts": 360, - "packages/acp/tests/provider.test.ts": 1111, - "packages/acp/tests/serial-queue.test.ts": 67, - "packages/acp/tests/session-key.test.ts": 68, - "packages/acp/tests/terminal-screen.test.ts": 89, - "packages/cli/tests/agent-adapters.test.ts": 1143, - "packages/cli/tests/agent-cli.test.ts": 19308, - "packages/cli/tests/agent-config.test.ts": 30, - "packages/cli/tests/agent-options-cli.test.ts": 7322, - "packages/cli/tests/agent-options.test.ts": 34, - "packages/cli/tests/agent-session-coordinator.test.ts": 399, - "packages/cli/tests/cli-help.test.ts": 11628, - "packages/cli/tests/command.test.ts": 7784, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 2776, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 103100, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2302, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 4895, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 4707, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 8518, - "packages/cli/tests/evaluate-component.test.ts": 7384, - "packages/cli/tests/fetch-cli.test.ts": 2242, - "packages/cli/tests/file-stream.test.ts": 60, - "packages/cli/tests/inline-cli.test.ts": 30477, - "packages/cli/tests/launch-timeout.test.ts": 10085, - "packages/cli/tests/packaged-document.test.ts": 450, - "packages/cli/tests/plan-args.test.ts": 34, - "packages/cli/tests/plan-cli.test.ts": 53900, - "packages/cli/tests/plan-command-document.test.ts": 5403, - "packages/cli/tests/plan-component.test.ts": 8654, - "packages/cli/tests/plan-host-acts.test.ts": 1131, - "packages/cli/tests/plan.test.ts": 12257, - "packages/cli/tests/plugin-cli.test.ts": 37306, - "packages/cli/tests/plugin-host.test.ts": 1956, - "packages/cli/tests/plugin-modules.test.ts": 37, - "packages/cli/tests/plugin-selection.test.ts": 52, - "packages/cli/tests/process-retention.test.ts": 2272, - "packages/cli/tests/props-cli.test.ts": 27218, - "packages/cli/tests/props-schema.test.ts": 422, - "packages/cli/tests/props-sources.test.ts": 127, - "packages/cli/tests/run-composition.test.ts": 2863, - "packages/cli/tests/run-deadline.test.ts": 2337, - "packages/cli/tests/run-profile.test.ts": 317, - "packages/cli/tests/run-timeouts.test.ts": 10981, - "packages/cli/tests/secret-detection-cli.test.ts": 17255, - "packages/cli/tests/service-document.test.ts": 1535, - "packages/cli/tests/service-host.test.ts": 1402, - "packages/cli/tests/session-launch-cli.test.ts": 5111, - "packages/cli/tests/stdin-cli.test.ts": 29679, + "packages/acp/tests/acceptance.test.ts": 821, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4324, + "packages/acp/tests/acpx-materialization.test.ts": 6516, + "packages/acp/tests/acpx-transient-env.test.ts": 265, + "packages/acp/tests/adapter-materialization.test.ts": 71519, + "packages/acp/tests/adapter-protocol.test.ts": 54748, + "packages/acp/tests/codex-surfaces.test.ts": 133, + "packages/acp/tests/native-launch.test.ts": 1034, + "packages/acp/tests/permission-bridge.test.ts": 373, + "packages/acp/tests/provider.test.ts": 1134, + "packages/acp/tests/serial-queue.test.ts": 68, + "packages/acp/tests/session-key.test.ts": 70, + "packages/acp/tests/terminal-screen.test.ts": 90, + "packages/cli/tests/agent-adapters.test.ts": 1205, + "packages/cli/tests/agent-cli.test.ts": 21400, + "packages/cli/tests/agent-config.test.ts": 29, + "packages/cli/tests/agent-options-cli.test.ts": 7787, + "packages/cli/tests/agent-options.test.ts": 35, + "packages/cli/tests/agent-session-coordinator.test.ts": 407, + "packages/cli/tests/cli-help.test.ts": 11902, + "packages/cli/tests/command.test.ts": 8035, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 3053, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 106303, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2319, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 5178, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 4845, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 9026, + "packages/cli/tests/evaluate-component.test.ts": 7930, + "packages/cli/tests/fetch-cli.test.ts": 2327, + "packages/cli/tests/file-stream.test.ts": 58, + "packages/cli/tests/inline-cli.test.ts": 32416, + "packages/cli/tests/launch-timeout.test.ts": 10094, + "packages/cli/tests/packaged-document.test.ts": 474, + "packages/cli/tests/plan-args.test.ts": 37, + "packages/cli/tests/plan-cli.test.ts": 59014, + "packages/cli/tests/plan-command-document.test.ts": 6025, + "packages/cli/tests/plan-component.test.ts": 9085, + "packages/cli/tests/plan-host-acts.test.ts": 1201, + "packages/cli/tests/plan.test.ts": 13416, + "packages/cli/tests/plugin-cli.test.ts": 40990, + "packages/cli/tests/plugin-host.test.ts": 2257, + "packages/cli/tests/plugin-modules.test.ts": 40, + "packages/cli/tests/plugin-selection.test.ts": 39, + "packages/cli/tests/process-retention.test.ts": 2565, + "packages/cli/tests/props-cli.test.ts": 30520, + "packages/cli/tests/props-schema.test.ts": 456, + "packages/cli/tests/props-sources.test.ts": 138, + "packages/cli/tests/repl-model.test.ts": 1013, + "packages/cli/tests/repl-route.test.ts": 844, + "packages/cli/tests/run-composition.test.ts": 3235, + "packages/cli/tests/run-deadline.test.ts": 2457, + "packages/cli/tests/run-profile.test.ts": 318, + "packages/cli/tests/run-timeouts.test.ts": 11790, + "packages/cli/tests/secret-detection-cli.test.ts": 19433, + "packages/cli/tests/service-document.test.ts": 1671, + "packages/cli/tests/service-host.test.ts": 1496, + "packages/cli/tests/session-launch-cli.test.ts": 5930, + "packages/cli/tests/stdin-cli.test.ts": 32991, "packages/cli/tests/stdout-delivery.test.ts": 55, - "packages/cli/tests/syntax-cli.test.ts": 32095, - "packages/cli/tests/targets-cli.test.ts": 21119, - "packages/cli/tests/test-root-profile.test.ts": 3397, - "packages/cli/tests/test-target.test.ts": 34941, - "packages/cli/tests/testing-activation.test.ts": 564, - "packages/cli/tests/testing-execution-host.test.ts": 48481, - "packages/cli/tests/upgrade-cli.test.ts": 17488, - "packages/cli/tests/upgrade-command-document.test.ts": 3465, - "packages/cli/tests/upgrade-output.test.ts": 790, - "packages/cli/tests/value-root.test.ts": 15061, - "packages/cli/tests/verbose-component.test.ts": 794, - "packages/cli/tests/workflow-declaration.test.ts": 520, - "packages/cli/tests/workflow-host.test.ts": 3849, - "packages/code-review-agent/tests/doctor.test.ts": 30, + "packages/cli/tests/syntax-cli.test.ts": 34400, + "packages/cli/tests/targets-cli.test.ts": 22723, + "packages/cli/tests/test-root-profile.test.ts": 3844, + "packages/cli/tests/test-target.test.ts": 37047, + "packages/cli/tests/testing-activation.test.ts": 591, + "packages/cli/tests/testing-execution-host.test.ts": 49882, + "packages/cli/tests/upgrade-cli.test.ts": 18343, + "packages/cli/tests/upgrade-command-document.test.ts": 3542, + "packages/cli/tests/upgrade-output.test.ts": 849, + "packages/cli/tests/value-root.test.ts": 17019, + "packages/cli/tests/verbose-component.test.ts": 872, + "packages/cli/tests/workflow-declaration.test.ts": 576, + "packages/cli/tests/workflow-host.test.ts": 4226, + "packages/code-review-agent/tests/doctor.test.ts": 31, "packages/code-review-agent/tests/parse-diagnostics.test.ts": 41, - "packages/code-review-agent/tests/parse-diff.test.ts": 35, - "packages/code-review-agent/tests/parse-doctor.test.ts": 299, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 28, - "packages/code-review-agent/tests/policy.test.ts": 31, - "packages/core/tests/agent-api.test.ts": 35, - "packages/core/tests/agent-components.test.ts": 1183, - "packages/core/tests/agent-function-components.test.ts": 2848, - "packages/core/tests/agent-session-launch.test.ts": 3240, + "packages/code-review-agent/tests/parse-diff.test.ts": 38, + "packages/code-review-agent/tests/parse-doctor.test.ts": 310, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 32, + "packages/code-review-agent/tests/policy.test.ts": 33, + "packages/core/tests/agent-api.test.ts": 39, + "packages/core/tests/agent-components.test.ts": 1313, + "packages/core/tests/agent-function-components.test.ts": 3254, + "packages/core/tests/agent-session-launch.test.ts": 3517, "packages/core/tests/agent-session-placement.test.ts": 37, - "packages/core/tests/agent-session-use.test.ts": 33, - "packages/core/tests/agent-stack.test.ts": 685, - "packages/core/tests/answer-identity.test.ts": 40, - "packages/core/tests/answers-component.test.ts": 1325, - "packages/core/tests/answers-expansion-recursion.test.ts": 313, - "packages/core/tests/capture-errors.test.ts": 543, - "packages/core/tests/capture-props.test.ts": 322, - "packages/core/tests/cli-journal.test.ts": 9827, - "packages/core/tests/code-block-component.test.ts": 2310, - "packages/core/tests/compiler-boundary.test.ts": 361, - "packages/core/tests/component-api.test.ts": 56, - "packages/core/tests/component-registration.test.ts": 1118, - "packages/core/tests/component-returns.test.ts": 1607, - "packages/core/tests/component-schema-conformance.test.ts": 520, - "packages/core/tests/config-api.test.ts": 338, - "packages/core/tests/construct-error-observation.test.ts": 660, - "packages/core/tests/contextual-cwd.test.ts": 1510, - "packages/core/tests/daemon-integration.test.ts": 1015, - "packages/core/tests/daemon.test.ts": 70, - "packages/core/tests/declared-markdown-component.test.ts": 1999, - "packages/core/tests/document-output-api.test.ts": 89, - "packages/core/tests/document-target-execution.test.ts": 3485, - "packages/core/tests/document-targets.test.ts": 403, - "packages/core/tests/document-validation.test.ts": 2028, - "packages/core/tests/documentation-index.test.ts": 157, - "packages/core/tests/each.test.ts": 415, - "packages/core/tests/elicit-component.test.ts": 913, - "packages/core/tests/ephemeral-service.test.ts": 832, - "packages/core/tests/eval-bindings.test.ts": 385, - "packages/core/tests/eval-context.test.ts": 90, - "packages/core/tests/eval-durable.test.ts": 439, - "packages/core/tests/eval-error-mode.test.ts": 679, - "packages/core/tests/eval-interpolate.test.ts": 37, - "packages/core/tests/eval-middleware.test.ts": 72, - "packages/core/tests/eval-persist.test.ts": 489, - "packages/core/tests/eval-return.test.ts": 415, - "packages/core/tests/eval-scope.test.ts": 333, - "packages/core/tests/eval-timeout.test.ts": 437, - "packages/core/tests/eval-transform.test.ts": 61, - "packages/core/tests/evaluate-component.test.ts": 2460, - "packages/core/tests/evaluate-loaded-copy.test.ts": 491, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 570, - "packages/core/tests/evaluation-profile.test.ts": 177, - "packages/core/tests/exec-timeout.test.ts": 471, - "packages/core/tests/execute.test.ts": 1020, - "packages/core/tests/execution-protocol.test.ts": 1724, - "packages/core/tests/expand.test.ts": 1168, - "packages/core/tests/expansion-identity.test.ts": 1166, - "packages/core/tests/expansion-metadata.test.ts": 338, - "packages/core/tests/expression-props.test.ts": 831, - "packages/core/tests/fail-component.test.ts": 733, - "packages/core/tests/failure-printing.test.ts": 665, - "packages/core/tests/fatal-cause.test.ts": 183, - "packages/core/tests/fetch-component.test.ts": 1649, - "packages/core/tests/file-component.test.ts": 2368, - "packages/core/tests/file-delete-component.test.ts": 604, - "packages/core/tests/files-fatal.test.ts": 1417, - "packages/core/tests/foreground-exec.test.ts": 1233, - "packages/core/tests/frontmatter.test.ts": 48, - "packages/core/tests/function-components.test.ts": 910, - "packages/core/tests/generated-composition.test.ts": 1101, - "packages/core/tests/generated-xmd.test.ts": 2251, - "packages/core/tests/glob-api.test.ts": 95, - "packages/core/tests/glob-component.test.ts": 1220, - "packages/core/tests/guarded-journal.test.ts": 443, - "packages/core/tests/has-content.test.ts": 269, - "packages/core/tests/heal.test.ts": 257, - "packages/core/tests/if.test.ts": 810, - "packages/core/tests/inline-root.test.ts": 500, - "packages/core/tests/invocation-failures.test.ts": 104, - "packages/core/tests/invocation-identity.test.ts": 777, - "packages/core/tests/invocation-scope.test.ts": 513, - "packages/core/tests/journal-source-position.test.ts": 581, - "packages/core/tests/json-component.test.ts": 2440, + "packages/core/tests/agent-session-use.test.ts": 35, + "packages/core/tests/agent-stack.test.ts": 726, + "packages/core/tests/answer-identity.test.ts": 43, + "packages/core/tests/answers-component.test.ts": 1468, + "packages/core/tests/answers-expansion-recursion.test.ts": 352, + "packages/core/tests/capture-errors.test.ts": 661, + "packages/core/tests/capture-props.test.ts": 376, + "packages/core/tests/cli-journal.test.ts": 10850, + "packages/core/tests/code-block-component.test.ts": 2506, + "packages/core/tests/compiler-boundary.test.ts": 396, + "packages/core/tests/component-api.test.ts": 64, + "packages/core/tests/component-registration.test.ts": 1212, + "packages/core/tests/component-returns.test.ts": 1703, + "packages/core/tests/component-schema-conformance.test.ts": 610, + "packages/core/tests/config-api.test.ts": 383, + "packages/core/tests/construct-error-observation.test.ts": 795, + "packages/core/tests/contextual-cwd.test.ts": 1578, + "packages/core/tests/daemon-integration.test.ts": 1073, + "packages/core/tests/daemon.test.ts": 83, + "packages/core/tests/declared-markdown-component.test.ts": 2271, + "packages/core/tests/document-output-api.test.ts": 39, + "packages/core/tests/document-target-execution.test.ts": 3973, + "packages/core/tests/document-targets.test.ts": 460, + "packages/core/tests/document-validation.test.ts": 2332, + "packages/core/tests/documentation-index.test.ts": 176, + "packages/core/tests/each.test.ts": 467, + "packages/core/tests/elicit-component.test.ts": 1256, + "packages/core/tests/ephemeral-service.test.ts": 906, + "packages/core/tests/eval-bindings.test.ts": 424, + "packages/core/tests/eval-context.test.ts": 98, + "packages/core/tests/eval-durable.test.ts": 487, + "packages/core/tests/eval-error-mode.test.ts": 734, + "packages/core/tests/eval-interpolate.test.ts": 40, + "packages/core/tests/eval-middleware.test.ts": 79, + "packages/core/tests/eval-persist.test.ts": 551, + "packages/core/tests/eval-return.test.ts": 467, + "packages/core/tests/eval-scope.test.ts": 411, + "packages/core/tests/eval-timeout.test.ts": 482, + "packages/core/tests/eval-transform.test.ts": 67, + "packages/core/tests/evaluate-component.test.ts": 2800, + "packages/core/tests/evaluate-loaded-copy.test.ts": 571, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 679, + "packages/core/tests/evaluation-profile.test.ts": 202, + "packages/core/tests/exec-timeout.test.ts": 514, + "packages/core/tests/execute.test.ts": 1160, + "packages/core/tests/execution-protocol.test.ts": 1823, + "packages/core/tests/expand.test.ts": 1424, + "packages/core/tests/expansion-identity.test.ts": 1356, + "packages/core/tests/expansion-metadata.test.ts": 388, + "packages/core/tests/expression-props.test.ts": 991, + "packages/core/tests/fail-component.test.ts": 810, + "packages/core/tests/failure-printing.test.ts": 773, + "packages/core/tests/fatal-cause.test.ts": 199, + "packages/core/tests/fetch-component.test.ts": 1938, + "packages/core/tests/file-component.test.ts": 2522, + "packages/core/tests/file-delete-component.test.ts": 701, + "packages/core/tests/files-fatal.test.ts": 1706, + "packages/core/tests/foreground-exec.test.ts": 1362, + "packages/core/tests/frontmatter.test.ts": 50, + "packages/core/tests/function-components.test.ts": 1015, + "packages/core/tests/generated-composition.test.ts": 1298, + "packages/core/tests/generated-xmd.test.ts": 2540, + "packages/core/tests/glob-api.test.ts": 110, + "packages/core/tests/glob-component.test.ts": 1326, + "packages/core/tests/guarded-journal.test.ts": 493, + "packages/core/tests/has-content.test.ts": 303, + "packages/core/tests/heal.test.ts": 283, + "packages/core/tests/if.test.ts": 933, + "packages/core/tests/inline-root.test.ts": 559, + "packages/core/tests/invocation-failures.test.ts": 118, + "packages/core/tests/invocation-identity.test.ts": 867, + "packages/core/tests/invocation-scope.test.ts": 593, + "packages/core/tests/journal-source-position.test.ts": 593, + "packages/core/tests/json-component.test.ts": 2433, "packages/core/tests/json.test.ts": 34, - "packages/core/tests/let.test.ts": 421, - "packages/core/tests/loop.test.ts": 1166, - "packages/core/tests/named-slots.test.ts": 1266, - "packages/core/tests/output-error-mode.test.ts": 1549, - "packages/core/tests/output-normalize.test.ts": 41, - "packages/core/tests/output-terminal.test.ts": 158, - "packages/core/tests/parse-components.test.ts": 654, - "packages/core/tests/plan-response.test.ts": 92, - "packages/core/tests/plugin-api.test.ts": 45, - "packages/core/tests/plugin.test.ts": 192, - "packages/core/tests/props-binding.test.ts": 547, - "packages/core/tests/protected-content.test.ts": 55, - "packages/core/tests/registered-printed-errors.test.ts": 205, - "packages/core/tests/replay-stream.test.ts": 37, - "packages/core/tests/retain.test.ts": 470, - "packages/core/tests/root-composition.test.ts": 536, - "packages/core/tests/root-props.test.ts": 555, - "packages/core/tests/root-provider.test.ts": 553, - "packages/core/tests/sample-component.test.ts": 1095, - "packages/core/tests/scanner.test.ts": 74, - "packages/core/tests/scope-local.test.ts": 34, - "packages/core/tests/secret-detection.test.ts": 1100, - "packages/core/tests/secret-files.test.ts": 127, - "packages/core/tests/secret-rules.test.ts": 144, - "packages/core/tests/secret-scanner-baseline.test.ts": 141, - "packages/core/tests/secret-scanner.test.ts": 98, - "packages/core/tests/source-position.test.ts": 316, - "packages/core/tests/state-ownership.test.ts": 395, - "packages/core/tests/streaming-emission.test.ts": 334, - "packages/core/tests/switch.test.ts": 830, - "packages/core/tests/syntax-catalog.test.ts": 839, - "packages/core/tests/syntax-component.test.ts": 1695, - "packages/core/tests/temp-dir.test.ts": 2786, - "packages/core/tests/temp-file-compiler.test.ts": 80, - "packages/core/tests/text-interpolation.test.ts": 238, - "packages/core/tests/try-content.test.ts": 267, - "packages/core/tests/unused-in-diff.test.ts": 460, - "packages/core/tests/validate.test.ts": 282, - "packages/core/tests/validation-integration.test.ts": 373, - "packages/core/tests/workflow-component-bundle.test.ts": 676, - "packages/durable-streams/tests/context.test.ts": 38, - "packages/durable-streams/tests/deterministic-id.test.ts": 66, - "packages/durable-streams/tests/divergence-api.test.ts": 45, - "packages/durable-streams/tests/divergence.test.ts": 56, - "packages/durable-streams/tests/durable-each.test.ts": 64, - "packages/durable-streams/tests/durable-position.test.ts": 52, - "packages/durable-streams/tests/durable-run.test.ts": 64, - "packages/durable-streams/tests/ephemeral.test.ts": 61, - "packages/durable-streams/tests/fail-stop.test.ts": 75, - "packages/durable-streams/tests/guard-stream.test.ts": 103, - "packages/durable-streams/tests/live-coordinator.test.ts": 58, - "packages/durable-streams/tests/parse.test.ts": 48, - "packages/durable-streams/tests/replay-guard.test.ts": 68, - "packages/durable-streams/tests/replay-index.test.ts": 46, - "packages/durable-streams/tests/retained.test.ts": 60, - "packages/durable-streams/tests/serialize-event.test.ts": 87, - "packages/durable-streams/tests/smoke.test.ts": 33, - "packages/durable-streams/tests/structured-concurrency.test.ts": 84, - "packages/durable-streams/tests/terminal-boundary.test.ts": 72, - "packages/durable-streams/tests/types.test.ts": 30, - "packages/git/tests/api-entrypoint.test.ts": 304, - "packages/git/tests/git-host-effect.test.ts": 6170, - "packages/git/tests/git.test.ts": 367, - "packages/git/tests/github-activation.test.ts": 681, - "packages/git/tests/github-issues.test.ts": 302, - "packages/git/tests/github-pull-requests.test.ts": 80, - "packages/git/tests/issue-markdown.test.ts": 8376, - "packages/git/tests/issue-records.test.ts": 295, - "packages/git/tests/module-partition.test.ts": 1766, - "packages/git/tests/plugin.test.ts": 766, - "packages/git/tests/provider-neutrality.test.ts": 1002, - "packages/git/tests/pull-request-records.test.ts": 323, - "packages/git/tests/run-composition-lazy.test.ts": 765, - "packages/git/tests/selection-authentication.test.ts": 40, - "packages/runtime/tests/duration.test.ts": 30, - "packages/runtime/tests/fetch.test.ts": 610, - "packages/runtime/tests/fs.test.ts": 73, - "packages/runtime/tests/host-files.test.ts": 720, - "packages/runtime/tests/native-launcher.test.ts": 2422, - "packages/runtime/tests/process.test.ts": 87, - "packages/runtime/tests/service.test.ts": 66, - "packages/test-agent/tests/acp-server.test.ts": 107, - "packages/test-agent/tests/behavior-engine.test.ts": 638, - "packages/test-agent/tests/bridge.test.ts": 66, - "packages/test-agent/tests/command-lazy.test.ts": 3186, - "packages/test-agent/tests/components.test.ts": 50889, - "packages/test-agent/tests/controller.test.ts": 198, - "packages/test-agent/tests/cross-package-resolution.test.ts": 1764, - "packages/test-agent/tests/native-launch.test.ts": 23433, - "packages/test-agent/tests/net.test.ts": 100, - "packages/test-agent/tests/profile.test.ts": 310, - "packages/test-agent/tests/protocol.test.ts": 59, - "packages/test-agent/tests/provider.test.ts": 395, - "packages/test-agent/tests/public-api.test.ts": 474, - "packages/test-agent/tests/route-slot.test.ts": 58, - "packages/test-agent/tests/smoke.test.ts": 15103, - "packages/test-agent/tests/template.test.ts": 299, - "packages/test-agent/tests/worker-lifecycle.test.ts": 8954, - "packages/test-support/tests/journal.test.ts": 41, - "packages/testing/tests/agent-composition.test.ts": 628, - "packages/testing/tests/assert-throws.test.ts": 2212, - "packages/testing/tests/assert.test.ts": 56, - "packages/testing/tests/assertions.test.ts": 3225, - "packages/testing/tests/boundary-contract.test.ts": 1554, - "packages/testing/tests/cli.test.ts": 4872, - "packages/testing/tests/execution-harness.test.ts": 6686, - "packages/testing/tests/replay.test.ts": 2184, - "packages/testing/tests/smoke.test.ts": 1947, - "packages/testing/tests/test-component.test.ts": 3236, - "packages/testing/tests/testing-activation.test.ts": 2779, - "packages/testing/tests/testing-boundary.test.ts": 1173, - "packages/testing/tests/testing-mode.test.ts": 3280, - "packages/testing/tests/use-testing.test.ts": 1959, - "packages/web/tests/assets.test.ts": 30, - "packages/web/tests/client-logic.test.ts": 38, - "packages/web/tests/compile.test.ts": 981, - "packages/web/tests/component.test.ts": 1513, - "packages/web/tests/declaration.test.ts": 708, - "packages/web/tests/document.test.ts": 782, - "packages/web/tests/elicitation.test.ts": 547, - "packages/web/tests/live-form.test.ts": 528, - "packages/web/tests/markdown.test.ts": 144, - "packages/web/tests/opener.test.ts": 523, + "packages/core/tests/let.test.ts": 469, + "packages/core/tests/loop.test.ts": 1351, + "packages/core/tests/named-slots.test.ts": 1433, + "packages/core/tests/output-error-mode.test.ts": 1677, + "packages/core/tests/output-normalize.test.ts": 44, + "packages/core/tests/output-terminal.test.ts": 226, + "packages/core/tests/parse-components.test.ts": 642, + "packages/core/tests/plan-response.test.ts": 89, + "packages/core/tests/plugin-api.test.ts": 49, + "packages/core/tests/plugin.test.ts": 209, + "packages/core/tests/props-binding.test.ts": 576, + "packages/core/tests/protected-content.test.ts": 54, + "packages/core/tests/registered-printed-errors.test.ts": 223, + "packages/core/tests/replay-stream.test.ts": 40, + "packages/core/tests/retain.test.ts": 507, + "packages/core/tests/root-composition.test.ts": 626, + "packages/core/tests/root-props.test.ts": 639, + "packages/core/tests/root-provider.test.ts": 615, + "packages/core/tests/sample-component.test.ts": 1225, + "packages/core/tests/scanner.test.ts": 80, + "packages/core/tests/scope-local.test.ts": 38, + "packages/core/tests/secret-detection.test.ts": 1209, + "packages/core/tests/secret-files.test.ts": 131, + "packages/core/tests/secret-rules.test.ts": 162, + "packages/core/tests/secret-scanner-baseline.test.ts": 157, + "packages/core/tests/secret-scanner.test.ts": 103, + "packages/core/tests/source-position.test.ts": 368, + "packages/core/tests/state-ownership.test.ts": 514, + "packages/core/tests/streaming-emission.test.ts": 343, + "packages/core/tests/switch.test.ts": 905, + "packages/core/tests/syntax-catalog.test.ts": 1023, + "packages/core/tests/syntax-component.test.ts": 2024, + "packages/core/tests/temp-dir.test.ts": 2872, + "packages/core/tests/temp-file-compiler.test.ts": 79, + "packages/core/tests/text-interpolation.test.ts": 250, + "packages/core/tests/try-content.test.ts": 294, + "packages/core/tests/unused-in-diff.test.ts": 541, + "packages/core/tests/validate.test.ts": 403, + "packages/core/tests/validation-integration.test.ts": 406, + "packages/core/tests/workflow-component-bundle.test.ts": 761, + "packages/durable-streams/tests/context.test.ts": 39, + "packages/durable-streams/tests/deterministic-id.test.ts": 73, + "packages/durable-streams/tests/divergence-api.test.ts": 49, + "packages/durable-streams/tests/divergence.test.ts": 60, + "packages/durable-streams/tests/durable-each.test.ts": 75, + "packages/durable-streams/tests/durable-position.test.ts": 59, + "packages/durable-streams/tests/durable-run.test.ts": 69, + "packages/durable-streams/tests/ephemeral.test.ts": 65, + "packages/durable-streams/tests/fail-stop.test.ts": 83, + "packages/durable-streams/tests/guard-stream.test.ts": 111, + "packages/durable-streams/tests/live-coordinator.test.ts": 62, + "packages/durable-streams/tests/parse.test.ts": 51, + "packages/durable-streams/tests/replay-guard.test.ts": 78, + "packages/durable-streams/tests/replay-index.test.ts": 48, + "packages/durable-streams/tests/retained.test.ts": 47, + "packages/durable-streams/tests/serialize-event.test.ts": 44, + "packages/durable-streams/tests/smoke.test.ts": 37, + "packages/durable-streams/tests/structured-concurrency.test.ts": 106, + "packages/durable-streams/tests/terminal-boundary.test.ts": 122, + "packages/durable-streams/tests/types.test.ts": 41, + "packages/git/tests/api-entrypoint.test.ts": 326, + "packages/git/tests/git-host-effect.test.ts": 7113, + "packages/git/tests/git.test.ts": 389, + "packages/git/tests/github-activation.test.ts": 777, + "packages/git/tests/github-issues.test.ts": 333, + "packages/git/tests/github-pull-requests.test.ts": 86, + "packages/git/tests/issue-markdown.test.ts": 9218, + "packages/git/tests/issue-records.test.ts": 318, + "packages/git/tests/module-partition.test.ts": 1981, + "packages/git/tests/plugin.test.ts": 857, + "packages/git/tests/provider-neutrality.test.ts": 1196, + "packages/git/tests/pull-request-records.test.ts": 343, + "packages/git/tests/run-composition-lazy.test.ts": 883, + "packages/git/tests/selection-authentication.test.ts": 41, + "packages/runtime/tests/duration.test.ts": 32, + "packages/runtime/tests/fetch.test.ts": 629, + "packages/runtime/tests/fs.test.ts": 82, + "packages/runtime/tests/host-files.test.ts": 743, + "packages/runtime/tests/native-launcher.test.ts": 2427, + "packages/runtime/tests/process.test.ts": 95, + "packages/runtime/tests/service.test.ts": 72, + "packages/test-agent/tests/acp-server.test.ts": 120, + "packages/test-agent/tests/behavior-engine.test.ts": 724, + "packages/test-agent/tests/bridge.test.ts": 70, + "packages/test-agent/tests/command-lazy.test.ts": 3509, + "packages/test-agent/tests/components.test.ts": 53673, + "packages/test-agent/tests/controller.test.ts": 202, + "packages/test-agent/tests/cross-package-resolution.test.ts": 1847, + "packages/test-agent/tests/native-launch.test.ts": 24406, + "packages/test-agent/tests/net.test.ts": 108, + "packages/test-agent/tests/profile.test.ts": 312, + "packages/test-agent/tests/protocol.test.ts": 62, + "packages/test-agent/tests/provider.test.ts": 429, + "packages/test-agent/tests/public-api.test.ts": 503, + "packages/test-agent/tests/route-slot.test.ts": 61, + "packages/test-agent/tests/smoke.test.ts": 15578, + "packages/test-agent/tests/template.test.ts": 305, + "packages/test-agent/tests/worker-lifecycle.test.ts": 9301, + "packages/test-support/tests/journal.test.ts": 51, + "packages/testing/tests/agent-composition.test.ts": 696, + "packages/testing/tests/assert-throws.test.ts": 2317, + "packages/testing/tests/assert.test.ts": 57, + "packages/testing/tests/assertions.test.ts": 3071, + "packages/testing/tests/boundary-contract.test.ts": 1602, + "packages/testing/tests/cli.test.ts": 4891, + "packages/testing/tests/execution-harness.test.ts": 6557, + "packages/testing/tests/replay.test.ts": 2287, + "packages/testing/tests/smoke.test.ts": 1970, + "packages/testing/tests/test-component.test.ts": 3101, + "packages/testing/tests/testing-activation.test.ts": 2744, + "packages/testing/tests/testing-boundary.test.ts": 1149, + "packages/testing/tests/testing-mode.test.ts": 3264, + "packages/testing/tests/use-testing.test.ts": 1934, + "packages/web/tests/assets.test.ts": 31, + "packages/web/tests/client-logic.test.ts": 37, + "packages/web/tests/compile.test.ts": 962, + "packages/web/tests/component.test.ts": 1501, + "packages/web/tests/declaration.test.ts": 721, + "packages/web/tests/document.test.ts": 813, + "packages/web/tests/elicitation.test.ts": 560, + "packages/web/tests/live-form.test.ts": 509, + "packages/web/tests/markdown.test.ts": 145, + "packages/web/tests/opener.test.ts": 532, "packages/web/tests/page.test.ts": 35, - "packages/web/tests/responder.test.ts": 880, - "packages/web/tests/server-lifecycle.test.ts": 704, - "packages/web/tests/server.test.ts": 1071, - "packages/workflow/tests/generated-observations.test.ts": 529, - "packages/workflow/tests/retained-run.test.ts": 1210, - "packages/workflow/tests/service-denial.test.ts": 62, - "packages/workflow/tests/workflow-bundle.test.ts": 692, - "packages/workflow/tests/workflow-definition.test.ts": 342, - "packages/workflow/tests/workflow-fork.test.ts": 296, - "packages/workflow/tests/workflow-run.test.ts": 1068, - "packages/workflow/tests/workspace-effect.test.ts": 1413, - "scripts/tests/adapter-vendor.test.ts": 152, - "scripts/tests/bootstrap-npm-package.test.ts": 5724, - "scripts/tests/bump-version.test.ts": 75, - "scripts/tests/changed-paths.test.ts": 39, - "scripts/tests/ci-workflow.test.ts": 1978, - "scripts/tests/consumer-cycle.test.ts": 106, - "scripts/tests/filesystem-contract-workflow.test.ts": 86, - "scripts/tests/main-green.test.ts": 299, - "scripts/tests/main-health.test.ts": 102, - "scripts/tests/no-module-scoped-registry.test.ts": 879, - "scripts/tests/no-redundant-test-scope.test.ts": 5026, - "scripts/tests/no-sync-filesystem.test.ts": 4606, - "scripts/tests/no-yield-in-finally.test.ts": 892, - "scripts/tests/oxlint-policy.test.ts": 10610, - "scripts/tests/packaged-document.test.ts": 71, - "scripts/tests/prefer-effection-operation.test.ts": 5964, - "scripts/tests/prefer-effection-result.test.ts": 4165, - "scripts/tests/publish-workflow-membership.test.ts": 74, - "scripts/tests/review-infrastructure.test.ts": 1869, - "scripts/tests/runtime-exclusions.test.ts": 312, - "scripts/tests/runtime-tests.test.ts": 83, - "scripts/tests/scope-bound-event-registration.test.ts": 4584, - "scripts/tests/staged-write.test.ts": 70, - "scripts/tests/test-file-discovery.test.ts": 240, - "scripts/tests/test-shards.test.ts": 45, - "scripts/tests/test-weights.test.ts": 243, - "scripts/tests/tracked.test.ts": 41, - "scripts/tests/verify-coordinator.test.ts": 227, + "packages/web/tests/responder.test.ts": 894, + "packages/web/tests/server-lifecycle.test.ts": 657, + "packages/web/tests/server.test.ts": 1081, + "packages/workflow/tests/generated-observations.test.ts": 563, + "packages/workflow/tests/retained-run.test.ts": 1322, + "packages/workflow/tests/service-denial.test.ts": 71, + "packages/workflow/tests/workflow-bundle.test.ts": 751, + "packages/workflow/tests/workflow-definition.test.ts": 369, + "packages/workflow/tests/workflow-fork.test.ts": 311, + "packages/workflow/tests/workflow-run.test.ts": 1163, + "packages/workflow/tests/workspace-effect.test.ts": 1526, + "scripts/tests/adapter-vendor.test.ts": 216, + "scripts/tests/bootstrap-npm-package.test.ts": 5655, + "scripts/tests/bump-version.test.ts": 84, + "scripts/tests/changed-paths.test.ts": 40, + "scripts/tests/ci-workflow.test.ts": 2013, + "scripts/tests/consumer-cycle.test.ts": 110, + "scripts/tests/filesystem-contract-workflow.test.ts": 77, + "scripts/tests/main-green.test.ts": 366, + "scripts/tests/main-health.test.ts": 113, + "scripts/tests/no-module-scoped-registry.test.ts": 909, + "scripts/tests/no-redundant-test-scope.test.ts": 5097, + "scripts/tests/no-sync-filesystem.test.ts": 4798, + "scripts/tests/no-yield-in-finally.test.ts": 908, + "scripts/tests/oxlint-policy.test.ts": 10746, + "scripts/tests/packaged-document.test.ts": 78, + "scripts/tests/prefer-effection-operation.test.ts": 6061, + "scripts/tests/prefer-effection-result.test.ts": 4330, + "scripts/tests/publish-workflow-membership.test.ts": 107, + "scripts/tests/review-infrastructure.test.ts": 1975, + "scripts/tests/runtime-exclusions.test.ts": 324, + "scripts/tests/runtime-tests.test.ts": 87, + "scripts/tests/scope-bound-event-registration.test.ts": 4535, + "scripts/tests/staged-write.test.ts": 69, + "scripts/tests/test-file-discovery.test.ts": 249, + "scripts/tests/test-shards.test.ts": 44, + "scripts/tests/test-weights.test.ts": 210, + "scripts/tests/tracked.test.ts": 40, + "scripts/tests/verify-coordinator.test.ts": 224, + "scripts/tests/version-lockstep.test.ts": 119, "scripts/tests/web-client-module.test.ts": 52, - "scripts/tests/workspace.test.ts": 49 + "scripts/tests/workspace.test.ts": 48 } } }