diff --git a/architecture.md b/architecture.md index fa26f8e2..12e45b4b 100644 --- a/architecture.md +++ b/architecture.md @@ -136,7 +136,7 @@ and categorization rather than clarify them, so both stay exactly as written. | `JournalProvenance` | a non-operational, equality-only witness that a live publication stream descends from the exact journal backend a provider selected for one workflow run; it grants no append, read, execution, publication or reconciliation capability, and is meaningful only because the provider retains the witness it established and later requires exact equality | | result object | the value a component binds instead of failing: `{ok: true, value}` or `{ok: false, …}`, whose failure members the component declares | | syntax symbols | the complete, versioned description of what a document may write in one directory under one host profile: every structural construct the engine reserves, and the one implementation selection chooses for every other name. It is observation — describing an environment installs no operational state, runs nothing and journals nothing — and it is produced once per request and projected, never rediscovered per format. `xmd syntax` prints one for an environment nobody is running; canonical `` renders one for the site an element was written at, from the same construction and the same Markdown renderer | -| execution environment | the private bundle of services and records one execution supplies while its document expands: what resolves a name, what routes it to a retained implementation, what the execution declared, and the records it keeps about its own identities, forms, vocabulary and source output. The execution builds it from what it captured and admitted, owns it, and passes it by value through canonical expansion — never through a context, because a context resolves by name and a name is not a secret. An expansion driven directly is handed none at all, which is what "nothing is installed here" means; there is no partial one. Its shape is an architecture and product boundary | +| execution environment | the private bundle of services and records one execution supplies while its document expands: what resolves a name, what routes it to a retained implementation, what the execution declared, and the records it keeps about its own identities, forms, vocabulary and source output. The execution builds it from what it captured and admitted, owns it, and passes it by value through canonical expansion — never through a context, because a context resolves by name and a name is not a secret. An expansion driven directly is handed none at all, which is what "nothing is installed here" means; there is no partial one. Its shape is an architecture and product boundary, which is why the resolution an execution performs for one *import* is not on it: that terminal is handed to expansion by hand, as an internal argument beside this bundle, named by no entry point and reachable only by having been given it | | syntax reference | the engine-owned lexical answer to "what may a document write here", carried by value on canonical core's execution environment beside what resolves an import. The execution builds one at its root from the selection inputs it captured before any installation, middleware or document code ran, or from the one set of symbols a trusted host stated for its profile; a trusted canonical evaluation boundary replaces it for the subtree it evaluates. It answers with text and decides nothing: a component the symbols name is not a component anything may run | | run profile declarations | the component registrations a first-party package makes, held as plain values apart from the middleware, providers, activation and launchers its installer also arranges. The installer registers exactly those values and inspection reads exactly those values, so what a run installs and what the symbols report cannot drift | | Plugin | trusted code installed once before an execution imports a root document. XMD bundles exactly one — `@executablemd/git` — and activates it for the run-profile commands: `run`, `plan`, `syntax`, and `workflow start`, `resume` and `fork`. The `xmd test` root is not one of them, because a harness that quietly gained the repository vocabulary would be claiming names its children are entitled to shadow; a nested `` child assembles the profile for itself rather than inheriting what its parent declined. Everything else an operator selects with `--plugin`, and a package that happens to be installed stays inert until it is named. A plain structural value carrying a name and one optional `install`; what it contributes is the existing `ExecutionInstallation` members, and what it composes through are the three stable contextual APIs below. Selecting one runs its module, and selected Plugin code can execute whatever the surrounding runtime permits: it is trusted, and it is not sandboxed | @@ -1743,8 +1743,8 @@ component neither imports it nor grants its effects. Bindings and structural constructs are engine-owned language syntax and require no profile entry. Every built-in construct is available inside a generated fragment with its ordinary semantics — `Content`, `Output`, `Return`, `Let`, -`Each`, `If`, `Else`, `Switch`, `Case`, `Loop`, `Break`, `PrintErrors`, -`Answers` and `Answer` — decided by the same source rules ordinary validation +`Each`, `If`, `Else`, `Switch`, `Case`, `Loop`, `Break`, `All`, `Spawn`, +`PrintErrors`, `Answers` and `Answer` — decided by the same source rules ordinary validation and expansion read, so a construct means one thing whether a person or an Agent wrote it. Preflight dispatches a reserved name to those rules before the admitted table is consulted, which is why a construct is never reported as a @@ -1766,8 +1766,10 @@ nothing it binds survives it, because that body may run no times at all — only `` branches of a `` are checked from one incoming snapshot, so no alternative supplies a binding to another; the union of what they can produce becomes visible afterwards, which keeps reading a binding only one arm makes a -runtime failure rather than a preflight refusal. ``, `` and -`` bodies read and write the enclosing environment. Constructs keep their +runtime failure rather than a preflight refusal. Each `` under an `` +is checked from that same incoming snapshot and contributes nothing back, which +is the binding contract the children actually run under. ``, +`` and `` bodies read and write the enclosing environment. Constructs keep their exact spelling in the retained source, never join the retained named-component list, and change no record version. @@ -3197,6 +3199,9 @@ for the loop it exits. One state exists per execution of one value body. ``, ``, ``, `` and `` keep the ambient one; a component invocation hides the caller's from the invoked body, so a component's own `` satisfies its own declaration; a +`` passes none either, and the shared rule refuses the `` that +would have reached for one, so concurrent children cannot race to claim a value +owned outside their ``; a nested value body installs a separate one, and both executions stand; and content the caller projected restores the caller's, through a Markdown `` and a function component's `content()` alike, with every @@ -3279,6 +3284,64 @@ partial replay rebuilds the selection through ordinary expansion while completed effects in the chosen branch replay from their own records, and a completed document replay reuses the retained result without expanding anything. +### Concurrent spawned children + +`` and `` are the engine's own syntax, not components, so core owns +what they mean and no repository file, registration, bundle or Plugin can supply +either name. `` runs its direct `` children at the same time and +waits for all of them. It takes no props, produces no value, and requires at +least two spawns — one alone is the sequential document it was already written +as. + +The whole structure is decided from source before a child is constructed: the +paired forms, the absent props, the two-spawn minimum, that only blank text sits +between the spawns, that no `` is written outside its direct ``, and +that no `` or `` inside a spawn reaches for an owner outside it. +One shared rule states all of it, and expansion, non-executing validation and +the generated-fragment preflight read the same result — so a malformed construct +starts no child anywhere, including in a fragment an Agent generated. + +**Concurrent expansion shares immutable inputs and nothing else.** Every child +gets a binding environment and live overlay snapshotted from the one that +reached the ``, an eval scope of its own, a fresh block counter, a private +output buffer, its own checked-failure ledger, a copied hide set and an +expansion path extended by its authored ordinal. Nothing merges back, so a name +one child binds or rebinds is invisible to its siblings and to the work after +``, and a resource it retains is released when it ends. The outer +`` and `` owners are cleared at the boundary; a loop or value +component created wholly inside a child establishes its own. + +**Durable identity comes from the source, never from the schedule.** The +existing durable structured-concurrency substrate is the implementation: in +source order, before any child starts, each `` receives one child +coroutine of the one that reached the ``, and nesting produces the existing +hierarchical identity. Effects inside a child keep their ordinary descriptions +under that coroutine, so two children may derive the same local block id without +colliding, and work after `` continues on the parent counter it would have +had without any child. Each successful child closes with the ordered `{ text, exact }` runs it +rendered. Exact presentation is a provenance recorded against the segment +objects expansion marked, and those segments do not cross the join — so the runs +are what carries it, live and on replay alike, and a retained close that does +not read as that shape fails the run instead of rebuilding part of a document. + +**Output is authored order; the journal is completion order.** A child emits +into its private buffer, and `` appends the renderings to its caller's +region only after every child has succeeded. Which child finished first may +decide when its records append and never what the document renders. A complete +replay reads the child closes and runs no spawned work; a partial replay +restores the children that closed and resumes only the unrecorded ones under the +same identities. Inserting, removing or reordering spawns is a definition change +and takes the existing divergence path rather than being reassigned by position +or completion time. + +**Failure is the existing fail-fast join.** The first child failure fails the +``, cancels every unfinished sibling and does not return until all of them +have finished tearing down. No private buffer is emitted, records acknowledged +before the failure stay durable, and interrupted work is never recorded as +complete. Cancelling the parent takes the same ownership path, and no task, +subscription, retained resource or buffer survives its spawn. No scheduler, no +new record family and no document-visible concurrency control is introduced. + ## Foreground commands An executable block is a foreground child process. Its output reaches the reader @@ -3930,7 +3993,8 @@ matching name says nothing about which registration answered. What settles it is the selection itself, recorded where it is made: - expansion opens an **execution-private import frame** before it asks the - public import chain anything; + public import chain anything, and hands it to the terminal it builds for that + one import; - canonical core resolution records, inside that frame, the exact name it was asked for and the exact implementation it selected — the function object this execution built from the host's factory, by identity; @@ -3947,10 +4011,62 @@ provenance and no permission: what a handler decides is which implementation runs, and an implementation running where canonical resolution did not select it names nothing. Nested and re-entrant frames stay contained, because a frame is opened per import and settled the moment that import answers, however it -answered; concurrent interleaving leaves more than one selection in a frame and -so yields no domain, which is the safe direction; and a replay decides the same -way, against this execution's own factory-created implementation rather than -against anything a previous run recorded. +answered; and a replay decides the same way, against this execution's own +factory-created implementation rather than against anything a previous run +recorded. + +**A frame is owned by the import's own terminal, not by an Effection scope and +not by the execution.** `` gives two `` children imports in flight at +the same time, so "the frame this selection belongs to" can be neither "the last +one anybody opened" nor "the one opened where this code happens to be running". +Each import creates its own frame and its own `Component` Api descriptor whose +core `importComponent` handler closes over that frame; the engine asks through +that descriptor, and the selection is recorded by the terminal `next` reaches: + +- the descriptor carries the same stable Api name as the public one, so it + receives the same installed middleware in the same order — **a stable name + shares middleware, and shares no terminal authority**; +- the resolution that terminal performs is the execution's own, handed to + expansion as an internal argument beside the execution environment rather than + on it, and carried by every recursion that can invoke a component — so a + component body, a branch and a spawned child resolve the way their execution + does, while an expansion driven directly and a generated fragment are handed + none and answer through the ordinary public chain; +- middleware may observe, delegate, replace or refuse the answer, and may run + `next` in any descendant scope: `next` still terminates in the terminal that + dispatch was created with, so delegating through a scope of its own keeps the + identity; +- sibling spawns resolve through different descriptors, so one spawned child's + selection cannot land in another's frame, and closing or cancelling one + neither clears nor authorizes the other; +- nesting is unchanged and needs no stack: an inner authored import is its own + dispatch with its own terminal, so the outer one is neither shadowed nor + inferred from scheduling; +- a handler that starts an import of its own by calling the public operation is + making a **separate** import, not delegating the authored one — it may resolve + normally, and it grants the authored import no identity; and +- two selections in *one* frame — a handler delegating twice — still yield no + domain, which remains the safe direction. + +The frame is a closure and nothing else: it is in no map, under no key, and +reachable only from the dispatch that opened it. Nothing is published, keyed or +read from — not a scope, not a context, not a token, and not a durable coroutine +id, which non-journaled expansion does not have. A counterfeit descriptor made +with the same Api name changes only what is asked through that counterfeit; it +cannot become the terminal of the engine's own call. Live expansion and replay +decide this the same way. + +Canonical answer windows are owned the same way and by the same kind of value — +an explicit object rather than a scope — but serially: component answers are +resolved in one capture phase before any document expands, so one window is open +at a time. Each provider request captures that window object before its handler +runs, a claim states it back explicitly, and a handler delegating through a +descendant scope answers the resolution it was opened in. One installation may +answer more than one import with the same definition — a reusable provider owns +one object and a claim is per window — and a second, *different* answer in +either still refuses. Concurrent document imports happen after capture, with no +window open at all: such an import may be answered as ordinary middleware, and +acquires no profile identity. The engine then mints one issuance per invocation, carrying that domain — or none — the authored name for what a refusal says, and the frame the body is diff --git a/packages/cli/tests/syntax-cli.test.ts b/packages/cli/tests/syntax-cli.test.ts index 957363ea..6967d450 100644 --- a/packages/cli/tests/syntax-cli.test.ts +++ b/packages/cli/tests/syntax-cli.test.ts @@ -326,6 +326,7 @@ describe("Tier SX — the run profile the command describes", () => { // The whole structural vocabulary this profile publishes, so a construct // returning to it has to be written down here. expect([...names(structural.entries)].sort()).toEqual([ + "All", "Answer", "Answers", "Break", @@ -339,6 +340,7 @@ describe("Tier SX — the run profile the command describes", () => { "Output", "PrintErrors", "Return", + "Spawn", "Switch", ]); for (const name of ["Terminal.Grid", "Terminal"]) { diff --git a/packages/core/src/body-structure.ts b/packages/core/src/body-structure.ts index c0ff0c68..99e24142 100644 --- a/packages/core/src/body-structure.ts +++ b/packages/core/src/body-structure.ts @@ -81,13 +81,20 @@ function collectOutputs(bodySegments: Segment[], minimumDepth: number): Componen * this walk does not see is the only thing that still cannot declare one — * another component's definition, and markdown produced at runtime — because * it reads this body's source AST and nothing else. + * + * A `` is the one region of this body's own source the walk stops at. + * The spawned child owns no value body, so a `` inside one neither + * satisfies this body's declaration nor is an undeclared return of it: the + * `` rule owns that diagnostic, and counting it here would report one + * mistake as two while letting a spawn stand in for the return the body still + * has to write. */ function collectReturns(bodySegments: Segment[]): ComponentElement[] { const declared: ComponentElement[] = []; const walk = (segments: Segment[]): void => { for (const segment of segments) { - if (segment.type !== "component") { + if (segment.type !== "component" || segment.name === "Spawn") { continue; } if (segment.name === "Return") { diff --git a/packages/core/src/component-answers.ts b/packages/core/src/component-answers.ts index 3ce4d1a2..05ba2a83 100644 --- a/packages/core/src/component-answers.ts +++ b/packages/core/src/component-answers.ts @@ -114,6 +114,11 @@ export function componentAnswerRegistrar( *around(handler: ComponentAnswerHandler): Operation { yield* Component.around({ *importComponent([name, position], next) { + // One request for this handler invocation, capturing the resolution + // window that is open right now. Minted here rather than carried on + // the installation: one installation serves every name it registered, + // and a retained handle states nothing about which resolution is + // being decided. const asked = installation.open(name, position); try { return yield* handler( diff --git a/packages/core/src/component-api.ts b/packages/core/src/component-api.ts index 9ce938c3..f65dd7f9 100644 --- a/packages/core/src/component-api.ts +++ b/packages/core/src/component-api.ts @@ -175,16 +175,92 @@ export interface ComponentApi { registry: ComponentRegistry; } -export const Component: Api = createApi("Component", { +/** + * The mark an empty public terminal puts on what it raises: a namespaced, + * non-enumerable own property carrying the name that was asked for. + * + * A property rather than a class, because the readers are in a different copy of + * this module than the writer as often as not. A component loaded from disk with + * `--include`, and a middleware package holding its own copy of core, each build + * their own `Component` descriptor with their own empty terminal and their own + * error class — and an execution has to recognize "nothing answered this" across + * exactly that seam, which `instanceof` cannot do. The name is namespaced so it + * collides with nothing, and non-enumerable so it does not travel into whatever + * a reporter serializes. + * + * It conveys one fact — *this call reached an empty public terminal, asking for + * this name* — and no authority. Anything may put it on anything; all it can + * cause is the ordinary resolution an unanswered import gets anyway. + */ +const MISSING_IMPORT_PROVIDER = "@executablemd/core/missing-import-provider"; + +/** + * What the public descriptor's own terminal raises when nothing answered. + * + * An execution's provider delegates to `next` first and performs its ordinary + * resolution only for this, so a descriptor that supplied a terminal of its own + * is answered by that terminal and never resolved twice. Exported for core, + * published from no package entry point, and carrying nothing but the name that + * was asked. + */ +export class MissingImportProvider extends Error { + override name = "MissingImportProvider"; + constructor(asked: string) { + super( + `Component.importComponent("${asked}") has no provider. Install one with ` + + `Component.around({ importComponent }, { at: "min" }) before expansion.`, + ); + Object.defineProperty(this, MISSING_IMPORT_PROVIDER, { + value: { asked }, + enumerable: false, + writable: false, + configurable: false, + }); + } +} + +/** + * Whether `error` is an empty public terminal reporting that nothing answered an + * import of exactly `asked`. + * + * The whole mark is parsed rather than trusted: an own, non-enumerable property + * under the namespaced name, holding an object whose complete own-key set is one + * member, `asked`, whose value is the name this call asked for. Nothing is cast; + * a payload carrying anything besides that member, however hidden, is not the + * mark; and a mark that names another component — a foreign terminal's report + * about a *different* import, arriving here on some other failure — is not this + * call's. + */ +export function isMissingImportProvider(error: unknown, asked: string): boolean { + if (typeof error !== "object" || error === null) { + return false; + } + const mark = Object.getOwnPropertyDescriptor(error, MISSING_IMPORT_PROVIDER); + if (mark === undefined || mark.enumerable) { + return false; + } + const value: unknown = mark.value; + // The complete own-key set, so "one member" means one: `Object.keys()` would + // report a payload carrying hidden extras — a non-enumerable member, a symbol — + // as the single-member mark it is not. + if (typeof value !== "object" || value === null || Reflect.ownKeys(value).length !== 1) { + return false; + } + return Object.getOwnPropertyDescriptor(value, "asked")?.value === asked; +} + +/** + * Every default but the registry, which each descriptor is given its own of: + * a `Map` living at module scope would be one table shared by every run in the + * process, and the lint rule that says so is right. + */ +const COMPONENT_DEFAULTS: Omit = { // deno-lint-ignore require-yield *importComponent( name: string, _position?: Readonly, ): Operation { - throw new Error( - `Component.importComponent("${name}") has no provider. Install one with ` + - `Component.around({ importComponent }, { at: "min" }) before expansion.`, - ); + throw new MissingImportProvider(name); }, // deno-lint-ignore require-yield *applyModifiers(_modifiers: Modifier[], block: CodeBlockContext): Operation { @@ -259,9 +335,52 @@ export const Component: Api = createApi("Component", *handleFailure(failure: ComponentFailure): Operation { throw failure.error; }, +}; + +export const Component: Api = createApi("Component", { + ...COMPONENT_DEFAULTS, registry: new Map(), }); +/** + * Ask one import through a descriptor whose terminal belongs to that import. + * + * The name is what shares middleware: a descriptor created with the stable + * `Component` name receives every handler installed anywhere, in the order they + * were installed, exactly as the public descriptor does. What a name does not + * share is the default handler — each `createApi()` instance owns its own — so + * the chain composed here terminates in the continuation this caller passed and + * in nothing a handler can reach, replace or reorder. + * + * That is the whole of the correlation. A middleware may delegate `next` through + * any descendant Effection scope and still arrive here; two imports asking at + * the same time have two descriptors and two terminals; and a counterfeit + * descriptor built with the same name changes only the calls made through it. + */ +export function importThroughTerminal( + name: string, + position: Readonly | undefined, + terminal: ( + asked: string, + at: Readonly | undefined, + ) => Operation, +): Operation { + const owned = createApi("Component", { + ...COMPONENT_DEFAULTS, + // Its own, and empty: what a registration installed is middleware, which + // this descriptor shares by name, so a registry asked through it is answered + // there exactly as it is through the public one. + registry: new Map(), + *importComponent( + asked: string, + at?: Readonly, + ): Operation { + return yield* terminal(asked, at); + }, + }); + return owned.operations.importComponent(name, position); +} + export const importComponent: Operations["importComponent"] = Component.operations.importComponent; export const applyModifiers: Operations["applyModifiers"] = diff --git a/packages/core/src/component-failures.ts b/packages/core/src/component-failures.ts index fb8bc2c3..9db405ed 100644 --- a/packages/core/src/component-failures.ts +++ b/packages/core/src/component-failures.ts @@ -20,7 +20,7 @@ */ import { Component, raise } from "./component-api.ts"; -import { attributeCause, ErrorMode } from "./errors.ts"; +import { attributeCause, documentationError, ErrorMode } from "./errors.ts"; import type { ComponentFailure, ErrorSegment, FunctionComponent } from "./types.ts"; import type { Operation } from "effection"; @@ -92,6 +92,27 @@ export function containedLedger(inherited: CheckedFailures | undefined): Checked return { authorized: inherited?.authorized ?? false }; } +/** + * Refuse a successful outcome for work that suffered an unauthorized checked + * command failure. + * + * The failure was already raised where the command ran, and something enclosing + * it — a `printErrors(fn)` component like ``, or one that caught the + * `ContentError` its projected content raised — printed it and returned. Those + * boundaries decide how a failure of their own is reported. Whether a command + * that exited nonzero failed the run is not theirs to decide (#441). + * + * Whoever owns the ledger says so: the execution for the run's own, and a + * spawned child for the one it keeps to itself, which is how a checked failure + * inside one `` fails that child rather than stopping a sibling's walk. + */ +export function* refuseCheckedFailure(checkedFailures: CheckedFailures): Operation { + const segment = checkedFailures.failure; + if (segment !== undefined) { + throw yield* documentationError(segment, "output"); + } +} + /** * Continue after this component fails, reporting the failure as a printed error. * diff --git a/packages/core/src/components/component-resolution.ts b/packages/core/src/components/component-resolution.ts index e46a6c1f..39393220 100644 --- a/packages/core/src/components/component-resolution.ts +++ b/packages/core/src/components/component-resolution.ts @@ -199,25 +199,54 @@ export interface IdentifiedAnswer { readonly definition: ImportedDefinition; } -/** One claim this owner recorded, with core's own copy of what was claimed. */ +/** + * What one answer object was claimed to be, and which resolutions claimed it. + * + * One implementation says what it is once, so the name, the identity and the + * installation belong to the object: a new resolution cannot rename it, restate + * its origin or revision, or take it for another provider. + * + * Which import it answered is a different question, and the answer to it is per + * resolution — so the copies are keyed by the window *object*, not by a number + * describing one. A caller asking about provenance presents the window it is + * holding, so the comparison is between two references to one thing rather than + * between a record and whatever the owner's mutable current state happens to + * say. A number would have to be trusted against that mutable state; an object + * cannot be forged into being the one the caller opened. + * + * A reusable provider returns one immutable definition to every import it + * answers. Keying the claim by the object alone made the first resolution part + * of the object's permanent identity and refused the same definition the second + * time, which is a provider being punished for not copying itself. + */ interface Claim { readonly name: string; readonly identity: AnswerIdentity; /** Which provider installation stated it, so a second cannot overwrite. */ readonly installation: object; /** - * The exact resolution this was an answer to. + * Core's copy of the definition the first claim described. + * + * The identity above is a statement about *this* definition, so a later claim + * of the same object has to be a claim about the same definition. Without this + * baseline, a provider could claim an object, edit it, and have the next window + * retain the edited version under the identity and revision that described the + * original — a different implementation wearing the first one's name. * - * The window *object*, not a number describing one. Provenance is a question - * about which import a statement answered, and a caller asking it presents - * the window it is holding — so the comparison is between two references to - * one thing rather than between a record and whatever the owner's mutable - * current state happens to say. A number would have to be trusted against - * that mutable state; an object cannot be forged into being the one the - * caller opened. + * Absent when the first claim could not be copied at all, which is not a state + * a later claim can improve: an answer core could not retain is not one it can + * start vouching for because the object has since changed. */ - readonly window: ResolutionWindow; - readonly canonical: ImportedDefinition | undefined; + readonly baseline: ImportedDefinition | undefined; + /** + * One retained copy per resolution this answer was claimed in. + * + * Weak on the window, so a claim lasts exactly as long as somebody still holds + * the resolution it belongs to. Presence is the provenance check: a window + * that never claimed this answer has no entry, and `identify` answers nothing + * for it however many other windows did. + */ + readonly copies: WeakMap; } /** A provider request used after its execution ended. */ @@ -300,7 +329,14 @@ export interface OpenAnswerRequest { * cannot revive a settled one or reach another provider's. */ export interface ProviderInstallation { - /** Begin one handler invocation's request for one asked name. */ + /** + * Begin one handler invocation's request for one asked name. + * + * The request captures the resolution that is open when it is minted, by + * identity, which is how it finds the asking it answers. Nothing about where + * the handler runs takes part in that, and nothing about it reaches the + * provider. + */ open(name: string, position?: Readonly): OpenAnswerRequest; } @@ -335,7 +371,7 @@ export class CanonicalImports { */ #active = false; /** - * The one resolution a claim may be stated during, when one is open. + * The one resolution this owner has open, if it has one. * * A provider installation outlives every resolution it takes part in, while a * fresh request belongs to one handler invocation. Canonical execution asks @@ -348,25 +384,36 @@ export class CanonicalImports { * belonging to a resolution other than the live one, and the name keeps a * handler settled for one name from recording under it while a different * name is being resolved. + * + * One window at a time, because component answers are resolved in one serial + * capture phase before any document expands: the execution asks for each + * admitted name in turn, and nothing concurrent is resolving beside it. A + * request captures the window object itself before its handler runs, so a + * handler delegating through any number of descendant scopes still answers the + * resolution it was opened in — no scope takes part in this at all. */ #window: ResolutionWindow | undefined; /** How many resolutions this owner has opened, so each one is its own. */ #occurrences = 0; /** - * The resolution each installation last stated an answer for. + * The resolutions each installation has already stated an answer for. * * Secondary. What proves a statement belongs to an import is the request's * captured window, not this; this only keeps one provider from naming two * different implementations for one import, where only one of them could be * what it resolved to. * - * Keyed by the installation's own frozen token and holding the window object, - * so an installation stays reusable: it answers several admitted names and - * the same name resolved more than once, because each of those is a different - * window. Spending the installation itself would break a valid multi-name - * provider, which the contract does not ask a host to split up. + * A set of windows per installation rather than the last one it answered. + * Windows follow one another, and one installation may legitimately answer in + * window A and then in window B — while a request kept from A may still try to + * state a second, different answer for A after B was answered. Remembering only + * the most recent window would have let that one through. + * + * Keyed by the installation's own frozen token, so an installation stays + * reusable: it answers several admitted names and the same name resolved more + * than once, because each of those is a different window. */ - readonly #spent = new WeakMap(); + readonly #spent = new WeakMap>(); /** Begin identifying. Called after teardown is registered. */ activate(): void { @@ -376,6 +423,8 @@ export class CanonicalImports { /** Stop. Called at teardown, on completion, failure or cancellation. */ revoke(): void { this.#active = false; + // Whatever was open goes with it: an execution torn down mid-resolution + // leaves nothing claimable. this.#window = undefined; } @@ -403,10 +452,8 @@ export class CanonicalImports { name, occurrence, close(): void { - // Only this window is closed. A nested resolution that already replaced - // it has its own close, and clearing another one here would settle a - // decision still being made. Compared by identity, so no bookkeeping - // value has to be trusted to say which window this is. + // By identity, and idempotent: a close that ran twice must not clear a + // window somebody else opened afterwards. if (owner.#window === window) { owner.#window = undefined; } @@ -438,7 +485,8 @@ export class CanonicalImports { // Captured by identity, here, at the moment this invocation begins. A // request minted while resolution N is open answers resolution N or // nothing: it holds the object, so it cannot be made to describe - // whichever window is open later. + // whichever window is open later, and running its handler in a + // descendant scope changes nothing about which window it captured. const opened = owner.#window; let live = true; const request: ComponentAnswerRequest = Object.freeze({ @@ -471,7 +519,11 @@ export class CanonicalImports { #record( installation: object, origin: string, - asked: { name: string; window: ResolutionWindow | undefined; live: () => boolean }, + asked: { + name: string; + window: ResolutionWindow | undefined; + live: () => boolean; + }, answer: ImportedDefinition, stated: ClaimedIdentity, ): ImportedDefinition { @@ -480,9 +532,9 @@ export class CanonicalImports { } // Four questions, and each of them is about the invocation rather than // about the provider: is this handler still deciding; is the resolution it - // was asked in the one still open, by identity; and is the name it was - // asked the name that resolution is deciding. A stable installation handle - // answers none of them, which is why it does not claim. + // was asked in the one still open, by identity; and is the name it was asked + // the name that resolution is deciding. A stable installation handle answers + // none of them, which is why it does not claim. const open = asked.window; if (!asked.live() || open === undefined || open !== this.#window || open.name !== asked.name) { throw new AnswerIdentityError(SETTLED_CLAIM); @@ -491,14 +543,13 @@ export class CanonicalImports { const held = typeof answer === "object" && answer !== null ? this.#claims.get(answer) : undefined; if (held !== undefined) { - // Restating exactly what is already there is what a provider installed - // twice does, and it is not a conflict. Anything else is two providers - // disagreeing about one object, and the first statement stands: a later - // claim that overwrote it would let a second provider rename the first's - // implementation. + // What this object is was settled by its first claim. Restating it is what + // a provider installed twice does, and what a reusable one does for every + // import it answers; anything else is two providers disagreeing about one + // object, and the first statement stands — a later claim that overwrote it + // would let a second provider rename the first's implementation. if ( held.installation !== installation || - held.window !== open || held.name !== asked.name || held.identity.origin !== identity.origin || held.identity.key !== identity.key || @@ -509,30 +560,77 @@ export class CanonicalImports { "implementation states what it is once.", ); } + if (held.copies.has(open)) { + // Already recorded for this exact resolution, so there is nothing to + // add: one import is one implementation, and this is that one again. + return answer; + } + // Retained once before it is compared: the answer may run user-controlled + // machinery when read, so comparing it and then retaining it would leave + // a gap in which those two reads could describe different definitions. + // Asked before anything is recorded and before the window is spent, so a + // refusal leaves both untouched. + const copy = retain(answer); + if ( + held.baseline === undefined || + copy === undefined || + !stillDescribes(held.baseline, copy) + ) { + throw new AnswerIdentityError( + "this answer no longer describes the implementation its identity was stated about, so a " + + "further import cannot be answered with it. One implementation states what it is " + + "once, and stays that.", + ); + } + // The same definition answering a different import. That is one provider + // reused, not a second answer — but it still spends *this* resolution, so + // a different answer afterwards refuses as it always did. + if (this.#spent.get(installation)?.has(open) === true) { + throw new AnswerIdentityError(SPENT_OPPORTUNITY); + } + held.copies.set(open, copy); + this.#spend(installation, open); return answer; } // Secondary, and about the provider rather than the invocation: naming a // *different* implementation for an import this provider already answered // is two answers where only one could be what it resolved to. - if (this.#spent.get(installation) === open) { + if (this.#spent.get(installation)?.has(open) === true) { throw new AnswerIdentityError(SPENT_OPPORTUNITY); } // Copied on the way in, so a later edit of the claimed object is visible as // the change it is. + const baseline = retain(answer); + const copies = new WeakMap(); + copies.set(open, baseline); this.#claims.set(answer, { name: asked.name, identity, installation, - window: open, - canonical: retain(answer), + baseline, + copies, }); - // Spent for this window and no other: the next import is a different - // window, which is what lets one installation answer several admitted - // names and the same name resolved twice. - this.#spent.set(installation, open); + this.#spend(installation, open); return answer; } + /** + * Record that this installation has answered this resolution. + * + * Spent for this window and no other: another import is a different window, + * which is what lets one installation answer several admitted names, the same + * name resolved twice, and sibling spawns resolving at the same time — while a + * second answer in a window it already spent still refuses. + */ + #spend(installation: object, open: ResolutionWindow): void { + const spent = this.#spent.get(installation); + if (spent === undefined) { + this.#spent.set(installation, new WeakSet([open])); + } else { + spent.add(open); + } + } + /** * The identity stated for this exact answer, and core's own copy of it. * @@ -558,21 +656,24 @@ export class CanonicalImports { if (claim === undefined) { return undefined; } - // The caller presents the resolution it opened, and the claim has to be an - // answer to *that* one, by object identity and under the name it decides. - // Nothing here consults the owner's current window: provenance read out of - // mutable state would be a claim about whenever the question was asked - // rather than about which import the statement answered. - if (claim.window !== resolution || claim.name !== resolution.name) { + // The caller presents the resolution it opened, and this answer has to have + // been claimed in *that* one, under the name it decides. Nothing here + // consults the owner's current window: provenance read out of mutable state + // would be a claim about whenever the question was asked rather than about + // which import the statement answered. A window that never claimed this + // answer holds no copy of it, whatever its siblings did. + if (claim.name !== resolution.name || !claim.copies.has(resolution)) { return undefined; } - // A claimed object the chain went on to edit is not the thing that was - // claimed. Reading it runs whatever it is made of, and a value that refuses - // to be compared has failed the comparison. - if (claim.canonical === undefined || !stillDescribes(claim.canonical, answer)) { + // This window's own copy, taken when it claimed. A claimed object the chain + // went on to edit is not the thing that was claimed: reading it runs + // whatever it is made of, and a value that refuses to be compared has + // failed the comparison. + const canonical = claim.copies.get(resolution); + if (canonical === undefined || !stillDescribes(canonical, answer)) { return undefined; } - return Object.freeze({ identity: claim.identity, definition: claim.canonical }); + return Object.freeze({ identity: claim.identity, definition: canonical }); } /** diff --git a/packages/core/src/document-validation.ts b/packages/core/src/document-validation.ts index 43188604..2090ca02 100644 --- a/packages/core/src/document-validation.ts +++ b/packages/core/src/document-validation.ts @@ -61,6 +61,7 @@ import { readRootSource, rootSourcePath } from "./root-source.ts"; import { composeRootDefinition } from "./root-composition.ts"; import type { RootDocumentSource } from "./root-source.ts"; import { + allStructure, answersViolations, answerViolations, breakViolations, @@ -74,6 +75,7 @@ import { strayAnswerMessage, strayCaseMessage, strayElseMessage, + straySpawnMessage, strayStructuralMessage, switchStructure, } from "./structural-rules.ts"; @@ -315,6 +317,26 @@ interface LexicalContext { readonly insideIf: boolean; /** Whether a `` in this source lexically encloses this point. */ readonly insideSwitch: boolean; + /** + * Whether an `` in this source lexically encloses this point. + * + * Not the immediate parent: a `` written anywhere below an `` is + * that ``'s to report, because the shared structure analysis walks the + * whole region and anchors the diagnostic at the element. This is what tells + * such a spawn from one written with no `` above it at all, which is the + * only case the element's own rule owns. + */ + readonly insideAll: boolean; + /** + * Whether a `` in this source lexically encloses this point. + * + * A spawned child owns no value body and no loop it did not open itself, so + * this is what tells a `` written inside one from the ordinary + * `` of the body it was written in. It never clears again within one + * source: a definition invoked inside the child is its own source and starts + * the walk over. + */ + readonly insideSpawn: boolean; /** Whether the immediate parent is an ``. */ readonly underAnswers: boolean; /** @@ -498,8 +520,10 @@ class ValidationState { entry, isRoot: entry.ordinal === 0, insideLoop: false, + insideAll: false, insideIf: false, insideSwitch: false, + insideSpawn: false, underAnswers: false, enclosing: undefined, }); @@ -1144,8 +1168,27 @@ class ValidationState { return switchStructure(segment).violations; case "Loop": return loopViolations(segment); + case "All": + // Every child's own source is decided here too, so a malformed + // `` — and a `` or `` that would leave one — is + // reported wherever it was written, before any child could start. + return allStructure(segment).violations; + case "Spawn": + // Every `` below an `` belongs to that ``, wherever it + // was written: `allStructure()` walks the whole region and reports a + // misplaced one at the element it found, so a second report here would + // be the same sentence twice about one mistake. What is left is a + // `` with no `` above it at all. + return context.insideAll + ? [] + : [{ code: "structural-usage-invalid", source: "Spawn", message: straySpawnMessage() }]; case "Break": - return breakViolations(segment, context.insideLoop); + // A `` reaching past its spawn is already reported by that + // spawn's ``, which says what is actually wrong with it — the loop + // it reached for is outside the child — rather than that no loop exists. + return context.insideSpawn && !context.insideLoop + ? [] + : breakViolations(segment, context.insideLoop); case "PrintErrors": return printErrorsViolations(segment); case "Answers": @@ -1195,7 +1238,8 @@ class ValidationState { default: // `` is the remaining case, and its whole contract belongs to // the body that declares — or fails to declare — `returns`, which the - // source's own facts already stated. + // source's own facts already stated. One written inside a `` + // belongs to no such body, and is reported by that spawn's ``. return []; } } @@ -1331,12 +1375,20 @@ function hasDynamicOperand(segment: ComponentElement): boolean { /** The lexical facts one element's children are written under. */ function childContext(segment: ComponentElement, context: LexicalContext): LexicalContext { + const spawned = segment.name === "Spawn"; return { entry: context.entry, isRoot: context.isRoot, - insideLoop: context.insideLoop || segment.name === "Loop", + // A spawn is a control-flow boundary: the loop that encloses the `` is + // not a loop its children may break, so the outer fact stops here and only + // a `` written inside the spawn opens a new one. + insideLoop: !spawned && (context.insideLoop || segment.name === "Loop"), insideIf: context.insideIf || segment.name === "If", insideSwitch: context.insideSwitch || segment.name === "Switch", + // Never cleared by a spawn: a nested `` owns its own descendants, and + // everything below the outermost one already has an owner. + insideAll: context.insideAll || segment.name === "All", + insideSpawn: context.insideSpawn || spawned, underAnswers: segment.name === "Answers", enclosing: segment.name, }; diff --git a/packages/core/src/execute.ts b/packages/core/src/execute.ts index 1964e858..f205bdea 100644 --- a/packages/core/src/execute.ts +++ b/packages/core/src/execute.ts @@ -10,7 +10,7 @@ * See DEC-005 in specs/decisions.md. */ -import { Err, Ok, ensure, scoped, spawn, withResolvers, until } from "effection"; +import { Err, Ok, ensure, scoped, spawn, useScope, withResolvers, until } from "effection"; import type { Operation, Result, Stream } from "effection"; import { type Api, createApi, type Operations } from "@effectionx/context-api"; import { @@ -83,7 +83,7 @@ import { createBlockCounter, } from "./expand.ts"; import { createReturnBody, missingReturnMessage } from "./return-flow.ts"; -import type { BlockCounter } from "./expand.ts"; +import type { BlockCounter, ComponentImportTerminal } from "./expand.ts"; import { DocumentationError, documentationError, @@ -93,7 +93,7 @@ import { filesFatalFailure, useSegmentCauses, } from "./errors.ts"; -import { Component, importComponent, raise } from "./component-api.ts"; +import { Component, importComponent, isMissingImportProvider, raise } from "./component-api.ts"; import { sourceDescription } from "./source-position.ts"; import { emissions, exactly, renderSegment } from "./render.ts"; import { createExactSource } from "./output/exact-source.ts"; @@ -142,7 +142,7 @@ import { installIdentities, parseFormDeclaration, } from "./invocation-identity.ts"; -import type { IdentityComponent } from "./invocation-identity.ts"; +import type { IdentityComponent, ImportSelection } from "./invocation-identity.ts"; import { CanonicalImports, ExecutionImports, @@ -181,7 +181,7 @@ import type { RootDocumentSource } from "./root-source.ts"; import { useEvalScope } from "@effectionx/scope-eval"; import { declaredRouting, FOREGROUND, route, withRouting } from "./foreground.ts"; import type { ForegroundRouting } from "./foreground.ts"; -import { checkedFailureLedger } from "./component-failures.ts"; +import { checkedFailureLedger, refuseCheckedFailure } from "./component-failures.ts"; import { provideTestHarnessInstallers } from "./test-harness.ts"; import type { TestHarnessInstaller } from "./test-harness.ts"; import type { CheckedFailures } from "./component-failures.ts"; @@ -2022,6 +2022,8 @@ function* runValueRoot( /** This run's record of an unauthorized checked command failure (#441). */ checkedFailures: CheckedFailures, environment: ExecutionEnvironment, + /** This execution's own import resolution, for the expansions below. */ + importTerminal: ComponentImportTerminal, ): Operation { // Created outside the scope the body runs in, so the value it selected is // still readable after that scope — and its teardown — has finished. @@ -2051,6 +2053,7 @@ function* runValueRoot( 0, checkedFailures, environment, + importTerminal, ownBody, ); const exactRecord = environment.sourceSegments; @@ -2073,27 +2076,19 @@ function* runValueRoot( return { status: "ok", output: chunks.join(""), value: selected.value }; } -/** - * Refuse a successful outcome for a run that suffered an unauthorized checked - * command failure. - * - * The failure was already raised where the command ran, and something enclosing - * it — a `printErrors(fn)` component like ``, or one that caught the - * `ContentError` its projected content raised — printed it and returned. Those - * boundaries decide how a failure of their own is reported. Whether a command - * that exited nonzero failed the run is not theirs to decide, and this is where - * the run says so (#441). - */ -function* refuseCheckedFailure(checkedFailures: CheckedFailures): Operation { - const segment = checkedFailures.failure; - if (segment !== undefined) { - throw yield* documentationError(segment, "output"); - } -} - function* documentWorkflow( props: Record, environment: ExecutionEnvironment, + /** + * How this execution resolves one component import, handed to every expansion + * this root causes. + * + * By hand, beside the environment rather than on it: what a host installed and + * what a fragment narrowed are described by the environment, and this is + * neither — it is the execution's own resolution, reachable only by having been + * given it (`expand.ts`). + */ + importTerminal: ComponentImportTerminal, ): Workflow { // This run's memory of a checked command failure it never authorized. Passed // by value into core's own expansion and reachable from nowhere else, so no @@ -2203,6 +2198,7 @@ function* documentWorkflow( rootPath, checkedFailures, environment, + importTerminal, ); } @@ -2226,6 +2222,7 @@ function* documentWorkflow( rootPath, checkedFailures, environment, + importTerminal, undefined, ); // An empty buffered root emits no output event. @@ -2254,6 +2251,7 @@ function* documentWorkflow( 0, checkedFailures, environment, + importTerminal, undefined, ); @@ -2591,6 +2589,59 @@ function* executeDocument( identity.componentRouting.project, ); + /** + * Resolve one component import the way this execution resolves them, and + * the only thing that puts an invocation in one of its identity domains. + * + * Owned by the execution and handed to expansion **as an argument**, never + * on the environment: `ExecutionEnvironment` is the boundary a host's + * installation and a fragment's narrowed table are described against, and + * an execution-private terminal belongs in neither. Expansion calls this as + * the terminal of a descriptor it created for one authored import, passing + * that import's own frame; the ordinary provider below calls it with no + * frame, for a direct `importComponent()` an invocation started itself. + * Neither path publishes the frame, and nothing outside core can name this + * operation. + */ + function* resolveComponentImport( + name: string, + position: Readonly | undefined, + selection: ImportSelection | undefined, + ): Operation { + // Read per import, in the invoking scope, so a component registered + // by a nested scope is visible to what that scope expands. + const registered = yield* Component.operations.registry; + const definition = yield* durableImportComponent( + name, + name === "__root__" ? root : undefined, + position, + { + searchPaths: includes, + registry: registered, + bundle, + declared: installedComponents, + catalog, + guarded: identity.protected, + }, + ); + // Canonical selection, recorded into the frame of the import that asked + // for it. This is the only thing that puts an invocation in one of this + // execution's identity domains: not the name, not the answer that comes + // back, and nothing a handler above this can hold + // (`invocation-identity.ts`). + if (definition.kind === "function") { + selection?.select(name, definition); + // The same record, for the other thing canonical resolution decides + // here: which dispatcher — if any — this import selected. A dispatcher + // a handler kept from another import reaches no body without it. + forms.select(name, definition); + } + // The witness for this answer. It is issued where the answer is + // produced and verified where it is invoked, so what a handler does to + // the value in between is visible rather than authoritative. + return imports === undefined ? definition : imports.issue(name, definition); + } + const environment: ExecutionEnvironment = { componentResolution: imports, // Everything this host declared, admitted above. Expansion asks it @@ -2631,39 +2682,31 @@ function* executeDocument( // and the root eval scope. yield* Component.around( { - *importComponent([name, position], _next) { - // Read per import, in the invoking scope, so a component registered - // by a nested scope is visible to what that scope expands. - const registered = yield* Component.operations.registry; - const definition = yield* durableImportComponent( - name, - name === "__root__" ? root : undefined, - position, - { - searchPaths: includes, - registry: registered, - bundle, - declared: installedComponents, - catalog, - guarded: identity.protected, - }, - ); - // Canonical selection, recorded where it is made. This is the only - // thing that puts an invocation in one of this execution's identity - // domains: not the name, not the answer that comes back, and - // nothing a handler above this can hold (`invocation-identity.ts`). - if (definition.kind === "function") { - identity.identities.select(name, definition); - // The same record, for the other thing canonical resolution - // decides here: which dispatcher — if any — this import selected. - // A dispatcher a handler kept from another import reaches no body - // without it. - forms.select(name, definition); + *importComponent([name, position], next) { + // Delegation first. An authored import is asked through a descriptor + // that supplied a terminal of its own, so `next` answers it there — + // in that import's frame — and this provider is the middleware it + // composed through rather than the thing that resolved it. + try { + return yield* next(name, position); + } catch (error) { + // Only an empty public terminal reporting *this* name: nothing + // answered this call, so it is a direct `importComponent()` an + // invocation started for itself. It resolves ordinarily and names + // no frame, which is what makes it a separate import rather than a + // delegation of the authored one. Every other failure is somebody + // else's and propagates unchanged. + // + // The mark is parsed rather than the class compared: the terminal + // that raised it belongs to whichever copy of core built the + // descriptor that was asked, and a component loaded with + // `--include` or a middleware package holding its own copy is an + // ordinary arrangement, not a hypothetical. + if (!isMissingImportProvider(error, name)) { + throw error; + } + return yield* resolveComponentImport(name, position, undefined); } - // The witness for this answer. It is issued where the answer is - // produced and verified where it is invoked, so what a handler does - // to the value in between is visible rather than authoritative. - return imports === undefined ? definition : imports.issue(name, definition); }, *applyModifiers([modifiers, context], _next) { const chain = composeModifierChain(modifiers, context, registry); @@ -2699,7 +2742,7 @@ function* executeDocument( const returned = yield* durableRun( function* (): Operation { const issued = issueDocument(props, (claimed) => - documentWorkflow(claimed, environment), + documentWorkflow(claimed, environment, resolveComponentImport), ); try { return yield* beforeAnyImport(issued); diff --git a/packages/core/src/expand.ts b/packages/core/src/expand.ts index 2b96bf92..1ba20ed2 100644 --- a/packages/core/src/expand.ts +++ b/packages/core/src/expand.ts @@ -13,7 +13,7 @@ * middleware installation) execute before children's code blocks. */ -import { ensure, Err, Ok, scoped, useScope, withResolvers } from "effection"; +import { all, ensure, Err, Ok, scoped, useScope, withResolvers } from "effection"; import type { Operation, Result, Stream } from "effection"; import type { FunctionComponent, @@ -39,6 +39,7 @@ import { validateOutputPlacement, } from "./body-structure.ts"; import { + allStructure, breakElementViolations, eachCaptureBinding, eachItemBinding, @@ -56,10 +57,11 @@ import { strayBreakMessage, strayCaseMessage, strayElseMessage, + straySpawnMessage, strayStructuralMessage, switchStructure, } from "./structural-rules.ts"; -import type { StructuralViolation, SwitchCase } from "./structural-rules.ts"; +import type { SpawnRegion, StructuralViolation, SwitchCase } from "./structural-rules.ts"; import { asBindingViolation, asExpressionViolation, @@ -77,6 +79,7 @@ import { evalScope, handleFailure, importComponent, + importThroughTerminal, raise, } from "./component-api.ts"; import { @@ -92,7 +95,7 @@ import { SegmentCauses, useSegmentCauses, } from "./errors.ts"; -import type { InvocationForm } from "./invocation-identity.ts"; +import type { ImportSelection, InvocationForm } from "./invocation-identity.ts"; import { structuralPlacement } from "./execution-declarations.ts"; import type { AdmittedStructural, @@ -102,8 +105,9 @@ import type { } from "./execution-declarations.ts"; import { regionStream } from "./expansion-region.ts"; import { emissions } from "./render.ts"; +import type { Emission } from "./render.ts"; import { printsErrors, usePrintErrors } from "./component-failures.ts"; -import { containedLedger, recoveringLedger } from "./component-failures.ts"; +import { containedLedger, recoveringLedger, refuseCheckedFailure } from "./component-failures.ts"; import type { CheckedFailures } from "./component-failures.ts"; import type { ImportedDefinition } from "./components/component-resolution.ts"; import type { ExecutionEnvironment } from "./execution-environment.ts"; @@ -129,8 +133,15 @@ import { createReturnBody, missingReturnMessage } from "./return-flow.ts"; import type { ReturnBody } from "./return-flow.ts"; import { unbox, useEvalScope } from "@effectionx/scope-eval"; import type { EvalScope } from "@effectionx/scope-eval"; +import { + DurableContext, + durableAll, + ephemeral, + StaleInputError, +} from "@executablemd/durable-streams"; +import type { Workflow } from "@executablemd/durable-streams"; import { SchemaValidationError, validateProps, validateReturnValue } from "./validate.ts"; -import { parseJson } from "./json.ts"; +import { isJsonObject, parseJson } from "./json.ts"; import { healSegment } from "./heal.ts"; import { scanSegments } from "./scanner.ts"; import { declareChildAnswers, expandAnswers, strayAnswerError } from "./answers.ts"; @@ -161,6 +172,35 @@ export interface BlockCounter { next(): number; } +/** + * How expansion reaches the resolution its execution performs for one import. + * + * Execution owns the resolver and hands it to expansion by hand, as an argument + * beside the environment and never on it: the environment is the architecture + * boundary a host's installation and a fragment's narrowed table are described + * against, and an execution-private terminal belongs in neither. It is not + * exported from a package entry point, installed in no Context or contextual Api, + * and stored in no module-scoped table — the only way to have it is to be handed + * it. + * + * Every internal expansion function carries it beside the environment, so a + * recursion that forgot it would fail to compile rather than quietly expanding a + * component body, a branch or a spawned child without the execution's resolution. + * Two callers legitimately pass none: expansion driven directly, which resolves + * through the ordinary public Component Api, and a generated fragment, whose + * narrowed provider answers its imports and which inherits nothing from the + * document execution that admitted it. + * + * The `selection` is the asking import's own frame (`invocation-identity.ts`). + * Resolution records what it selected there, which is what makes the answer this + * element's rather than whichever import resolved last. + */ +export type ComponentImportTerminal = ( + name: string, + position: Readonly | undefined, + selection: ImportSelection | undefined, +) => Operation; + export function createBlockCounter(): BlockCounter { let id = 0; return { next: () => id++ }; @@ -246,6 +286,7 @@ function expandChildrenScoped( /** Whether the region that caused this expansion grants recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { return scoped(function* () { @@ -269,6 +310,7 @@ function expandChildrenScoped( 0, checkedFailures, environment, + imports, returnBody, ); }); @@ -325,6 +367,7 @@ interface ProjectionState { */ checkedFailures: CheckedFailures | undefined; environment: ExecutionEnvironment | undefined; + imports: ComponentImportTerminal | undefined; } interface ProjectionFrame { @@ -469,6 +512,7 @@ function createProjectionHandle(state: ProjectionState): ProjectionHandle { 0, state.checkedFailures, state.environment, + state.imports, options.returnFrame, ); outcome.resolve({ segments: rendered }); @@ -578,6 +622,7 @@ function createProjectionHandle(state: ProjectionState): ProjectionHandle { 0, state.checkedFailures, state.environment, + state.imports, request.kind === "markdown" ? undefined : state.callerReturn, ); outcome.resolve({ segments: [...errors, ...rendered] }); @@ -783,6 +828,9 @@ export function expandSegments( indexBase, checkedFailures, environment, + // No execution terminal: an expansion driven directly resolves its imports + // through the ordinary public Component Api, exactly as it always has. + undefined, undefined, ); } @@ -862,6 +910,7 @@ function* expandListSegments( */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // An execution opens the table its printed errors record their causes in. @@ -885,6 +934,7 @@ function* expandListSegments( indexBase, checkedFailures, environment, + imports, returnBody, ); }); @@ -1006,6 +1056,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, )), ); @@ -1026,6 +1077,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, )), ); @@ -1047,6 +1099,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); break; @@ -1080,6 +1133,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); break; @@ -1098,6 +1152,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); break; @@ -1119,6 +1174,7 @@ function* expandListSegments( 0, checkedFailures, environment, + imports, returnBody, ); // Which placement this is, answered by identity rather than by name: @@ -1165,6 +1221,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); break; @@ -1190,6 +1247,38 @@ function* expandListSegments( break; } + if (segment.name === "All") { + // No raise() here, like the branches above: expandAll reports the + // errors it creates, and each child settled its own (§6.9). + yield* expandAll( + segment, + parentMeta, + parentProps, + hideSet, + result, + elementPath, + checkedFailures, + environment, + imports, + ); + break; + } + + if (segment.name === "Spawn") { + // A well-placed is consumed by its and never expanded on + // its own. Reaching this branch means the element sits outside any + // , so it names no component and is diagnosed rather than + // resolved from the filesystem. + result.push( + yield* raise({ + type: "error", + message: positioned(straySpawnMessage(), segment), + source: "Spawn", + }), + ); + break; + } + if (RESERVED_STRUCTURAL.has(segment.name)) { // Every other structural name is consumed above; reaching here means // one was written where the construct that gives it meaning is not. @@ -1223,6 +1312,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); break; @@ -1245,6 +1335,7 @@ function* expandListSegments( elementPath, checkedFailures, environment, + imports, returnBody, ); // A printed error the callee produced is data, and stays data here: it @@ -1389,6 +1480,7 @@ function* expandListSegments( }, checkedFailures, environment, + imports, returnBody, ), ); @@ -1451,6 +1543,7 @@ function* checkedCommandFailure( segment: ErrorSegment, checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Written down before it is raised or projected, and before the error mode is @@ -1556,6 +1649,7 @@ function* expandLet( /** Whether the enclosing region grants checked-failure recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Every one of these is decided from what the author wrote, so the whole @@ -1588,6 +1682,7 @@ function* expandLet( 0, checkedFailures, environment, + imports, returnBody, ), ); @@ -1699,6 +1794,7 @@ function* expandEach( /** Whether the region that caused this expansion grants recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Decided from source alone, so the catalog is shared with validation. A @@ -1758,6 +1854,7 @@ function* expandEach( extendPath(path, { f: "item", i: iteration }), checkedFailures, environment, + imports, returnBody, ); // A `` in the body exits the enclosing ``, so the remaining @@ -1853,6 +1950,7 @@ function* expandIf( /** Whether the enclosing region grants checked-failure recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Decided from source alone and shared with validation: which props were @@ -1924,6 +2022,7 @@ function* expandIf( 0, checkedFailures, environment, + imports, returnBody, ); } @@ -1993,6 +2092,7 @@ function* expandSwitch( /** Whether the enclosing region grants checked-failure recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Decided from source alone and shared with validation: which props were @@ -2059,6 +2159,7 @@ function* expandSwitch( 0, checkedFailures, environment, + imports, returnBody, ); } @@ -2134,6 +2235,7 @@ function* expandLoop( /** Whether the enclosing region grants checked-failure recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { const unknownProp = loopPropsViolation(segment); @@ -2191,6 +2293,7 @@ function* expandLoop( 0, checkedFailures, environment, + imports, returnBody, ); if (frame.broken) { @@ -2270,6 +2373,222 @@ function* expandBreak( return reported; } +/** + * The environment one spawned child starts from (spec §6.5 ``). + * + * Shallow snapshots of both maps: the durable values the `` was reached + * with, and the private live overlay beside them. Ordinary binding values are + * carried by identity — isolating what an author deliberately bound inside an + * object is not this construct's business — while the maps themselves are the + * child's own, so a name one child binds or rebinds is invisible to its + * siblings and to the work after ``. + */ +function spawnEnvironment(incoming: EvalEnv | undefined): EvalEnv { + const child: EvalEnv = { values: { ...(incoming?.values ?? {}) } }; + const overlay = liveEnvironment(child); + if (incoming !== undefined) { + Object.assign(overlay.values, liveEnvironment(incoming).values); + } + return child; +} + +/** + * Expand one `` body into the emission runs its child closes with. + * + * Everything mutable the ordinary walk threads is allocated here rather than + * inherited: the binding environment and its live overlay, an eval scope the + * child owns and whose resources die with it, a block counter, a private + * segment buffer, a checked-failure ledger and a copy of the hide set. + * Concurrent children therefore share immutable inputs and nothing else. + * + * The outer `` and `` owners are cleared: several children + * running at once may not race to select one enclosing value or decide one + * enclosing loop, and the structural rule already refused the elements that + * would try. A `` or value component created wholly inside the child + * establishes its own owner exactly as it does anywhere else. + */ +function spawnChild( + spawn: SpawnRegion, + incoming: EvalEnv | undefined, + parentMeta: Record, + parentProps: Record, + hideSet: Set, + path: string, + inherited: CheckedFailures | undefined, + environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, +): Operation { + return scoped(function* () { + yield* provideEnv(spawnEnvironment(incoming)); + yield* provideEvalScope(yield* useEvalScope()); + yield* ActiveLoop.set(undefined); + // The `` is consumed by its `` and never reaches dispatch, so + // its frame is added here — otherwise every child of one `` would + // expand under one path (§5.6). + const childPath = extendPath( + path, + elementFrame(spawn.element.name, elementSite(spawn.element.position, spawn.ordinal)), + ); + const segments: Segment[] = []; + // A fresh record with the inherited recovery, like a contained invocation's: + // a checked command failure inside one child fails that child, and a + // sibling's walk is not stopped by a ledger it does not own. + const ledger = containedLedger(inherited); + yield* expandSegmentsWithin( + spawn.element.children, + parentMeta, + parentProps, + new Set(hideSet), + createBlockCounter(), + segments, + childPath, + 0, + ledger, + environment, + imports, + undefined, + ); + yield* refuseCheckedFailure(ledger); + // Runs rather than one joined string. What a child produced is prose, or a + // program's approved source, or both in order — and exact presentation is a + // provenance this execution recorded against the segments it marked. Those + // segments do not leave the child, so the child states what they were: a + // string would arrive in the caller as prose, and a durable close holding + // one could not say otherwise on a replay either. + return emissions(environment?.sourceSegments, segments); + }); +} + +/** + * Run the children of an `` at the same time (spec §6.5 ``). + * + * The children are durable coroutines of the one that reached the ``, + * allocated in source order before any of them starts, so identity comes from + * where a `` was written and never from the order work was scheduled in. + * `durableAll()` is the whole join: it allocates those identities, returns the + * results in input order, reuses a completed child's recorded close on replay, + * and cancels unfinished siblings when one fails. + * + * Nothing is appended until the join succeeds. Each child renders into a buffer + * of its own, and the strings are written to the caller's region in authored + * order afterwards — so which child finished first may decide when its durable + * records append, and never what the document renders. A failed or cancelled + * join appends none of them. + * + * Expansion driven without a journal — a test, a tool describing a document — + * runs the same children on the same join without durable identities, exactly + * as `` records nothing there and behaves identically otherwise. + */ +function* expandAll( + segment: ComponentElement, + parentMeta: Record, + parentProps: Record, + hideSet: Set, + /** The region this renders into, once every child has succeeded. */ + owner: Segment[], + path: string, + /** Whether the enclosing region grants checked-failure recovery (§3.6). */ + checkedFailures: CheckedFailures | undefined, + environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, +): Operation { + // Decided from source alone and shared with validation, completely, before a + // child is constructed: a malformed `` starts none of them. + const structure = allStructure(segment); + if (structure.violations.length > 0) { + for (const violation of structure.violations) { + owner.push(yield* raise(structuralErrorSegment(violation, segment))); + } + return; + } + + // Read once, here: every child starts from the same incoming snapshot, and + // reading it inside a child would let whichever ran first decide what the + // others saw. + const incoming = yield* env; + const children = structure.spawns.map( + (spawn) => () => + spawnChild( + spawn, + incoming, + parentMeta, + parentProps, + hideSet, + path, + checkedFailures, + environment, + imports, + ), + ); + + const scope = yield* useScope(); + const rendered = + scope.get(DurableContext) === undefined + ? yield* all(children.map((child) => child())) + : (yield* durableAll( + children.map((child) => (): Workflow => ephemeral(retained(child()))), + )).map(readEmissions); + + for (const runs of rendered) { + for (const run of runs) { + if (run.text === "") { + continue; + } + // One segment per run, and the exact ones are marked again here: the + // record is keyed by segment identity, and these segments are this + // region's, not the child's. A child that ran on a previous attempt says + // what its runs were through its own durable close, so a replay marks + // exactly what the live run marked. + const segment: Segment = { type: "text", content: run.text }; + owner.push(segment); + if (run.exact) { + markExactSource(environment?.sourceSegments, [segment]); + } + } + } +} + +/** One child's runs, as its durable close retains them. */ +function retained(child: Operation): Operation { + return (function* (): Operation { + return (yield* child).map((run) => ({ text: run.text, exact: run.exact })); + })(); +} + +/** What a child's retained close is refused with when it will not read. */ +const UNREADABLE_RUNS = + "a spawned child's retained close cannot be read as what it rendered, so this run cannot " + + "rebuild the document that child produced. A history is read whole or not at all."; + +/** + * The runs a child closed with, read back from what the journal holds. + * + * Parsed whole, and refused whole. A replay hands back whatever the history has; + * skipping a member it cannot read, or reading a value that is not a list of + * runs as an empty one, would publish a document missing exactly the part that + * could not be read — and it would look like a document that rendered nothing + * there. So every member is checked, the shape is closed at the two members a + * run has, and anything else fails this run rather than shortening its output. + */ +function readEmissions(value: Json): Emission[] { + if (!Array.isArray(value)) { + throw new StaleInputError(UNREADABLE_RUNS); + } + const runs: Emission[] = []; + for (const entry of value) { + if (!isJsonObject(entry)) { + throw new StaleInputError(UNREADABLE_RUNS); + } + const text = entry["text"]; + const exact = entry["exact"]; + if (Object.keys(entry).length !== 2 || typeof text !== "string" || typeof exact !== "boolean") { + throw new StaleInputError(UNREADABLE_RUNS); + } + runs.push({ text, exact }); + } + return runs; +} + function printErrorsPropError(segment: ComponentElement, message: string): ErrorSegment { return { type: "error", message: positioned(message, segment), source: "PrintErrors" }; } @@ -2300,6 +2619,7 @@ function* expandPrintErrors( /** The ledger this region grants recovery on top of (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { const refusal = printErrorsViolations(segment)[0]; @@ -2326,6 +2646,7 @@ function* expandPrintErrors( // recovers is not one the run suffered. recoveringLedger(), environment, + imports, returnBody, ); }); @@ -2364,6 +2685,7 @@ function* expandComponent( */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { // Cycle detection — Prosser's algorithm @@ -2392,6 +2714,12 @@ function* expandComponent( // answered: what canonical resolution selected here is what decides whether // this invocation is in one of this execution's identity domains, and nothing // on the answer or in the chain carries it (`invocation-identity.ts`). + // + // The frame is this dispatch's closure, and the terminal built for this one + // import is the only thing that can select into it. That is what makes it this + // invocation's own: a sibling `` resolving the same name at the same + // time has its own frame behind its own terminal, and a handler delegating + // through any number of descendant scopes still arrives at this one. const selection = environment?.componentIdentity?.beginImport(name); let selected: IdentityDomain | undefined; let dispatcher: FunctionComponent | undefined; @@ -2418,7 +2746,18 @@ function* expandComponent( const offered = environment?.installedComponents?.offer(environment.componentBodyScope, name); let answered: ImportedDefinition; try { - answered = yield* importComponent(name, position); + // Through this import's own terminal when the execution handed expansion + // one: the public middleware chain composes around it by name, and `next` + // terminates in the continuation that carries this frame. Where expansion + // was handed none — driven directly, or a generated fragment answering + // through its own narrowed provider — the ordinary public operation answers + // exactly as it always did. + answered = + imports === undefined + ? yield* importComponent(name, position) + : yield* importThroughTerminal(name, position, (asked, at) => + imports(asked, at, selection), + ); } finally { offered?.close(); } @@ -2516,6 +2855,7 @@ function* expandComponent( path, checkedFailures, environment, + imports, returnBody, selected, dispatcher, @@ -2662,6 +3002,7 @@ function* expandComponent( printedErrors: bodyContentErrors, checkedFailures, environment, + imports, }); // Published on the eval scope, which every task the invocation owns // descends from — including its persist-eval blocks and its content. @@ -2736,6 +3077,7 @@ function* expandComponent( path, checkedFailures, bodyEnvironment, + imports, returnBody, ); }); @@ -2784,6 +3126,7 @@ function* expandComponent( path, checkedFailures, bodyEnvironment, + imports, returnBody, ); }); @@ -2953,6 +3296,7 @@ function* expandFunctionComponent( /** This work's checked-failure ledger, inherited from the invoking element. */ inherited: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, /** * The identity domain canonical resolution selected for this invocation. @@ -3166,6 +3510,7 @@ function* expandFunctionComponent( ownPath: path, checkedFailures, environment, + imports, }; // Only the ordinary handle is published. The richer projection a // protected body may reach stays a closure at the dispatch below, so @@ -4026,6 +4371,7 @@ export function* expandBody( /** Whether the invoking element sits inside a `` region. */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { if (!bodyHasOutput(bodySegments)) { @@ -4041,6 +4387,7 @@ export function* expandBody( 0, checkedFailures, environment, + imports, returnBody, ); } @@ -4063,6 +4410,7 @@ export function* expandBody( 0, checkedFailures, environment, + imports, returnBody, ); } else if (chunk.output) { @@ -4079,6 +4427,7 @@ export function* expandBody( 0, checkedFailures, environment, + imports, returnBody, ); }); @@ -4097,6 +4446,7 @@ export function* expandBody( chunkBase, checkedFailures, environment, + imports, returnBody, ); }); @@ -4125,6 +4475,7 @@ function runDocumentation( /** Whether the region that caused this expansion grants recovery (§3.6). */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { return scoped(function* () { @@ -4140,6 +4491,7 @@ function runDocumentation( indexBase, checkedFailures, environment, + imports, returnBody, ); }); @@ -4192,6 +4544,7 @@ function* expandValueBody( /** Whether the invoking element sits inside a `` region. */ checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment | undefined, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { const slots = partitionBySlot(children); @@ -4218,6 +4571,7 @@ function* expandValueBody( index, checkedFailures, environment, + imports, ownBody, ); } @@ -4259,6 +4613,7 @@ function* expandInstalledStructural( path: string, checkedFailures: CheckedFailures | undefined, environment: ExecutionEnvironment, + imports: ComponentImportTerminal | undefined, returnBody: ReturnBody | undefined, ): Operation { const name = segment.name; @@ -4360,6 +4715,7 @@ function* expandInstalledStructural( path, checkedFailures, environment, + imports, returnBody, children: segment.children, }); @@ -4412,6 +4768,7 @@ interface RegionContext { readonly path: string; readonly checkedFailures: CheckedFailures | undefined; readonly environment: ExecutionEnvironment; + readonly imports: ComponentImportTerminal | undefined; readonly returnBody: ReturnBody | undefined; readonly children: Segment[]; } @@ -4576,6 +4933,7 @@ function* produceRegion( index, context.checkedFailures, context.environment, + context.imports, context.returnBody, ); } catch (error) { diff --git a/packages/core/src/generated-xmd.ts b/packages/core/src/generated-xmd.ts index da91d105..91778dc4 100644 --- a/packages/core/src/generated-xmd.ts +++ b/packages/core/src/generated-xmd.ts @@ -134,6 +134,7 @@ import { RESERVED_STRUCTURAL } from "./structural.ts"; // The ordinary source rules, read rather than reimplemented: a construct means // one thing whether a person wrote it or an Agent did. import { + allStructure, answersViolations, answerViolations, breakViolations, @@ -2003,7 +2004,7 @@ function stated(segment: ComponentElement, scope: ReadonlySet): void { } /** The structural names that only their own parent may consume. */ -const CONSUMED_BY_PARENT: ReadonlySet = new Set(["Else", "Case", "Answer"]); +const CONSUMED_BY_PARENT: ReadonlySet = new Set(["Else", "Case", "Answer", "Spawn"]); /** * The structural names the generated root supplies no context for. @@ -2105,6 +2106,22 @@ function* structural( return; } + if (name === "All") { + const structure = allStructure(segment); + refuseStructure(structure.violations); + // Every child, before the fragment's first effect, and each from the + // bindings that reach the ``: the children run at the same time and + // merge nothing back, so none of them may be proved against a binding + // another one makes, and nothing any of them binds is visible afterwards. + // The lexical facts reset with the boundary, exactly as the shared rule + // above already refused a `` or `` that would cross it. + const incoming: ReadonlySet = new Set(scope); + for (const spawn of structure.spawns) { + yield* walk(spawn.element.children, table, ceilings, named, new Set(incoming), ROOT_LEXICAL); + } + return; + } + if (name === "PrintErrors") { refuseStructure(printErrorsViolations(segment)); yield* transparent(segment.children, table, ceilings, named, scope, lexical); @@ -2458,6 +2475,10 @@ function expand( ...(componentRouting === undefined ? {} : { componentRouting }), ...(syntax === undefined ? {} : { syntax }), }, + // No execution terminal: this fragment's imports are answered by the + // narrowed provider installed above, and it inherits nothing of the + // document execution that admitted it. + undefined, // A generated fragment is the engine's own text, so it owns no value body // and a written into it satisfies no declaration. undefined, diff --git a/packages/core/src/invocation-identity.ts b/packages/core/src/invocation-identity.ts index 884813fe..17432fd2 100644 --- a/packages/core/src/invocation-identity.ts +++ b/packages/core/src/invocation-identity.ts @@ -393,28 +393,39 @@ export interface InvocationIdentities { /** * Open the frame for one import the engine is about to ask for. * - * The engine settles it as soon as that import answers, whatever the answer - * was, so nothing a handler does in between decides what the frame holds. + * The frame is a closure and nothing else: it is in no collection, under no + * key, and reachable only through the object this returns. The engine hands + * that object to the terminal it builds for this one import and settles it as + * soon as the import answers, however it answered — so nothing a handler does + * in between decides what the frame holds, and an import a sibling spawn + * opened at the same time is a different closure with a different terminal. */ beginImport(name: string): ImportSelection; - /** - * Record what canonical resolution selected. Only core's own resolver calls - * this, from inside the import the frame above was opened for. - */ - select(name: string, definition: object, dispatcher?: unknown): void; /** Answer for nothing, from here on. Called when the execution is torn down. */ revoke(): void; } /** One import's frame: what canonical resolution selected, once. */ export interface ImportSelection { + /** + * Record what canonical resolution selected for this import. + * + * Reachable only by holding this frame, which only the engine does: the + * terminal it builds for this import closes over it, and that terminal is + * what `next` reaches however many scopes a handler delegates through. A + * second selection in one frame is ambiguity rather than a correction, and + * settles to nothing. + */ + select(name: string, definition: object): void; /** * The domain of the registration canonical resolution selected here, if it * selected one of this execution's own. * * Answers `undefined` unless exactly one canonical resolution happened inside - * this frame, for the name the engine asked, selecting the implementation - * this execution built for that domain. + * this frame, for the name the engine asked, selecting the implementation this + * execution built for that domain. Idempotent: the frame closes the first time + * it is settled, and a selection after that is not one that happened inside + * it. */ settle(): IdentityDomain | undefined; } @@ -1138,53 +1149,60 @@ export function installIdentities( }); } - /** - * The import frames the engine has open, innermost last. - * - * A stack rather than a slot, because a handler may expand something of its - * own while an import is in flight. Anything that leaves two selections in - * one frame — a handler delegating twice, or two expansions interleaving — - * settles to nothing, which is the safe direction. - */ - const frames: { asked: string; selected: Minted | undefined; count: number }[] = []; - return { identities: { + /** + * One import's frame, and the terminal that will select into it. + * + * No collection, no key, no stack: the frame is this closure, and the only + * way to select into it is to hold the object returned here. The engine + * hands it to the terminal it builds for this exact import, so a handler + * delegating through any number of descendant scopes still reaches *this* + * frame, and a sibling spawn resolving the same name at the same time + * reaches its own. Nothing about either frame is inferred from where it + * was opened or from what it resolved to. + */ beginImport(asked: string): ImportSelection { - const frame = { asked, selected: undefined as Minted | undefined, count: 0 }; - frames.push(frame); + let selected: Minted | undefined; + let count = 0; + let closed = false; return { + select(name: string, definition: FunctionComponentDefinition): void { + // A frame the engine has already settled records nothing: the + // invocation it belonged to has its answer, and a selection arriving + // afterwards is another call's. + if (closed) { + return; + } + count += 1; + // The name canonical resolution answered for has to be the one the + // engine asked: a handler that delegates a different name selects a + // registration the element never named. + const domain = asked === name ? minted.get(name) : undefined; + // And the implementation has to be the one this execution built. A + // repository file, a nested registration and another execution's + // component all resolve to a different function. + selected = + domain !== undefined && domain.implementation === definition.fn ? domain : undefined; + }, settle(): IdentityDomain | undefined { - const index = frames.lastIndexOf(frame); - if (index >= 0) { - frames.splice(index, 1); + if (closed) { + return undefined; } - return frame.count === 1 && frame.selected !== undefined - ? frame.selected.domain - : undefined; + closed = true; + return count === 1 && selected !== undefined ? selected.domain : undefined; }, }; }, - select(name: string, definition: FunctionComponentDefinition): void { - const frame = frames.at(-1); - if (frame === undefined) { - return; - } - frame.count += 1; - // The name canonical resolution answered for has to be the one the - // engine asked: a handler that delegates a different name selects a - // registration the element never named. - const domain = frame.asked === name ? minted.get(name) : undefined; - // And the implementation has to be the one this execution built. A - // repository file, a nested registration and another execution's - // component all resolve to a different function. - frame.selected = - domain !== undefined && domain.implementation === definition.fn ? domain : undefined; - }, revoke: () => { for (const domain of minted.values()) { domain.revoke(); } + // There is no frame registry to clear. An execution torn down while a + // spawn still had an import open leaves that frame reachable only from + // the dispatch that owns it, and its domains answer for nothing from + // here — so a frame still in flight settles to nothing of its own + // accord. // The route goes with the domains: a wrapper projected into it, or a // route narrowed from it, answers for nothing once the execution that // minted the bodies is gone. diff --git a/packages/core/src/structural-rules.ts b/packages/core/src/structural-rules.ts index e343ad87..de6b9afd 100644 --- a/packages/core/src/structural-rules.ts +++ b/packages/core/src/structural-rules.ts @@ -716,6 +716,207 @@ export function switchStructure(segment: ComponentElement): SwitchStructure { return { violations, matching, ...(fallback === undefined ? {} : { fallback }) }; } +/** What a `` written outside the `` that runs it says. */ +export function straySpawnMessage(): string { + return ( + " must be a direct child of . is reserved: it never resolves a " + + "component, and only the it belongs to can run it." + ); +} + +/** What a `` written inside a spawned child says. */ +export function spawnReturnMessage(): string { + return ( + " cannot cross . A spawned child owns no value body, so a " + + "written inside one would have to select a value owned outside — which several " + + "children running at once may not race to claim. A component invoked inside the spawn " + + "still owns its own ." + ); +} + +/** What a `` written inside a spawned child with no loop of its own says. */ +export function spawnBreakMessage(): string { + return ( + " cannot cross . A spawned child cannot exit a written outside " + + ", because several children running at once may not race to decide one loop. A " + + " written wholly inside the spawn is broken by its own as usual." + ); +} + +/** + * One `` `` runs, and where it was written. + * + * `index` is the child index the element sits at, which is what its expansion + * frame is derived from (§5.6); `ordinal` is its place among the spawns, which + * is the order its durable child identity and its rendered output follow. + */ +export interface SpawnRegion { + readonly element: ComponentElement; + readonly index: number; + readonly ordinal: number; +} + +/** How an `` body divides into spawned children, and what it got wrong. */ +export interface AllStructure { + readonly violations: StructuralViolation[]; + /** The direct `` children, in authored order. */ + readonly spawns: SpawnRegion[]; +} + +/** Everything one `` element decides from what the author wrote. */ +function spawnElementViolations(segment: ComponentElement): StructuralViolation[] { + const found: StructuralViolation[] = []; + const names = authoredPropNames(segment); + if (names.length > 0) { + found.push( + violation( + "structural-usage-invalid", + "Spawn", + ` accepts no props. Got: "${names[0]}".`, + segment, + ), + ); + } + if (segment.selfClosing) { + found.push( + violation( + "structural-usage-invalid", + "Spawn", + " holds the markdown its child expands, so it is written paired: " + + "....", + segment, + ), + ); + } + return found; +} + +/** + * Every `` below an `` that is not one of its direct children. The + * walk stops at a nested ``, which owns the spawns beneath it. + */ +function misplacedSpawnViolations(children: Segment[]): StructuralViolation[] { + const found: StructuralViolation[] = []; + + const walk = (segments: Segment[], depth: number): void => { + for (const segment of segments) { + if (segment.type !== "component" || segment.name === "All") { + continue; + } + if (segment.name === "Spawn" && depth > 0) { + found.push(violation("structural-usage-invalid", "Spawn", straySpawnMessage(), segment)); + } + walk(segment.children, depth + 1); + } + }; + + walk(children, 0); + return found; +} + +/** + * What one spawned child's own body says about control flow leaving it. + * + * Lexical, like every other placement rule here: a `` is legal because a + * `` written inside the same spawn encloses it, and a `` has no + * owner to select at all, because `` produces no value and a spawn is not + * a value body. A definition invoked inside the child is its own source and is + * not reached from here, so its `` keeps its ordinary meaning. + * + * The walk stops at a nested ``, whose own structure owns the spawns + * beneath it, so one misplaced element is reported once. + */ +function spawnFlowViolations(spawn: ComponentElement): StructuralViolation[] { + const found: StructuralViolation[] = []; + + const walk = (segments: Segment[], insideLoop: boolean): void => { + for (const segment of segments) { + if (segment.type !== "component" || segment.name === "All") { + continue; + } + if (segment.name === "Return") { + found.push(violation("return-usage-invalid", "Return", spawnReturnMessage(), segment)); + continue; + } + if (segment.name === "Break" && !insideLoop) { + found.push(violation("structural-usage-invalid", "Break", spawnBreakMessage(), segment)); + continue; + } + walk(segment.children, insideLoop || segment.name === "Loop"); + } + }; + + walk(spawn.children, false); + return found; +} + +/** + * Divide an `` body into the children it runs and validate the division + * (spec §6.5). Everything here is read from source, before any child starts, so + * a malformed construct refuses the whole `` rather than being discovered + * once one child is already running. + * + * Two spawns is the smallest thing worth running at the same time: one alone is + * the sequential document it was already written as, and saying so is a fact + * about the source rather than a scheduling decision. + */ +export function allStructure(segment: ComponentElement): AllStructure { + const violations: StructuralViolation[] = []; + const spawns: SpawnRegion[] = []; + + const names = authoredPropNames(segment); + if (names.length > 0) { + violations.push( + violation("structural-usage-invalid", "All", ` accepts no props. Got: "${names[0]}".`), + ); + } + if (segment.selfClosing) { + violations.push( + violation( + "structural-usage-invalid", + "All", + " holds the children it runs, so it is written paired: " + + ".......", + ), + ); + } + + for (const [index, child] of segment.children.entries()) { + if (isBlankText(child)) { + continue; + } + if (child.type !== "component" || child.name !== "Spawn") { + violations.push( + violation( + "structural-usage-invalid", + "All", + ` holds only children. Found ${describeSegment(child)} directly inside ` + + "it.", + child.type === "component" ? child : undefined, + ), + ); + continue; + } + violations.push(...spawnElementViolations(child)); + violations.push(...spawnFlowViolations(child)); + spawns.push({ element: child, index, ordinal: spawns.length }); + } + + if (!segment.selfClosing && spawns.length < 2) { + violations.push( + violation( + "structural-usage-invalid", + "All", + " runs at least two children at the same time. Write the second one, " + + `or write the work inline. Found ${spawns.length}.`, + ), + ); + } + + violations.push(...misplacedSpawnViolations(segment.children)); + return { violations, spawns }; +} + const LOOP_PROPS = new Set(["max", "name"]); /** How a `` names itself in its own printed errors. */ diff --git a/packages/core/src/structural.ts b/packages/core/src/structural.ts index 5a375f3a..afc5c21f 100644 --- a/packages/core/src/structural.ts +++ b/packages/core/src/structural.ts @@ -143,6 +143,28 @@ export const STRUCTURAL_DECLARATIONS: readonly EngineStructuralDeclaration[] = [ as: null, context: null, }, + { + name: "All", + syntax: ["……"], + description: + "Run tasks in parallel. " + + "`firstsecond` expands every task at the same " + + "time, waits for them all to succeed, and preserves their Markdown order. Write at least " + + "two `` children directly inside it; if one fails, unfinished tasks are cancelled " + + "and `` produces no output.", + as: null, + context: "The `` children this runs at the same time.", + }, + { + name: "Spawn", + syntax: ["…"], + description: + "Run a task in parallel inside of ``. Write `…` directly inside " + + "``; bindings made in its content stay with that task, and `` or `` " + + "cannot affect work outside it.", + as: null, + context: "Markdown expanded in its own child, beside its siblings.", + }, { name: "PrintErrors", syntax: ["…"], diff --git a/packages/core/tests/all.test.ts b/packages/core/tests/all.test.ts new file mode 100644 index 00000000..eeb35108 --- /dev/null +++ b/packages/core/tests/all.test.ts @@ -0,0 +1,1683 @@ +/** + * Tier ALL — spawned document work running at the same time + * (spec §6.5 `` and ``). + * + * Every case here drives the ordinary scanner, the structural rules, the + * expansion engine and the durable stream. Concurrency is established with + * signals the children and the journal publish — never with a sleep — and the + * releases are ordered so that the child which finishes *second* is the one the + * document renders *first*. Calling the durable substrate directly would prove + * the substrate rather than the language, so nothing here does. + * + * Each test names the defect it kills: expanding the children in sequence, + * emitting them in completion order, running them on the parent coroutine, + * sharing one block counter, sharing the binding environment, leaving a held + * sibling alive after a failure, publishing one child's output before the join, + * and letting a `` or `` claim an owner outside the spawn. + */ + +import { beforeAll, describe, it } from "@executablemd/test-support/bdd"; +import { expect } from "@executablemd/test-support/expect"; +import { race, resource, scoped, sleep, spawn, withResolvers } from "effection"; +import type { Operation } from "effection"; +import { createDurableOperation, ephemeral, InMemoryStream } from "@executablemd/durable-streams"; +import type { + DurableEvent, + EffectDescription, + Json, + Workflow, +} from "@executablemd/durable-streams"; +import { useHostFiles } from "@executablemd/runtime"; +import { useEchoExec, useStubFs } from "@executablemd/runtime/test"; +import { execute } from "../src/execute.ts"; +import { executeInstalled } from "../host.ts"; +import { evaluateGeneratedXmd, pinnedComponent } from "../host.ts"; +import type { + DurablePreparation, + ExecutionInstallation, + GeneratedObservation, + GeneratedXmdRequest, + RetainedFragmentIdentity, +} from "../host.ts"; +import { forEach } from "@effectionx/stream-helpers"; +import { collect } from "../src/collect.ts"; +import { Component, retain } from "../src/component-api.ts"; +import { getExpansion } from "../src/expansion.ts"; +import { retainedSource } from "../src/root-source.ts"; +import { registerComponents } from "../src/components/registration.ts"; +import { Agent } from "../src/agent/agent-api.ts"; +import type { AgentPromptEvent } from "../src/agent/agent-api.ts"; +import { agentIdentityComponents, installAgentComponents } from "../src/agent/components.ts"; +import { useTempFileCompiler } from "../src/temp-file-compiler.ts"; +import { inspectSyntax } from "../src/inspect.ts"; +import { RESERVED_STRUCTURAL } from "../src/structural.ts"; +import { validateDocument } from "../src/document-validation.ts"; +import { inlineSource } from "../src/root-source.ts"; +import type { FunctionComponentDefinition, JsonObject } from "../src/types.ts"; + +const NO_PROPS = { type: "object", properties: {}, additionalProperties: false }; +const NAMED = { + type: "object", + properties: { name: { type: "string" } }, + required: ["name"], + additionalProperties: false, +}; + +/** + * How long a signal that a correct engine publishes immediately may go + * unpublished before the wait is called a deadlock. + * + * Never reached by a passing run: every barrier below is opened by the children + * themselves or by the journal, so this bounds only the failure mode. A + * sequential expansion would otherwise hold its first child at a barrier its + * sibling can no longer reach, and the suite would hang instead of saying what + * went wrong. + */ +const DEADLOCK_MS = 10_000; + +/** A one-shot signal two operations coordinate through. */ +interface Signal { + publish(): void; + readonly published: Operation; +} + +function signal(): Signal { + const resolvers = withResolvers(); + let settled = false; + return { + publish() { + if (!settled) { + settled = true; + resolvers.resolve(true); + } + }, + get published() { + return resolvers.operation; + }, + }; +} + +/** Wait for a signal, reporting a deadlock rather than hanging on one. */ +function* awaiting(what: string, waited: Operation): Operation { + const reached = yield* race([ + waited, + (function* (): Operation { + yield* sleep(DEADLOCK_MS); + return false; + })(), + ]); + if (!reached) { + throw new Error(`${what} never happened: the spawned children did not run at the same time`); + } +} + +/** + * The gates, tripwires and timelines one document's registered components + * share. + * + * Three facts about one child are kept apart on purpose: that it reached its + * barrier, that it was let go, and that its own durable record landed. A + * driver that could only see the last of them could not tell a document whose + * children ran together from one whose children ran in turn. + */ +class Harness { + /** Every `` and `` that reached its barrier, in arrival order. */ + readonly arrived: string[] = []; + /** Every registered component invocation, in order. */ + readonly ran: string[] = []; + /** The expansion identifier each `` saw, by its name. */ + readonly identities = new Map(); + /** `start:`/`stop:` marks the retained resources published. */ + readonly timeline: string[] = []; + /** What each `` saw of a retained resource. */ + readonly observed: string[] = []; + + readonly #arrivals = new Map(); + readonly #releases = new Map(); + readonly #records = new Map(); + + static #slot(map: Map, name: string): Signal { + const existing = map.get(name); + if (existing !== undefined) { + return existing; + } + const created = signal(); + map.set(name, created); + return created; + } + + announce(name: string): void { + this.arrived.push(name); + Harness.#slot(this.#arrivals, name).publish(); + } + + held(name: string): Operation { + return awaiting(`<${name}> being released`, Harness.#slot(this.#releases, name).published); + } + + recorded(name: string): void { + Harness.#slot(this.#records, name).publish(); + } + + arrival(name: string): Operation { + return awaiting(`${name} arriving`, Harness.#slot(this.#arrivals, name).published); + } + + append(name: string): Operation { + return awaiting(`${name} recording its work`, Harness.#slot(this.#records, name).published); + } + + release(name: string): void { + Harness.#slot(this.#releases, name).publish(); + } +} + +function harness(): Harness { + return new Harness(); +} + +type Gated = Harness; + +/** Append one entry to the journal, the way any durable operation does. */ +function* mark(description: EffectDescription, value: Json): Workflow { + return yield createDurableOperation(description, function* () { + return value; + }); +} + +/** A resource whose start and stop are both on the record. */ +function useWatch(timeline: string[], label: string): Operation { + return resource(function* (provide) { + timeline.push(`start:${label}`); + try { + yield* provide(`held:${label}`); + } finally { + timeline.push(`stop:${label}`); + } + }); +} + +/** + * The components every document below is written against. + * + * `` is the barrier: it announces that it is live, waits to be released, + * and only then appends its own durable record — so which child reached the + * document first, which was released first and which appended first are three + * separately observable facts. + */ +function* useComponents(shared: Gated): Operation { + yield* registerComponents([ + { + name: "Gate", + origin: "tier-all", + props: NAMED, + *fn(props) { + const name = String(props.name); + shared.ran.push(`gate:${name}`); + shared.announce(name); + yield* shared.held(name); + yield* mark({ type: "gate", name: `gate:${name}` }, name); + shared.recorded(name); + return `[${name}]`; + }, + }, + { + name: "Probe", + origin: "tier-all", + props: NAMED, + *fn(props) { + const name = String(props.name); + shared.ran.push(`probe:${name}`); + shared.identities.set(name, (yield* getExpansion()).id); + return `(${name})`; + }, + }, + { + name: "Keep", + origin: "tier-all", + props: NAMED, + *fn(props) { + const name = String(props.name); + shared.ran.push(`keep:${name}`); + return yield* retain(() => useWatch(shared.timeline, name)); + }, + }, + { + name: "Observe", + origin: "tier-all", + props: NAMED, + // deno-lint-ignore require-yield + *fn(props) { + const name = String(props.name); + shared.observed.push( + `${name}:${shared.timeline.includes("stop:kept") ? "released" : "live"}`, + ); + return ""; + }, + }, + { + name: "Fail", + origin: "tier-all", + props: NAMED, + *fn(props) { + const name = String(props.name); + shared.ran.push(`fail:${name}`); + shared.announce(name); + yield* shared.held(name); + throw new Error(`FAILED:${name}`); + }, + }, + ]); +} + +/** What one document run produced. */ +interface Run { + readonly ok: boolean; + readonly output: string; + readonly failure: string; + readonly events: DurableEvent[]; +} + +/** + * Run one document to completion, with the gate components registered and a + * driver task free to open the barriers while the run is still live. + */ +function run( + source: string, + options: { + shared?: Gated; + stream?: InMemoryStream; + files?: Record; + drive?: (shared: Gated, stream: InMemoryStream) => Operation; + install?: () => Operation; + } = {}, +): Operation { + return scoped(function* () { + const shared = options.shared ?? harness(); + const stream = options.stream ?? new InMemoryStream(); + yield* useHostFiles(); + yield* useStubFs({ "test.md": source, ...options.files }); + yield* useEchoExec(); + yield* useComponents(shared); + if (options.install) { + yield* options.install(); + } + if (options.drive) { + yield* spawn(() => options.drive!(shared, stream)); + } + // Drained chunk by chunk rather than collected: a run that fails still + // emitted whatever it emitted, and a test asking whether `` published + // a child early has to be able to see it. + const chunks: string[] = []; + const result = yield* scoped(function* () { + const execution = yield* execute({ path: "test.md", stream }); + try { + yield* forEach(function* (chunk: string) { + chunks.push(chunk); + }, execution.output); + } catch { + // The execution's own outcome below says why the stream stopped. + } + return yield* execution; + }); + return { + ok: result.ok, + output: chunks.join(""), + failure: result.ok ? "" : result.error.message, + events: stream.snapshot(), + }; + }); +} + +/** The name one durable record settled with, or `undefined` for anything else. */ +function recordedName(event: DurableEvent): string | undefined { + if (event.type !== "yield" || event.description.type !== "gate") { + return undefined; + } + return event.result.status === "ok" ? String(event.result.value ?? "") : undefined; +} + +/** Every durable record one gate appended, in journal order. */ +function gateOrder(events: DurableEvent[]): string[] { + const found: string[] = []; + for (const event of events) { + const name = recordedName(event); + if (name !== undefined) { + found.push(name); + } + } + return found; +} + +/** The coroutine each gate record was written under, by gate name. */ +function gateCoroutines(events: DurableEvent[]): Map { + const found = new Map(); + for (const event of events) { + const name = recordedName(event); + if (name !== undefined) { + found.set(name, event.coroutineId); + } + } + return found; +} + +interface EvalEntry { + readonly name: string; + readonly coroutineId: string; +} + +function evalEntries(events: DurableEvent[]): EvalEntry[] { + return events + .filter((event) => event.type === "yield" && event.description.type === "eval") + .map((event) => ({ + name: event.type === "yield" ? event.description.name : "", + coroutineId: event.coroutineId, + })); +} + +/** Every coroutine this run closed, with the status it closed under. */ +function closes(events: DurableEvent[]): Array<{ id: string; status: string }> { + return events + .filter((event) => event.type === "close") + .map((event) => ({ id: event.coroutineId, status: event.result.status })); +} + +/** The value each closed child coroutine settled with, in journal order. */ +function childCloseValues(events: DurableEvent[]): Array<[string, Json]> { + const found: Array<[string, Json]> = []; + for (const event of events) { + if (event.type === "close" && event.coroutineId.includes(".") && event.result.status === "ok") { + found.push([event.coroutineId, event.result.value ?? null]); + } + } + return found; +} + +const TWO_GATES = [ + "", + '', + '', + "", +].join("\n"); + +describe("Tier ALL — PA1: both children are live, and output follows the source", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL1: releasing the second child first appends it first and renders it second", function* () { + const result = yield* run(TWO_GATES, { + *drive(shared) { + // Both children have to be live at once before either is released: + // a sequential expansion can never satisfy this, because the second + // child would not start until the first had finished. + yield* shared.arrival("one"); + yield* shared.arrival("two"); + expect(shared.arrived).toEqual(["one", "two"]); + shared.release("two"); + yield* shared.append("two"); + shared.release("one"); + }, + }); + + expect(result.ok).toBe(true); + // Completion order decided the journal; it did not decide the document. + expect(gateOrder(result.events)).toEqual(["two", "one"]); + expect(result.output).toContain("[one][two]"); + expect(result.output).not.toContain("[two][one]"); + }); + + it("ALL2: no child output is published before the join succeeds", function* () { + const emitted: string[] = []; + const result = yield* run(TWO_GATES, { + *drive(shared, stream) { + stream.onAppend = (event) => { + const name = recordedName(event); + if (name !== undefined) { + // What the document has emitted at the moment one child's own + // durable work lands: nothing of either child's, because the join + // has not returned. + emitted.push(`append:${name}`); + } + }; + yield* shared.arrival("one"); + yield* shared.arrival("two"); + shared.release("two"); + shared.release("one"); + }, + }); + + expect(result.ok).toBe(true); + expect(emitted).toHaveLength(2); + expect(result.output).toContain("[one][two]"); + }); +}); + +describe("Tier ALL — PA2: durable identity follows the source", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL3: children close on source-ordered coroutines under the one that reached ", function* () { + const result = yield* run(TWO_GATES, { + *drive(shared) { + yield* shared.arrival("one"); + yield* shared.arrival("two"); + shared.release("two"); + yield* shared.append("two"); + shared.release("one"); + }, + }); + + expect(result.ok).toBe(true); + const coroutines = gateCoroutines(result.events); + // Source order, not completion order: "two" finished first and is still + // the second child. + expect(coroutines.get("one")).toBe("root.0"); + expect(coroutines.get("two")).toBe("root.1"); + expect(closes(result.events)).toEqual([ + { id: "root.1", status: "ok" }, + { id: "root.0", status: "ok" }, + { id: "root", status: "ok" }, + ]); + // Each child closes with exactly the markdown it rendered, and with what + // that markdown is: a run says whether those bytes are prose or a program's + // approved source, because the segments they came from do not leave the + // child and a replay has nothing else to read the disposition from. + expect(new Map(childCloseValues(result.events))).toEqual( + new Map([ + ["root.1", [{ text: "[two]", exact: false }]], + ["root.0", [{ text: "[one]", exact: false }]], + ]), + ); + }); + + it("ALL4: nesting produces hierarchical child identities", function* () { + const result = yield* run( + [ + "", + "", + "", + '', + '', + "", + "", + '', + "", + ].join("\n"), + { + *drive(shared) { + yield* shared.arrival("inner-a"); + yield* shared.arrival("inner-b"); + yield* shared.arrival("outer"); + shared.release("inner-a"); + shared.release("inner-b"); + shared.release("outer"); + }, + }, + ); + + expect(result.ok).toBe(true); + const coroutines = gateCoroutines(result.events); + expect(coroutines.get("inner-a")).toBe("root.0.0"); + expect(coroutines.get("inner-b")).toBe("root.0.1"); + expect(coroutines.get("outer")).toBe("root.1"); + }); + + it("ALL5: a complete replay runs no spawned work at all", function* () { + const stream = new InMemoryStream(); + const live = harness(); + const first = yield* run(TWO_GATES, { + shared: live, + stream, + *drive(shared) { + yield* shared.arrival("one"); + yield* shared.arrival("two"); + shared.release("two"); + shared.release("one"); + }, + }); + expect(first.ok).toBe(true); + expect(live.ran).toEqual(["gate:one", "gate:two"]); + + const replayed = yield* run(TWO_GATES, { stream: new InMemoryStream(first.events) }); + expect(replayed.ok).toBe(true); + expect(replayed.output).toBe(first.output); + // Nothing spawned ran, so no barrier had to be opened for the replay. + expect(gateOrder(replayed.events)).toEqual(gateOrder(first.events)); + }); +}); + +const COUNTER_DOC = [ + "```js eval", + "output('BEFORE');", + "```", + "", + "", + "", + "", + "```js eval", + "output('S1');", + "```", + "", + '', + "", + "", + "", + "", + "```js eval", + "output('S2');", + "```", + "", + '', + "", + "", + "", + "", + "```js eval", + "output('AFTER');", + "```", +].join("\n"); + +/** + * Hold every import of `component` until `width` of them are live, then release + * them in `order`. + * + * The barrier is inside `importComponent` deliberately. `` holds a child in + * its component *body*, which its import has already finished by then — so two + * gated children prove the bodies overlapped, not the imports. Only a wait + * inside the import puts two of them in flight together, which is the one + * arrangement in which an import could take a sibling's canonical selection. + */ +function heldImports( + component: string, + width: number, + order: readonly number[], +): () => Operation { + const arrivals = Array.from({ length: width }, () => signal()); + const releases = Array.from({ length: width }, () => signal()); + let seen = 0; + return function* () { + yield* spawn(function* () { + for (const [index, arrival] of arrivals.entries()) { + yield* awaiting( + `the import of <${component}> number ${index + 1} starting`, + arrival.published, + ); + } + for (const index of order) { + releases[index].publish(); + } + }); + yield* Component.around( + { + *importComponent([name, position], next) { + if (name !== component || seen >= width) { + return yield* next(name, position); + } + const index = seen++; + arrivals[index].publish(); + yield* awaiting( + `the import of <${component}> number ${index + 1} being released`, + releases[index].published, + ); + return yield* next(name, position); + }, + }, + { at: "max" }, + ); + }; +} + +/** Two spawns, each held at its own gate and then naming its own identity. */ +const GATED_PROBES = [ + "", + '', + '', + "", + "", +].join("\n"); + +describe("Tier ALL — PA2: each concurrent site owns its own identity", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL3b: two concurrent authored sites each name their own identity, in either release order", function* () { + // Both children are held at their gates and then inside their own import, so + // the two imports are live at the same time. What this row holds is what the + // *document* decides under that overlap: which authored site each expansion + // identity belongs to, and that each site's component ran once. Where the + // canonical selection landed is CIV27's row, over declared components. + const first = harness(); + const forwards = yield* run(GATED_PROBES, { + shared: first, + // Both gates open first, and then both children suspend inside their own + // import until the other one is there too. + install: heldImports("Probe", 2, [0, 1]), + *drive(shared) { + yield* shared.arrival("one"); + yield* shared.arrival("two"); + shared.release("one"); + shared.release("two"); + }, + }); + expect(forwards.ok).toBe(true); + + const p1 = first.identities.get("p1"); + const p2 = first.identities.get("p2"); + expect(typeof p1).toBe("string"); + expect(typeof p2).toBe("string"); + expect(p1).not.toBe(p2); + // Each site claimed once. A frame that received a second selection would + // have settled to nothing and the invocation would name no identity at all. + expect(first.ran.filter((one) => one.startsWith("probe:"))).toEqual(["probe:p1", "probe:p2"]); + + // The same document, released in the opposite order. Ownership is the + // authored element's, so scheduling decides nothing about it. + const second = harness(); + const backwards = yield* run(GATED_PROBES, { + shared: second, + install: heldImports("Probe", 2, [1, 0]), + *drive(shared) { + yield* shared.arrival("one"); + yield* shared.arrival("two"); + shared.release("two"); + shared.release("one"); + }, + }); + expect(backwards.ok).toBe(true); + expect(second.identities.get("p1")).toBe(p1); + expect(second.identities.get("p2")).toBe(p2); + }); + + it("ALL3c: one child failing leaves the surviving sibling's own identity intact", function* () { + // One import is cancelled while the other is still live. There is no frame + // registry for a cancelled branch to leave behind, so the sibling that + // already claimed keeps exactly what it claimed. + const shared = harness(); + const result = yield* run( + [ + "", + '', + '', + "", + "", + ].join("\n"), + { + shared, + *drive(inner) { + yield* inner.arrival("one"); + yield* inner.arrival("two"); + // The survivor runs first, then the sibling fails under it. + inner.release("one"); + yield* inner.append("one"); + inner.release("two"); + }, + }, + ); + + expect(result.ok).toBe(false); + // The join published nothing, and the child that did run named its own + // identity rather than nothing or its sibling's. + const p1 = shared.identities.get("p1"); + expect(typeof p1).toBe("string"); + // The same identity that site names when nothing fails beside it: what the + // element is called its work after does not depend on a sibling at all. + const alone = harness(); + const quiet = yield* run(GATED_PROBES, { + shared: alone, + *drive(inner) { + inner.release("one"); + inner.release("two"); + }, + }); + expect(quiet.ok).toBe(true); + expect(p1).toBe(alone.identities.get("p1")); + }); +}); + +describe("Tier ALL — PA3: counters, paths and the parent's own numbering", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL6: children reuse local block ids, and later parent work keeps its own", function* () { + const shared = harness(); + const result = yield* run(COUNTER_DOC, { shared }); + + expect(result.ok).toBe(true); + const entries = evalEntries(result.events); + const onParent = entries.filter((entry) => entry.coroutineId === "root"); + // The parent's counter never saw the children: the block after `` is + // numbered as though no child had run. + expect(onParent.map((entry) => entry.name)).toEqual(["eval:eval:root:0", "eval:eval:root:1"]); + + const inChildren = entries.filter((entry) => entry.coroutineId !== "root"); + // `sort()` rather than `toSorted()`: the Node typecheck targets ES2022, where + // the latter does not exist, and the array being sorted is the one `map` just + // made — so there is nothing of anyone else's to mutate. + expect(inChildren.map((entry) => entry.coroutineId).sort()).toEqual(["root.0", "root.1"]); + // Both children number their own first block the same way and collide with + // nothing, because the child coroutine namespaces it. + expect(new Set(inChildren.map((entry) => entry.name))).toEqual(new Set(["eval:eval:root:0"])); + + // Two spawns, two stable and distinct expansion identities. + const p1 = shared.identities.get("p1"); + const p2 = shared.identities.get("p2"); + expect(typeof p1).toBe("string"); + expect(typeof p2).toBe("string"); + expect(p1).not.toBe(p2); + + // A second run of the same document derives all three again. + const again = harness(); + const rerun = yield* run(COUNTER_DOC, { shared: again }); + expect(rerun.ok).toBe(true); + expect(again.identities.get("p1")).toBe(p1); + expect(again.identities.get("p2")).toBe(p2); + // The same identities on the same coroutines. Compared as a set, because + // which child appends first is exactly what scheduling is allowed to + // decide — the contract fixes what a block is called and where, never the + // order two independent children reach the journal. + // `sort()` for the same reason, on the array `map` just made. + const identify = (found: EvalEntry[]): string[] => + found.map((entry) => `${entry.coroutineId}/${entry.name}`).sort(); + expect(identify(evalEntries(rerun.events))).toEqual(identify(entries)); + + // And a replay reproduces the rendering without running any of it. + const idle = harness(); + const replayed = yield* run(COUNTER_DOC, { + shared: idle, + stream: new InMemoryStream(result.events), + }); + expect(replayed.output).toBe(result.output); + expect(idle.ran).toEqual([]); + }); +}); + +const ISOLATION_DOC = [ + "", + "", + 'chosen', + '', + "spawn1({picked}/{handle})", + "", + "", + "spawn2({picked}/{handle})", + "", + "", + "", + "after({picked}/{handle})", + "", + '', +].join("\n"); + +describe("Tier ALL — PA4: a child's bindings and resources are its own", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL7: a binding and a retained resource live on in their child and nowhere else", function* () { + const shared = harness(); + const result = yield* run(ISOLATION_DOC, { shared }); + + expect(result.ok).toBe(true); + // Later work in the same child reads both. + expect(result.output).toContain("spawn1(chosen/held:kept)"); + // The sibling and the work after `` read neither: the names are not + // bound there, so they render as the text that was written. + expect(result.output).toContain("spawn2({picked}/{handle})"); + expect(result.output).toContain("after({picked}/{handle})"); + // The resource lived while the child did, and was released when it ended. + expect(shared.timeline).toEqual(["start:kept", "stop:kept"]); + expect(shared.observed).toEqual(["after:released"]); + }); +}); + +describe("Tier ALL — PA5: a partial replay restores one child and resumes the other", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL8: the completed child is restored and only the unrecorded child runs", function* () { + const stream = new InMemoryStream(); + const live = harness(); + const golden = yield* run(TWO_GATES, { + shared: live, + stream, + *drive(shared, journal) { + const closedOne = signal(); + journal.onAppend = (event) => { + if (event.type === "close" && event.coroutineId === "root.0") { + closedOne.publish(); + } + }; + yield* shared.arrival("one"); + yield* shared.arrival("two"); + // The first child is released and allowed to close before the second + // records anything, so the truncation below has exactly one complete + // child and one that has not started its work. + shared.release("one"); + yield* awaiting("the first child closing", closedOne.published); + shared.release("two"); + }, + }); + expect(golden.ok).toBe(true); + expect(golden.output).toContain("[one][two]"); + + const events = golden.events; + const closedAt = events.findIndex( + (event) => event.type === "close" && event.coroutineId === "root.0", + ); + expect(closedAt).toBeGreaterThan(0); + const partial = events.slice(0, closedAt + 1); + expect(partial.some((event) => event.type === "close" && event.coroutineId === "root.1")).toBe( + false, + ); + expect(gateOrder(partial)).toEqual(["one"]); + + const resumed = harness(); + const replayed = yield* run(TWO_GATES, { + shared: resumed, + stream: new InMemoryStream(partial), + *drive(shared) { + yield* shared.arrival("two"); + shared.release("two"); + }, + }); + + expect(replayed.ok).toBe(true); + // The completed child's rendering came back from its recorded close, and + // only the child with no close ran. + expect(replayed.output).toBe(golden.output); + expect(resumed.ran).toEqual(["gate:two"]); + expect(gateCoroutines(replayed.events).get("two")).toBe("root.1"); + }); +}); + +describe("Tier ALL — PA6: a failure cancels its siblings and publishes nothing", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL9: a held sibling is cancelled and joined, and renders nothing", function* () { + const shared = harness(); + const result = yield* run( + [ + "", + '', + '', + "", + "", + "tail", + ].join("\n"), + { + shared, + *drive(inner) { + yield* inner.arrival("bad"); + yield* inner.arrival("held"); + // The sibling is still holding at its barrier when the first child + // fails, and is never released by this driver. + inner.release("bad"); + }, + }, + ); + + expect(result.ok).toBe(false); + expect(result.failure).toContain("FAILED:bad"); + // Neither child's private buffer reached the document, and nothing after + // `` ran. + expect(result.output).not.toContain("[held]"); + expect(result.output).not.toContain("tail"); + // The held sibling's scope was torn down as part of the join. + expect(shared.timeline).toEqual(["start:kept", "stop:kept"]); + // The journal holds only what was acknowledged: the failing child closed + // with its error, the cancelled one as cancelled, and the held gate never + // recorded work it did not do. + expect(gateOrder(result.events)).toEqual([]); + const closed = new Map(closes(result.events).map((entry) => [entry.id, entry.status])); + expect(closed.get("root.0")).toBe("err"); + expect(closed.get("root.1")).toBe("cancelled"); + }); + + it("ALL9b: a child that already succeeded publishes nothing when a sibling fails", function* () { + const shared = harness(); + const result = yield* run( + [ + "", + '', + '', + "", + "", + "tail", + ].join("\n"), + { + shared, + *drive(inner) { + yield* inner.arrival("done"); + yield* inner.arrival("bad"); + // The first child runs to completion, and only then does its sibling + // fail: its rendering exists, and the join is what decides whether + // the document ever sees it. + inner.release("done"); + yield* inner.append("done"); + inner.release("bad"); + }, + }, + ); + + expect(result.ok).toBe(false); + expect(result.failure).toContain("FAILED:bad"); + // The successful child's own markdown was never published: the join failed, + // so `` emitted none of it. + expect(result.output).not.toContain("[done]"); + expect(result.output).not.toContain("tail"); + // Its durable record stands, because the journal only ever held work that + // was acknowledged. + expect(gateOrder(result.events)).toEqual(["done"]); + expect(shared.timeline).toEqual(["start:kept", "stop:kept"]); + const closed = new Map(closes(result.events).map((entry) => [entry.id, entry.status])); + expect(closed.get("root.0")).toBe("ok"); + expect(closed.get("root.1")).toBe("err"); + }); + + it("ALL10: cancelling the document leaves no spawned work alive", function* () { + const shared = harness(); + const stream = new InMemoryStream(); + const outcome = yield* scoped(function* () { + yield* useHostFiles(); + yield* useStubFs({ + "test.md": [ + "", + '', + '', + "", + ].join("\n"), + }); + yield* useEchoExec(); + yield* useComponents(shared); + return yield* race([ + (function* (): Operation { + const execution = yield* execute({ path: "test.md", stream }); + yield* collect(execution); + return "finished"; + })(), + (function* (): Operation { + yield* shared.arrival("one"); + yield* shared.arrival("two"); + return "cancelled"; + })(), + ]); + }); + + expect(outcome).toBe("cancelled"); + // The scope exited while both children were held, and both were joined: + // the retained resource is released and no gate recorded work. + expect(shared.timeline).toEqual(["start:kept", "stop:kept"]); + expect(gateOrder(stream.snapshot())).toEqual([]); + }); +}); + +describe("Tier ALL — PA7: malformed structure refuses before any child starts", () => { + beforeAll(() => useTempFileCompiler()); + + const cases: Array<[string, string, string]> = [ + [ + "ALL11: fewer than two spawns", + ["", '', ""].join("\n"), + "at least two ", + ], + [ + "ALL12: a prop on ", + [ + "", + '', + '', + "", + ].join("\n"), + " accepts no props", + ], + ["ALL13: a self-closing ", "", "written paired"], + [ + "ALL14: a self-closing ", + ["", "", '', ""].join("\n"), + " holds the markdown", + ], + [ + "ALL15: a prop on ", + [ + "", + '', + '', + "", + ].join("\n"), + " accepts no props", + ], + [ + "ALL16: substantive direct text", + [ + "", + "stray words", + '', + '', + "", + ].join("\n"), + " holds only children", + ], + [ + "ALL17: a direct component child", + [ + "", + '', + '', + '', + "", + ].join("\n"), + " holds only children", + ], + [ + "ALL18: a with no ", + '', + "direct child of ", + ], + [ + "ALL19: a below its rather than directly inside it", + [ + "", + '', + '', + "", + ].join("\n"), + "direct child of ", + ], + ]; + + for (const [title, source, expected] of cases) { + it(title, function* () { + const shared = harness(); + const result = yield* run(source, { shared }); + expect(result.ok).toBe(false); + expect(result.failure).toContain(expected); + // No tripwire in any child ran: the refusal is decided from source + // before a child is constructed. + expect(shared.ran).toEqual([]); + }); + } + + it("ALL19b: non-executing validation reports one nested misplaced once", function* () { + const shared = harness(); + const source = [ + "", + '', + '', + "", + ].join("\n"); + yield* useComponents(shared); + // Validation rather than a run, because a refusal reports the first thing + // wrong and this row is about how many things are reported. + const validation = yield* validateDocument(inlineSource(source)); + const misplaced = validation.diagnostics.filter((diagnostic) => + diagnostic.message.includes("must be a direct child of "), + ); + // One owner, one diagnostic. The `` walks its whole region, so the + // element's own rule has nothing left to say about a spawn below an `` + // — and a reader told the same thing twice about one mistake looks for two. + expect(misplaced).toHaveLength(1); + // Anchored where it was written: the inner ``, not the `` that + // found it and not the `` it was written inside. + expect(misplaced[0]?.position?.line).toBe(2); + expect(misplaced[0]?.position?.column).toBe(29); + // Nothing ran to decide any of it. + expect(shared.ran).toEqual([]); + }); + + it("ALL20: a repository file cannot supply or ", function* () { + const shared = harness(); + const result = yield* run("from a file", { + shared, + files: { "components/Spawn.md": "a repository spawn\n" }, + }); + expect(result.output).not.toContain("a repository spawn"); + expect(result.failure).toContain("never resolves a component"); + expect(shared.ran).toEqual([]); + }); +}); + +describe("Tier ALL — PA8: and cannot cross a spawn", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL21: a in a spawn cannot exit a loop outside ", function* () { + const shared = harness(); + const result = yield* run( + [ + "", + "", + "", + '', + "", + "", + ].join("\n"), + { shared }, + ); + expect(result.ok).toBe(false); + expect(result.failure).toContain(" cannot cross "); + expect(shared.ran).toEqual([]); + }); + + it("ALL22: a loop wholly inside a spawn is broken by its own ", function* () { + const result = yield* run( + [ + "", + "a", + "b", + "", + ].join("\n"), + ); + expect(result.ok).toBe(true); + expect(result.output).toContain("ab"); + expect(result.output).not.toContain("ERROR"); + }); + + it("ALL23: a in a spawn satisfies no value root", function* () { + const result = yield* run( + [ + "---", + "returns:", + " type: string", + "---", + "", + "", + '', + "b", + "", + ].join("\n"), + ); + expect(result.ok).toBe(false); + // The body still owes its own ``: the one inside the spawn neither + // satisfies the declaration nor stands in for it. + expect(result.failure).toContain("no "); + }); + + it("ALL24: a value component invoked inside a spawn keeps its own ", function* () { + const result = yield* run( + [ + "", + 'got {picked}', + "b", + "", + ].join("\n"), + { + files: { + "components/Answerer.md": [ + "---", + "returns:", + " type: string", + "---", + "", + '', + ].join("\n"), + }, + }, + ); + expect(result.ok).toBe(true); + expect(result.output).toContain("got an answer"); + expect(result.output).toContain("b"); + }); + + it("ALL25: non-executing validation reports both boundaries where they were written", function* () { + const shared = harness(); + const source = [ + "", + "", + "", + '', + "", + "", + ].join("\n"); + yield* useComponents(shared); + const validation = yield* validateDocument(inlineSource(source)); + const messages = validation.diagnostics.map((diagnostic) => diagnostic.message); + expect(messages.some((message) => message.includes(" cannot cross "))).toBe(true); + expect(messages.some((message) => message.includes(" cannot cross "))).toBe( + true, + ); + // One mistake, one diagnostic: the stray-element sentences are not also + // reported for the same two elements. + expect(messages.some((message) => message.includes("must be written inside a "))).toBe( + false, + ); + }); +}); + +const GENERATED_ROOT = "workflows/agent.md"; +const GENERATED_SOURCE = "The host ran a generated fragment.\n"; + +function hostIdentity(origin: string, key: string): RetainedFragmentIdentity { + return { kind: "component-answer", origin, key, revision: "1" }; +} + +const GENERATED_PROBE: FunctionComponentDefinition = { + kind: "function", + name: "Probe", + props: NO_PROPS, + // deno-lint-ignore require-yield + *fn() { + return "probed"; + }, +}; + +function generatedProbe(): GeneratedObservation { + return pinnedComponent("Probe", hostIdentity("test://probe", "Probe"), GENERATED_PROBE); +} + +function generatedRequest(source: string): GeneratedXmdRequest { + return { + id: "turn-1", + source, + workspaceRoots: ["workspace://primary"], + selectedRoot: "workspace://primary", + observations: [generatedProbe()], + }; +} + +interface Attempt { + readonly output?: string; + readonly failure?: string; + readonly events: DurableEvent[]; +} + +function evaluateFragment(source: string): Operation { + return scoped(function* () { + const stream = new InMemoryStream(); + const captured: { result?: string } = {}; + // A preparation yields durable effects, and the evaluator is an ordinary + // operation whose *own* effects identify themselves against the durable + // root they run in. `ephemeral()` is the bridge for exactly that: the + // wrapper writes no journal entry of its own, and the evaluator's + // admission record still lands under the root coroutine. + const prepare: DurablePreparation = function* () { + captured.result = yield* ephemeral(evaluateGeneratedXmd(generatedRequest(source))); + }; + const installation: ExecutionInstallation = { prepare }; + const execution = yield* executeInstalled( + { ...retainedSource(GENERATED_ROOT, GENERATED_SOURCE), stream, includes: [] }, + [installation], + ); + const result = yield* execution; + const events = stream.snapshot(); + return result.ok + ? { output: captured.result ?? "", events } + : { failure: result.error.message, events }; + }); +} + +/** What each generated-XMD admission decided. */ +function decisions(events: DurableEvent[]): string[] { + const found: string[] = []; + for (const event of events) { + if ( + event.type !== "yield" || + event.description.type !== "generated_xmd" || + event.result.status !== "ok" + ) { + continue; + } + const value = event.result.value; + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + const record: JsonObject = value; + found.push(typeof record.decision === "string" ? record.decision : ""); + } + } + return found; +} + +describe("Tier ALL — PA9: the construct exists everywhere the language does", () => { + beforeAll(() => useTempFileCompiler()); + + it("ALL26: the canonical syntax surface lists both names as structural", function* () { + const catalog = yield* inspectSyntax({ includes: [] }); + const names = catalog.categories[0].entries.map((entry) => entry.name); + expect(names).toContain("All"); + expect(names).toContain("Spawn"); + expect(RESERVED_STRUCTURAL.has("All")).toBe(true); + expect(RESERVED_STRUCTURAL.has("Spawn")).toBe(true); + }); + + it("ALL27: admitted generated XMD runs a valid ", function* () { + const attempt = yield* evaluateFragment( + ["", "", "", ""].join("\n"), + ); + expect(attempt.failure).toBe(undefined); + expect(attempt.output).toContain("probed"); + expect(decisions(attempt.events)).toEqual(["admitted"]); + }); + + it("ALL28: a malformed refuses the fragment before its first effect", function* () { + const attempt = yield* evaluateFragment( + ["", "", ""].join("\n"), + ); + expect(decisions(attempt.events)).toEqual(["refused"]); + expect(attempt.output ?? "").not.toContain("probed"); + }); + + it("ALL29: a stray refuses the fragment", function* () { + const attempt = yield* evaluateFragment("\n"); + expect(decisions(attempt.events)).toEqual(["refused"]); + expect(attempt.output ?? "").not.toContain("probed"); + }); +}); + +/** + * Tier PA10a/c — the Story's own example: an ordinary `` in each spawn. + * + * `` is a capability-backed identity component, so two of them running + * at once is the case where canonical resolution has to attribute each answer + * and each selection to the exact import that opened it. These rows drive the + * real components over a real journal; nothing here stands in for ``. + */ + +/** One conversation the stubbed provider holds, and how it is let go. */ +interface Conversation { + /** Session keys that reached the provider, in arrival order. */ + readonly arrived: string[]; + /** Whether a provider factory was materialized, and how often. */ + activations: () => number; + /** How many implementations the declared `` factory built. */ + sessions: () => number; + reached(sessionKey: string): Operation; + release(sessionKey: string): void; + readonly installation: ExecutionInstallation; + readonly declaration: ExecutionInstallation; +} + +function conversations(options: { gated?: boolean } = {}): Conversation { + const arrived: string[] = []; + const arrivals = new Map(); + const releases = new Map(); + const slot = (map: Map, key: string): Signal => { + const existing = map.get(key); + if (existing !== undefined) { + return existing; + } + const created = signal(); + map.set(key, created); + return created; + }; + let activations = 0; + let built = 0; + + const declared = agentIdentityComponents().map((component) => ({ + ...component, + factory: (claim: Parameters[0]) => { + // One implementation per declared component per execution, however many + // spawns invoke it. A domain minted per `` would show up here. + built += 1; + return component.factory(claim); + }, + })); + + return { + arrived, + activations: () => activations, + sessions: () => built, + reached(sessionKey: string) { + return awaiting(`${sessionKey} reaching the provider`, slot(arrivals, sessionKey).published); + }, + release(sessionKey: string) { + slot(releases, sessionKey).publish(); + }, + declaration: { components: declared }, + installation: { + *install(): Operation { + yield* installAgentComponents({ + defaultAgent: "stub-agent", + rootProvider: { + options: { defaultAgent: "stub-agent", permissionMode: "deny-all" }, + factory: function* () { + activations += 1; + yield* Agent.around( + { + // deno-lint-ignore require-yield + *agent([name]) { + return name ?? "stub-agent"; + }, + // deno-lint-ignore require-yield + *session([routed]) { + const name = typeof routed === "string" ? routed : routed?.name; + return { sessionKey: `stub:${name ?? "default"}`, cwd: "/stub" }; + }, + // deno-lint-ignore require-yield + *prompt([_content, promptOptions]) { + const session = promptOptions?.session; + const key = + typeof session === "object" && session !== null && "sessionKey" in session + ? String(session.sessionKey) + : "stub:default"; + return { + *[Symbol.iterator]() { + arrived.push(key); + slot(arrivals, key).publish(); + if (options.gated === true) { + yield* awaiting(`${key} being released`, slot(releases, key).published); + } + // Declared as the events they are, rather than as + // literals narrowed at the return: the provider's own + // vocabulary is what a turn produces. + const turn: AgentPromptEvent[] = [ + { + type: "started", + agent: "stub-agent", + session: { sessionKey: key, cwd: "/stub" }, + }, + { type: "text_delta", text: `[${key}]` }, + { type: "terminal", status: "completed" }, + ]; + let stage = 0; + return { + // deno-lint-ignore require-yield + *next() { + const event = turn[stage]; + if (event === undefined) { + return { done: true, value: `[${key}]` }; + } + stage += 1; + return { done: false, value: event }; + }, + }; + }, + }; + }, + }, + { at: "min" }, + ); + }, + }, + }); + }, + }, + }; +} + +/** What one Session document produced. */ +interface Talked { + readonly ok: boolean; + readonly failure: string; + readonly output: string; + readonly events: DurableEvent[]; +} + +function talking( + source: string, + held: Conversation, + options: { stream?: InMemoryStream; drive?: () => Operation } = {}, +): Operation { + return scoped(function* () { + const stream = options.stream ?? new InMemoryStream(); + if (options.drive) { + yield* spawn(options.drive); + } + const chunks: string[] = []; + const result = yield* scoped(function* () { + const execution = yield* executeInstalled({ ...inlineSource(source), stream }, [ + held.declaration, + held.installation, + ]); + try { + yield* forEach(function* (chunk: string) { + chunks.push(chunk); + }, execution.output); + } catch { + // The outcome below says why the stream stopped. + } + return yield* execution; + }); + return { + ok: result.ok, + failure: result.ok ? "" : result.error.message, + output: chunks.join(""), + events: stream.snapshot(), + }; + }); +} + +/** Every appended `agent_prompt`, in journal order, with its coroutine. */ +function prompts(events: readonly DurableEvent[]): Array<{ name: string; coroutineId: string }> { + return events + .filter((event) => event.type === "yield" && event.description.type === "agent_prompt") + .map((event) => ({ + name: event.type === "yield" ? event.description.name : "", + coroutineId: event.coroutineId, + })); +} + +const TWO_SESSIONS = [ + "", + '', + '', + "", +].join("\n"); + +describe("Tier PA10a — two ordinary Sessions at the same time", () => { + beforeAll(() => useTempFileCompiler()); + + it("PA10a: both conversations are live, and releasing the second first renders in source order", function* () { + const held = conversations({ gated: true }); + const stream = new InMemoryStream(); + // The journal says when a record lands, so the second release waits on the + // first append rather than on a clock. + const recordedOne = signal(); + stream.onAppend = (event) => { + if (event.type === "yield" && event.description.type === "agent_prompt") { + recordedOne.publish(); + } + }; + const result = yield* talking(TWO_SESSIONS, held, { + stream, + *drive() { + // Both ordinary `` paths reach the provider before + // either is let go, which a sequential engine could never produce. + yield* held.reached("stub:planner"); + yield* held.reached("stub:reviewer"); + expect([...held.arrived].sort()).toEqual(["stub:planner", "stub:reviewer"]); + // The second spawn goes first, so its record appends first. + held.release("stub:reviewer"); + yield* awaiting("the reviewer recording its turn", recordedOne.published); + held.release("stub:planner"); + }, + }); + + expect(result.ok).toBe(true); + // One declared component, one implementation, one provider installation + // serving both spawns — not a domain per spawn. + expect(held.sessions()).toBe(1); + expect(held.activations()).toBe(1); + + const recorded = prompts(result.events); + expect(recorded).toHaveLength(2); + // Completion order in the journal: the second spawn's child coroutine + // appended first, and each record is its own authored invocation. + expect(recorded.map((entry) => entry.coroutineId)).toEqual(["root.1", "root.0"]); + expect(new Set(recorded.map((entry) => entry.name)).size).toBe(2); + // And the document still renders in the order the spawns were written. + expect(result.output.indexOf("[stub:planner]")).toBeGreaterThanOrEqual(0); + expect(result.output.indexOf("[stub:planner]")).toBeLessThan( + result.output.indexOf("[stub:reviewer]"), + ); + }); +}); + +describe("Tier PA10c — nesting and replay with ordinary Sessions", () => { + beforeAll(() => useTempFileCompiler()); + + it("PA10c: a nested resolves two capability-backed children concurrently", function* () { + const held = conversations(); + const result = yield* talking( + [ + "", + "", + "", + '', + '', + "", + "", + '', + "", + ].join("\n"), + held, + ); + + expect(result.ok).toBe(true); + expect(held.sessions()).toBe(1); + const recorded = prompts(result.events); + expect(recorded).toHaveLength(3); + // Hierarchical child identity, source-ordered: a spawn inside a spawn + // owns its own frames as much as a top-level one does. + expect(new Set(recorded.map((entry) => entry.coroutineId))).toEqual( + new Set(["root.0.0", "root.0.1", "root.1"]), + ); + expect(new Set(recorded.map((entry) => entry.name)).size).toBe(3); + }); + + it("PA10c: a full replay calls no provider, and a partial one reaches only the unrecorded child", function* () { + const golden = conversations({ gated: true }); + const stream = new InMemoryStream(); + // The first child is released and allowed to close before the second does + // anything, so the truncation below has exactly one complete child and one + // that has not started its work. + const closedFirst = signal(); + stream.onAppend = (event) => { + if (event.type === "close" && event.coroutineId === "root.0") { + closedFirst.publish(); + } + }; + const first = yield* talking(TWO_SESSIONS, golden, { + stream, + *drive() { + yield* golden.reached("stub:planner"); + yield* golden.reached("stub:reviewer"); + golden.release("stub:planner"); + yield* awaiting("the first child closing", closedFirst.published); + golden.release("stub:reviewer"); + }, + }); + expect(first.ok).toBe(true); + expect(golden.arrived).toHaveLength(2); + + // Nothing spawned runs again: both children closed, so replay reads their + // recorded results. + const replayed = conversations(); + const whole = yield* talking(TWO_SESSIONS, replayed, { + stream: new InMemoryStream(first.events), + }); + expect(whole.ok).toBe(true); + expect(replayed.arrived).toEqual([]); + expect(whole.output).toBe(first.output); + + // Cut after the first child closed. The completed child is restored and + // only the other one reaches a provider, under the same child identity. + const closedAt = first.events.findIndex( + (event) => event.type === "close" && event.coroutineId === "root.0", + ); + expect(closedAt).toBeGreaterThan(0); + const cut = first.events.slice(0, closedAt + 1); + // Exactly one complete child, and nothing recorded for the other. + expect(cut.some((event) => event.type === "close" && event.coroutineId === "root.1")).toBe( + false, + ); + expect(prompts(cut).map((entry) => entry.coroutineId)).toEqual(["root.0"]); + + const resumed = conversations(); + const partial = yield* talking(TWO_SESSIONS, resumed, { stream: new InMemoryStream(cut) }); + expect(partial.ok).toBe(true); + // Only the child with no close reached a provider, and it is the other one. + expect(resumed.arrived).toEqual(["stub:reviewer"]); + expect(partial.output).toBe(first.output); + expect(prompts(partial.events).map((entry) => entry.coroutineId)).toEqual(["root.0", "root.1"]); + }); +}); diff --git a/packages/core/tests/answer-identity.test.ts b/packages/core/tests/answer-identity.test.ts index ae71982e..c793c005 100644 --- a/packages/core/tests/answer-identity.test.ts +++ b/packages/core/tests/answer-identity.test.ts @@ -29,6 +29,7 @@ import { describe, it } from "@executablemd/test-support/bdd"; import { expect } from "@executablemd/test-support/expect"; +import { scoped } from "effection"; import type { Operation } from "effection"; import { @@ -580,3 +581,262 @@ describe("Tier CIV — an identity belongs to one answer of one import", () => { }); }); }); + +/** + * Tier PA10b — two sibling spawns resolving at the same time. + * + * `` lets two `` children have an import open together, so a + * resolution is no longer the only one in flight. What makes an answer this + * import's is the exact window it was asked in, and windows now belong to the + * engine scope that opened them — so these rows are written with two real + * scopes standing for two spawns, and never with one spawn's bookkeeping + * standing in for the other's. + */ +describe("Tier PA10b — one serial resolution window", () => { + /** + * Component answers are captured serially, before any document expands. + * + * That is the whole reason no scope takes part here: the execution asks for + * each admitted name in turn, so there is one window at a time, and what a + * request answers is the window object it captured when its handler was + * opened — not whichever window is open when it happens to claim, and not + * anything about where it is running. + */ + it("PA10b: a request answers the window it captured, not the one open later", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + + const first = imports.beginResolution("Open"); + const kept = provider.open("Open"); + // The capture phase moves on: this resolution closes and the next name's + // window opens. The request is still holding the first one. + first.close(); + const second = imports.beginResolution("Open"); + try { + expect(refusalOf(() => kept.request.claim(answer(), OPEN))).toBeInstanceOf( + AnswerIdentityError, + ); + // And the window that is open answers for what was claimed in it. + const claimed = asking(provider, "Open", (request) => request.claim(answer(), OPEN)); + expect(imports.identify(second, claimed)?.identity).toEqual(IDENTITY); + } finally { + kept.close(); + second.close(); + } + }); + + it("PA10b: a handler that delegates through a descendant scope still identifies", function* () { + // The request captured its window before the handler ran, so a handler that + // does its work in a scope of its own answers the same resolution. Nothing + // about the scope participates in the correlation. + const imports = owner(); + const provider = imports.provider(ORIGIN); + + const window = imports.beginResolution("Open"); + try { + const claimed = yield* scoped(function* (): Operation { + return asking(provider, "Open", (request) => request.claim(answer(), OPEN)); + }); + expect(imports.identify(window, claimed)?.identity).toEqual(IDENTITY); + } finally { + window.close(); + } + }); + + it("PA10b: with no window open, a provider answers and identifies nothing", function* () { + // Where concurrent document imports are: after capture, with no profile + // window open at all. A handler may still answer such an import as ordinary + // middleware; what it cannot do is acquire a profile identity for it. + const imports = owner(); + const provider = imports.provider(ORIGIN); + + const window = imports.beginResolution("Open"); + window.close(); + + expect( + refusalOf(() => asking(provider, "Open", (request) => request.claim(answer(), OPEN))), + ).toBeInstanceOf(AnswerIdentityError); + expect(imports.identify(window, answer())).toBe(undefined); + }); +}); + +describe("Tier PA10c — one definition answering more than one import", () => { + it("PA10c: the same definition answers a later import after the first window closed", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + // One object, created once and never copied: this is what a provider that + // owns its definition hands to everybody who asks. + const shared = answer(); + + const first = resolving(imports, "Open", (resolution) => { + const supplied = asking(provider, "Open", (request) => request.claim(shared, OPEN)); + expect(supplied).toBe(shared); + expect(imports.identify(resolution, shared)?.identity).toEqual(IDENTITY); + return resolution; + }); + + // The second import is a new resolution of the same name, after the first is + // over. The definition is the same object, and it is still this provider's + // answer to say. + resolvingLater(imports, provider, shared); + // And the closed window still reads the claim it recorded, unchanged by the + // later one: each resolution keeps its own. + expect(imports.identify(first, shared)?.identity).toEqual(IDENTITY); + }); + + it("PA10c: reuse cannot substitute between comparison and retention", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + const reads: string[] = []; + const shared = new Proxy(answer(), { + get(target, key, receiver) { + if (key !== "name") { + return Reflect.get(target, key, receiver); + } + reads.push(key); + return reads.length === 1 ? "Open" : "Substituted"; + }, + getOwnPropertyDescriptor(target, key) { + const descriptor = Reflect.getOwnPropertyDescriptor(target, key); + if (key !== "name" || descriptor === undefined || !("value" in descriptor)) { + return descriptor; + } + return { ...descriptor, value: reads.length < 2 ? "Open" : "Substituted" }; + }, + }); + + const first = resolving(imports, "Open", (resolution) => { + asking(provider, "Open", (request) => request.claim(shared, OPEN)); + return resolution; + }); + expect(imports.identify(first, shared)?.definition.name).toBe("Open"); + resolving(imports, "Open", (resolution) => { + asking(provider, "Open", (request) => { + expect(refusalOf(() => request.claim(shared, OPEN))).toBeInstanceOf(AnswerIdentityError); + return undefined; + }); + expect(imports.identify(resolution, shared)).toBe(undefined); + }); + }); + + it("PA10c: an edited definition cannot be claimed again, and identifies nowhere", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + const shared = answer(); + + const first = resolving(imports, "Open", (resolution) => { + asking(provider, "Open", (request) => request.claim(shared, OPEN)); + expect(imports.identify(resolution, shared)?.identity).toEqual(IDENTITY); + return resolution; + }); + + // One member of the definition, edited after it was claimed and while the + // stated identity says nothing changed. This is a different implementation + // wearing the first one's revision. + shared.props = { type: "object", properties: {}, additionalProperties: true }; + + resolving(imports, "Open", (resolution) => { + asking(provider, "Open", (request) => { + // Refused before anything is recorded: the identity was stated about + // the definition core retained, and this object is no longer it. + expect(refusalOf(() => request.claim(shared, OPEN))).toBeInstanceOf(AnswerIdentityError); + return undefined; + }); + // So the new window vouches for nothing. + expect(imports.identify(resolution, shared)).toBe(undefined); + }); + // And the window that did claim it cannot identify the changed object + // either, which is the change detection that was already there. + expect(imports.identify(first, shared)).toBe(undefined); + }); + + it("PA10c: one definition answers two windows in turn, and each holds its own copy", function* () { + // Serial, because capture is: the first resolution closes before the second + // opens. What this holds is that reuse is per window — the second window + // identifies the same definition, and closing either leaves the other's copy + // alone. + const imports = owner(); + const provider = imports.provider(ORIGIN); + const shared = answer(); + + const first = imports.beginResolution("Open"); + asking(provider, "Open", (request) => request.claim(shared, OPEN)); + expect(imports.identify(first, shared)?.identity).toEqual(IDENTITY); + first.close(); + + const second = imports.beginResolution("Open"); + try { + asking(provider, "Open", (request) => request.claim(shared, OPEN)); + expect(imports.identify(second, shared)?.identity).toEqual(IDENTITY); + // And each window keeps its own copy: the first still identifies what it + // claimed, because provenance is a fact about that resolution rather than + // about which one is open now. + expect(imports.identify(first, shared)?.identity).toEqual(IDENTITY); + } finally { + second.close(); + } + }); + + it("PA10c: reuse spends each window, so a different answer in one still refuses", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + const shared = answer(); + + resolvingLater(imports, provider, shared); + // The same definition again in a fresh window is reuse; a *second* answer in + // that same window is two answers to one import, and that still refuses. + resolving(imports, "Open", (resolution) => { + asking(provider, "Open", (request) => { + expect(request.claim(shared, OPEN)).toBe(shared); + expect(refusalOf(() => request.claim(answer(), OPEN))).toBeInstanceOf(AnswerIdentityError); + return undefined; + }); + expect(imports.identify(resolution, shared)?.identity).toEqual(IDENTITY); + }); + }); + + it("PA10c: a new window cannot rename the definition or take it for another provider", function* () { + const imports = owner(); + const provider = imports.provider(ORIGIN); + const other = imports.provider("test://other"); + const shared = answer(); + + resolvingLater(imports, provider, shared); + // What this object is was settled by its first claim. A new resolution is a + // new question about which import it answered, never a chance to restate it. + resolving(imports, "Other", (resolution) => { + asking(provider, "Other", (request) => { + expect(refusalOf(() => request.claim(shared, OTHER))).toBeInstanceOf(AnswerIdentityError); + return undefined; + }); + expect(imports.identify(resolution, shared)).toBe(undefined); + }); + resolving(imports, "Open", (resolution) => { + asking(other, "Open", (request) => { + expect(refusalOf(() => request.claim(shared, OPEN))).toBeInstanceOf(AnswerIdentityError); + return undefined; + }); + expect(imports.identify(resolution, shared)).toBe(undefined); + }); + }); +}); + +/** + * Claim one shared definition in a resolution of its own, and read it back. + * + * Written once because three rows need the same second import: a fresh window, + * the same object, and the same provider saying the same thing about it. All of + * it is synchronous bookkeeping, so it is a plain function — there is nothing + * here for an operation to wait on. + */ +function resolvingLater( + imports: CanonicalImports, + provider: ProviderInstallation, + shared: ImportedDefinition, +): void { + resolving(imports, "Open", (resolution) => { + const supplied = asking(provider, "Open", (request) => request.claim(shared, OPEN)); + expect(supplied).toBe(shared); + expect(imports.identify(resolution, shared)?.identity).toEqual(IDENTITY); + }); +} diff --git a/packages/core/tests/declared-markdown-component.test.ts b/packages/core/tests/declared-markdown-component.test.ts index 17fbadd2..169833a9 100644 --- a/packages/core/tests/declared-markdown-component.test.ts +++ b/packages/core/tests/declared-markdown-component.test.ts @@ -36,7 +36,7 @@ import { API } from "@executablemd/runtime"; import { mkdir, mkdtemp, realpath } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { InMemoryStream } from "@executablemd/durable-streams"; +import { InMemoryStream, StaleInputError } from "@executablemd/durable-streams"; import type { DurableEvent, Json } from "@executablemd/durable-streams"; import { Component } from "../src/component-api.ts"; import { collect } from "../src/collect.ts"; @@ -1389,8 +1389,10 @@ function* published( source: string, declarations: readonly MarkdownComponent[], extra: readonly ExecutionInstallation[], + stream: InMemoryStream = new InMemoryStream(), + /** Where each published chunk lands, so a failed run's output is still readable. */ + chunks: string[] = [], ): Operation { - const chunks: string[] = []; return yield* scoped(function* () { yield* useNormalizedOutput(); yield* useTerminalOutput(); @@ -1404,7 +1406,7 @@ function* published( yield* executeInstalled( { ...retainedSource(ROOT_PATH, source), - stream: new InMemoryStream(), + stream, includes: [], }, [installation(declarations), ...extra], @@ -1433,6 +1435,55 @@ function answeringOpenName(definition: ImportedDefinition): ExecutionInstallatio }; } +/** + * The `{ text, exact }` runs one child's close retained, read rather than + * asserted. + * + * A row about what a durable value *is* cannot cast it into the shape it claims, + * so every member is parsed here and anything else throws — naming what it found — + * instead of arriving at the expectation as a plausible shape nobody checked. + */ +function retainedRuns(event: DurableEvent): { text: string; exact: boolean }[] { + if (event.type !== "close" || event.result.status !== "ok") { + return []; + } + const value = event.result.value; + if (!Array.isArray(value)) { + throw new Error(`the close of ${event.coroutineId} retained no runs: ${JSON.stringify(value)}`); + } + return value.map((entry) => { + if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) { + const { text, exact } = entry; + if (typeof text === "string" && typeof exact === "boolean") { + return { text, exact }; + } + } + throw new Error(`the close of ${event.coroutineId} retained ${JSON.stringify(entry)}`); + }); +} + +/** + * One replay that is expected to refuse, with whatever it published on the way. + * + * The chunks are the caller's array, so what this reports is what the run + * actually handed the Output Api before it failed — not an empty string this + * helper decided on. A row asserting "published nothing" has to be able to see + * something. + */ +function* refusalOfRun( + source: string, + declarations: readonly MarkdownComponent[], + history: readonly DurableEvent[], +): Operation<{ failure: unknown; output: string }> { + const chunks: string[] = []; + try { + yield* published(source, declarations, [], new InMemoryStream([...history]), chunks); + return { failure: undefined, output: chunks.join("") }; + } catch (error) { + return { failure: error, output: chunks.join("") }; + } +} + describe("Tier DM — exact source is a provenance, not a field", () => { it("DM49: a declared component the host called exact emits its bytes unpresented", function* () { // The positive control. Without it the two refusals below would pass for a @@ -1560,6 +1611,125 @@ describe("Tier DM — exact source is a provenance, not a field", () => { expect(unpresented(output)).toBe(false); expect(output).not.toContain("**these**"); }); + + it("DM54: a spawned child's exact source is still source", function* () { + // `` joins its children by value, so what crosses the join says what + // it is. A child that reduced its region to one string would hand the + // caller prose, and the approved bytes a `` rendered would be + // presented — reflowed, its markers eaten — as if a person had written them. + const inside = yield* published( + [ + "", + "", + "", + "", + "", + "ordinary prose", + "", + "", + "", + ].join("\n"), + [declared(PRESENTABLE, { exact: true })], + [], + ); + + expect(unpresented(inside)).toBe(true); + // And the sibling that rendered prose is still prose: the join does not + // make one child's disposition the other's. + expect(inside).toContain("ordinary prose"); + }); + + it("DM55: a replayed child restores what its bytes were", function* () { + // The disposition is a fact about what canonical expansion produced, and + // the segments it marked do not survive the process. A replay therefore + // reads it from the child's own durable close — and a close that retained + // only text could not answer at all. + const source = [ + "", + "", + "", + "", + "", + "ordinary prose", + "", + "", + "", + ].join("\n"); + const declarations = [declared(PRESENTABLE, { exact: true })]; + const stream = new InMemoryStream(); + const live = yield* published(source, declarations, [], stream); + expect(unpresented(live)).toBe(true); + + // The children's own closes, without the root's: the parent runs again and + // restores each child from what it recorded, which is the state a resumed + // run is actually in. + const history = (yield* stream.readAll()).filter( + (event) => !(event.type === "close" && event.coroutineId === "root"), + ); + const children = history.filter( + (event) => event.type === "close" && event.coroutineId.includes("."), + ); + expect(children).toHaveLength(2); + // What each child retained says what its bytes were, not only what they + // said. Read as a set: children close in whatever order they finished, and + // which one finished first is exactly what this must not depend on. + const runs = children.flatMap((event) => retainedRuns(event)); + expect(runs.filter((run) => run.exact)).toEqual([{ text: PRESENTABLE, exact: true }]); + expect(runs.some((run) => !run.exact && run.text.includes("ordinary prose"))).toBe(true); + + const replayed = yield* published(source, declarations, [], new InMemoryStream(history)); + + expect(replayed).toBe(live); + expect(unpresented(replayed)).toBe(true); + }); + + it("DM56: a child's close this version cannot read fails the run, and publishes nothing", function* () { + const source = [ + "", + "", + "", + "", + "", + "ordinary prose", + "", + "", + "", + ].join("\n"); + const declarations = [declared(PRESENTABLE, { exact: true })]; + const stream = new InMemoryStream(); + yield* published(source, declarations, [], stream); + const complete = (yield* stream.readAll()).filter( + (event) => !(event.type === "close" && event.coroutineId === "root"), + ); + + // Every way a retained close can stop being a list of runs: a value that is + // not a list, a member that is not a run, a member missing one of the two + // things a run is, and a member carrying something else besides. + const damaged: Json[] = [ + "not emission runs", + [{ text: PRESENTABLE }], + [{ text: PRESENTABLE, exact: "yes" }], + [{ text: PRESENTABLE, exact: true, presented: false }], + [PRESENTABLE], + ]; + + for (const value of damaged) { + const history = complete.map( + (event): DurableEvent => + event.type === "close" && event.coroutineId.endsWith(".0") + ? { ...event, result: { status: "ok", value } } + : event, + ); + const attempt = yield* refusalOfRun(source, declarations, history); + const which = JSON.stringify(value); + + // Refused as the stale input it is, and nothing published on the way: a + // document rebuilt without the part it could not read is a document that + // never existed. The chunks are the run's own, collected before it failed. + expect([which, attempt.failure instanceof StaleInputError]).toEqual([which, true]); + expect([which, attempt.output]).toEqual([which, ""]); + } + }); }); /** diff --git a/packages/core/tests/invocation-identity.test.ts b/packages/core/tests/invocation-identity.test.ts index afecba75..1d294ef3 100644 --- a/packages/core/tests/invocation-identity.test.ts +++ b/packages/core/tests/invocation-identity.test.ts @@ -19,9 +19,17 @@ import { describe, it } from "@executablemd/test-support/bdd"; import { expect } from "@executablemd/test-support/expect"; -import { createContext, scoped, useScope } from "effection"; +import { createContext, race, scoped, sleep, spawn, useScope, withResolvers } from "effection"; import type { Context, Operation } from "effection"; -import { collect, Component, content, inlineSource, registerComponents } from "../mod.ts"; +import { + collect, + Component, + content, + importComponent, + inlineSource, + registerComponents, +} from "../mod.ts"; +import { importThroughTerminal, MissingImportProvider } from "../src/component-api.ts"; import { executeInstalled } from "../host.ts"; import type { ExecutionInstallation, IdentityClaimant, IdentityComponent } from "../host.ts"; import { getExpansion } from "../src/expansion.ts"; @@ -102,6 +110,63 @@ function probe(seen: Seen, name = "Probe"): IdentityComponent { }; } +/** + * What another loaded copy's empty public terminal raises. + * + * The mark is copied off a genuine report rather than written out here, because + * the mark is exactly the contract between copies: what makes this one foreign is + * its class, which is this file's own. A real second copy is proved to be one of + * these arrangements by `syntax-loaded-copy.test.ts`, which bundles one; what + * matters here is that the class differs while the mark does not. + */ +class ForeignTerminalReport extends Error { + override name = "MissingImportProvider"; +} + +function foreignTerminalReport(asked: string): Error { + const authentic = new MissingImportProvider(asked); + const foreign = new ForeignTerminalReport(authentic.message); + for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(authentic))) { + if (key !== "stack" && key !== "message") { + Object.defineProperty(foreign, key, descriptor); + } + } + return foreign; +} + +/** + * The same report, with a payload that reads as one member and is not. + * + * The mark's own key is found structurally rather than written out — the payload + * is the object under it carrying `asked` — so this stays a statement about the + * shape a reader has to parse, not a copy of a constant. The extras are hidden + * exactly the way a reader looking at enumerable string keys alone would miss + * them: one non-enumerable member, one symbol. + */ +function hiddenMemberReport(asked: string): Error { + const authentic = new MissingImportProvider(asked); + const report = new ForeignTerminalReport(authentic.message); + for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(authentic))) { + if (key === "stack" || key === "message") { + continue; + } + const marked: unknown = descriptor.value; + const names = + typeof marked === "object" && marked !== null + ? Object.getOwnPropertyDescriptor(marked, "asked") !== undefined + : false; + if (!names) { + Object.defineProperty(report, key, descriptor); + continue; + } + const payload: Record = { asked }; + Object.defineProperty(payload, "hidden", { value: "not enumerable", enumerable: false }); + Object.defineProperty(payload, Symbol("also hidden"), { value: "not a string key" }); + Object.defineProperty(report, key, { ...descriptor, value: payload }); + } + return report; +} + /** One execution of `source`, with `components` declared to it. */ function run( source: string, @@ -158,7 +223,6 @@ function* nothing(): Operation {} * issuances — or one the engine has already ended — are stated here, at the * seam, with the issuances the engine would have minted. */ -// deno-lint-ignore require-yield function* seam(): Operation<{ claim: IdentityClaimant; domain: IdentityDomain }> { let claim: IdentityClaimant | undefined; const installed = installIdentities([ @@ -182,8 +246,11 @@ function* seam(): Operation<{ claim: IdentityClaimant; domain: IdentityDomain }> if (claim === undefined || registration === undefined) { throw new Error("the seam produced no claimant"); } + // The frame is the only thing a selection can be made into: the engine holds + // it and hands it to the terminal it builds for that import, so this seam + // selects through the frame rather than through the execution. const frame = installed.identities.beginImport("Both"); - installed.identities.select("Both", { + frame.select("Both", { kind: "function", name: "Both", props: NO_PROPS, @@ -807,6 +874,470 @@ describe("Tier CIV — the identity a host's component names its work after", () * These prove the fact itself: what it reports for each authored form, that * reading it costs nothing, and that it cannot be recovered from anywhere else. */ +describe("Tier CIV — the import's own terminal is the association", () => { + it("CIV26: a handler that delegates through a descendant scope keeps the identity", function* () { + // Ordinary middleware, delegating the same name once — in a scope of its + // own, which the Api allows and which nothing about an import forbids. The + // terminal `next` reaches is the one this import created, so the invocation + // is still in its execution's domain. + const seen = record(); + yield* run("\n", [probe(seen)], function* () { + yield* Component.around( + { + *importComponent([name, position], next) { + return yield* scoped(() => next(name, position)); + }, + }, + { at: "max" }, + ); + }); + + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(1); + + // And it is the same identity the undelegated import names, because the + // identity is the authored element's and the delegation changed nothing + // about which element asked. + const direct = record(); + yield* run("\n", [probe(direct)]); + expect(seen.taken).toEqual(direct.taken); + }); + + it("CIV26: nested imports shadow and restore, each through its own terminal", function* () { + // The outer probe expands a body that imports the inner one, so two imports + // are open at once in one scope. Each dispatch owns its terminal, so neither + // needs a stack to find its own frame. + const seen = record(); + yield* run("\n\n\n", [probe(seen, "Outer"), probe(seen, "Probe")]); + + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(2); + expect(new Set(seen.taken).size).toBe(2); + }); + + it("CIV26: delegating twice, or delegating another name, names nothing", function* () { + // Each of these reaches this import's terminal — that is not the failure. + // What fails is what the frame then holds: two selections, or one for a + // component the element never wrote. + const ways: readonly ("twice" | "another name")[] = ["twice", "another name"]; + for (const what of ways) { + const seen = record(); + yield* run("\n", [probe(seen), probe(seen, "Other")], function* () { + yield* Component.around( + { + *importComponent([name, position], next) { + // Only the element's own import. The root is imported through the + // same operation, and redirecting that is a different mistake. + if (name !== "Probe") { + return yield* next(name, position); + } + if (what === "twice") { + yield* next(name, position); + return yield* next(name, position); + } + return yield* next("Other", position); + }, + }, + { at: "max" }, + ); + }); + + expect([what, seen.taken]).toEqual([what, []]); + expect([what, seen.refusals]).toHaveLength(2); + } + }); + + it("CIV29: a foreign copy's nested import resolves, and is nobody's delegation", function* () { + // A handler starts an import of its own while the authored import is still + // open, through a descriptor carrying the same stable Api name whose terminal + // belongs to another copy of core — what `--include` and a middleware package + // holding its own copy produce. That terminal's "nothing answered this" + // report is not this copy's class, so an execution recognizing it by class + // would propagate the failure instead of resolving the nested import at all. + const foreign = foreignTerminalReport("Other"); + expect(foreign).not.toBeInstanceOf(MissingImportProvider); + + const seen = record(); + const nested: string[] = []; + yield* run("\n", [probe(seen), probe(seen, "Other")], function* () { + yield* Component.around( + { + *importComponent([name, position], next) { + if (name !== "Probe") { + return yield* next(name, position); + } + const answered = yield* importThroughTerminal( + "Other", + position, + // deno-lint-ignore require-yield + function* (asked: string) { + throw foreignTerminalReport(asked); + }, + ); + nested.push(`${answered.kind}:${answered.name}`); + return yield* next(name, position); + }, + }, + { at: "max" }, + ); + }); + + // The nested import was answered by this execution's ordinary resolution, + // with this execution's own implementation of that component. + expect(nested).toEqual(["function:Other"]); + // And the element that was authored is the one holding an identity: a + // separate import is not a delegation of it, however either one resolved. + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(1); + const direct = record(); + yield* run("\n", [probe(direct)]); + expect(seen.taken).toEqual(direct.taken); + }); + + /** + * Reports that carry the mark and are still not this call's: one about another + * import, and one whose payload only *looks* like the single member the mark + * is. Each names the fragment its failure has to reach the document with. + */ + const NOT_THIS_CALL: ReadonlyArray<{ + what: string; + report: (asked: string) => Error; + says: string; + }> = [ + { + what: "a report about another import", + report: () => foreignTerminalReport("Elsewhere"), + says: "Elsewhere", + }, + { + what: "a mark carrying hidden members", + report: (asked: string) => hiddenMemberReport(asked), + says: "Other", + }, + ]; + + for (const { what, report, says } of NOT_THIS_CALL) { + it(`CIV29: ${what} is somebody else's failure`, function* () { + // The same arrangement, with the foreign terminal raising something that + // carries the mark without being this call's report of it. Ordinary + // resolution answers imports nothing answered, not unrelated failures, so + // these propagate — and the element that asked says so rather than quietly + // resolving something nobody asked for. + const seen = record(); + let failure = ""; + try { + yield* run("\n", [probe(seen), probe(seen, "Other")], function* () { + yield* Component.around( + { + *importComponent([name, position], next) { + if (name !== "Probe") { + return yield* next(name, position); + } + return yield* importThroughTerminal( + "Other", + position, + // deno-lint-ignore require-yield + function* (asked: string) { + throw report(asked); + }, + ); + }, + }, + { at: "max" }, + ); + }); + } catch (error) { + failure = error instanceof Error ? error.message : String(error); + } + + expect([what, failure.includes(says)]).toEqual([what, true]); + expect([what, seen.taken]).toEqual([what, []]); + }); + } +}); + +/** + * How long a signal that a correct engine publishes immediately may go + * unpublished before the wait is called a deadlock. + * + * Never reached by a passing run: every barrier below is opened by the imports + * that arrive at it. An engine that could not hold two imports at once would + * otherwise hang the suite instead of saying what went wrong. + */ +const DEADLOCK_MS = 10_000; + +/** Wait for one signal, reporting a deadlock rather than hanging on one. */ +function* awaiting(what: string, waited: Operation): Operation { + const reached = yield* race([ + (function* (): Operation { + yield* waited; + return true; + })(), + (function* (): Operation { + yield* sleep(DEADLOCK_MS); + return false; + })(), + ]); + if (!reached) { + throw new Error(`${what} never happened: the two imports did not run at the same time`); + } +} + +/** + * Middleware that holds the import of each listed component until every one of + * them is live, and then releases them in `order`. + * + * The rendezvous is inside `importComponent` deliberately. A document holds its + * spawned children at barriers in their component *bodies*, which the import + * has already finished by then — so two such children prove that the bodies + * overlapped, not the imports. Only a wait inside the import itself puts two + * imports in flight together, which is the one arrangement in which an import + * could take a sibling's canonical selection. + * + * A name listed twice holds two separate sites of the same component: each + * arrival takes the next slot that name still has free. + */ +function heldImports(names: readonly string[], order: readonly number[]): () => Operation { + const arrivals = names.map(() => withResolvers()); + const releases = names.map(() => withResolvers()); + const held = names.map(() => false); + return function* () { + yield* spawn(function* () { + for (const [index, name] of names.entries()) { + yield* awaiting(`the import of <${name}> starting`, arrivals[index].operation); + } + for (const index of order) { + releases[index].resolve(); + } + }); + yield* Component.around( + { + *importComponent([name, position], next) { + const index = names.findIndex((listed, at) => listed === name && !held[at]); + if (index < 0) { + return yield* next(name, position); + } + held[index] = true; + arrivals[index].resolve(); + yield* releases[index].operation; + return yield* next(name, position); + }, + }, + { at: "max" }, + ); + }; +} + +/** + * One source file of core's own, read for what it declares. + * + * A boundary is a declaration, so the row about where the execution's import + * resolution may live reads the declarations rather than a runtime object: an + * object says what one execution happened to build, while the interface says what + * every host's installation and every fragment's narrowed table are described + * against. + */ +function sourceOf(file: string): Operation { + return readTextFile(fileURLToPath(new URL(`../${file}`, import.meta.url))); +} + +/** The body of the `ExecutionEnvironment` declaration, as written. */ +function* environmentDeclaration(): Operation { + const source = yield* sourceOf("src/execution-environment.ts"); + const opened = source.indexOf("export interface ExecutionEnvironment {"); + if (opened < 0) { + throw new Error("ExecutionEnvironment is no longer declared where this row reads it"); + } + const closed = source.indexOf("\n}\n", opened); + if (closed < 0) { + throw new Error("the ExecutionEnvironment declaration does not end"); + } + return source.slice(opened, closed); +} + +describe("Tier CIV — where the execution's import resolution lives", () => { + it("CIV30: the environment boundary carries no import terminal", function* () { + const declared = yield* environmentDeclaration(); + // Read as members rather than as text, so this is about what the interface + // offers a holder and not about a word appearing in a comment. + const members = [...declared.matchAll(/^ {2}(?:readonly )?([A-Za-z]+)\??[:(]/gm)].map( + (match) => match[1], + ); + // The row can see the boundary at all: it is the interface with the members + // canonical execution hands expansion, and it still has them. + expect(members).toContain("componentResolution"); + expect(members).toContain("componentIdentity"); + + expect(members).not.toContain("canonicalImport"); + expect(members).not.toContain("resolveComponentImport"); + expect(members).not.toContain("importTerminal"); + // And no member of it accepts an import's own frame or answers with a + // definition, whatever it might be called: that shape *is* an import + // terminal, and naming it something else would not move it off the boundary. + expect(declared).not.toContain("ImportSelection"); + expect(declared).not.toMatch(/=>\s*Operation<\s*\n?\s*ComponentDefinition/); + }); + + it("CIV30: the terminal reaches expansion by hand, and leaves no other way", function* () { + const expansion = yield* sourceOf("src/expand.ts"); + // Declared where it is consumed, and carried as a required parameter beside + // the environment: a recursion that dropped it would not compile, which is + // what the CIV26–CIV29 rows depend on for every nested body, branch and + // spawned child. + expect(expansion).toContain("export type ComponentImportTerminal = ("); + expect(expansion).toContain("imports: ComponentImportTerminal | undefined,"); + // No contextual route to the same value: it is not published under a context + // name and not the core of an Api, so nothing can read it by knowing a name. + expect(expansion).not.toMatch(/createContext<[^>]*ComponentImportTerminal/); + expect(expansion).not.toMatch(/createApi<[^>]*ComponentImportTerminal/); + + // And no package entry point re-exports the type or the identifier, so a + // host, a middleware package and a repository component have no name for it. + for (const entry of ["mod.ts", "api.ts", "host.ts"]) { + const published = yield* sourceOf(entry); + expect([entry, published.includes("ComponentImportTerminal")]).toEqual([entry, false]); + expect([entry, published.includes("resolveComponentImport")]).toEqual([entry, false]); + } + }); +}); + +describe("Tier CIV — two canonical imports live at the same time", () => { + const CONCURRENT = [ + "", + "", + "", + "", + "", + ].join("\n"); + + const ORDERS: Array<[string, readonly number[]]> = [ + ["in arrival order", [0, 1]], + ["in reverse", [1, 0]], + ]; + + for (const [what, order] of ORDERS) { + it(`CIV27: each of two live imports selects into its own frame, released ${what}`, function* () { + // Both spawned sites are inside their import before either resolves, so + // the frames are open at the same time. Each dispatch reaches the + // terminal it was created with, so neither selection can land in the + // sibling's frame — and which import resolves first decides nothing. + const seen = record(); + yield* run(CONCURRENT, [probe(seen)], heldImports(["Probe", "Probe"], order)); + + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(2); + expect(new Set(seen.taken).size).toBe(2); + + // And they are the identities the same two sites name when nothing holds + // their imports at all: overlapping changed the scheduling, not the + // authored element either invocation belongs to. + const direct = record(); + yield* run(CONCURRENT, [probe(direct)]); + expect(new Set(seen.taken)).toEqual(new Set(direct.taken)); + }); + } + + it("CIV28: cancelling one live import closes only its own frame", function* () { + // Three spawned children: one import is released and claims, a second is + // still suspended inside its import when the third fails and `` halts + // it. The cancelled branch settles its own frame in `finally`, and there is + // no execution-wide association for it to take the survivor's with it. + const FIXTURE = [ + "", + "", + "", + "", + "", + "", + ].join("\n"); + + /** A probe that says when it has claimed, so the failure lands after it. */ + function claiming(seen: Seen, done: { resolve(value: void): void }): IdentityComponent { + return { + name: "Probe", + origin: "test://probe", + props: NO_PROPS, + factory: (claim: IdentityClaimant) => + function* Probe( + _props: Record, + invocation: ComponentInvocation, + ): Operation { + try { + seen.taken.push(yield* claim(invocation)); + } catch (error) { + seen.refusals.push(error instanceof Error ? error.message : String(error)); + } + done.resolve(); + return ""; + }, + }; + } + + /** The sibling that decides the run's outcome once the survivor claimed. */ + function tripwire(waited: Operation, outcome: "fails" | "finishes"): IdentityComponent { + return { + name: "Boom", + origin: "test://boom", + props: NO_PROPS, + factory: () => + function* Boom(): Operation { + yield* awaiting("the surviving child claiming", waited); + if (outcome === "fails") { + throw new Error("BOOM"); + } + return ""; + }, + }; + } + + const survived = record(); + const cancelled = record(); + const claimed = withResolvers(); + let failure = ""; + try { + yield* run( + FIXTURE, + [ + claiming(survived, claimed), + probe(cancelled, "Other"), + tripwire(claimed.operation, "fails"), + ], + // `` is listed but never released: its import is live when the + // document fails under it. + heldImports(["Probe", "Other"], [0]), + ); + } catch (error) { + failure = error instanceof Error ? error.message : String(error); + } + + expect(failure).toContain("BOOM"); + // The survivor claimed once and was refused nothing; the branch cancelled + // inside its import claimed nothing at all. + expect(survived.refusals).toEqual([]); + expect(survived.taken).toHaveLength(1); + expect(cancelled.taken).toEqual([]); + + // The same document, with the third child finishing instead of failing: + // both siblings claim, and the survivor names exactly what it named while + // its sibling was being torn down beside it. Nothing outlived that + // teardown to lend or withhold an identity here. + const quiet = record(); + const other = record(); + const finished = withResolvers(); + yield* run( + FIXTURE, + [claiming(quiet, finished), probe(other, "Other"), tripwire(finished.operation, "finishes")], + heldImports(["Probe", "Other"], [0, 1]), + ); + + expect(quiet.refusals).toEqual([]); + expect(other.refusals).toEqual([]); + expect(quiet.taken).toEqual(survived.taken); + expect(other.taken).toHaveLength(1); + expect(new Set([...quiet.taken, ...other.taken]).size).toBe(2); + }); +}); + describe("Tier CIV — the authored form on the invocation", () => { const FORMS: Array<[string, string, boolean]> = [ ["self-closing", "\n", false], @@ -1029,3 +1560,60 @@ describe("Tier CIV — the authored form on the invocation", () => { expect(refusals.at(-1)).toEqual("refused:paired"); }); }); + +/** + * Tier PA10b — one declared component, two sibling spawns at once. + * + * `` gives two `` children imports in flight together. What puts an + * invocation in this execution's identity domain is the canonical selection + * made inside the frame its own import opened — so these rows are the general + * statement of the thing an ordinary `` in each spawn needs. + * + * They take the spawns as the engine schedules them. The rows that hold two + * imports open *together*, by suspending inside the import itself, are CIV27 + * and CIV28 below. + */ +describe("Tier PA10b — concurrent capability-backed identity", () => { + it("PA10b: two concurrent sites each claim their own identity, exactly once", function* () { + const seen = record(); + yield* run( + ["", "", "", ""].join("\n"), + [probe(seen)], + nothing, + ); + + // Both claimed, neither refused, and neither received the other's. + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(2); + expect(new Set(seen.taken).size).toBe(2); + // The engine's own identity for each invocation, which is what + // `getExpansion()` reports when nothing has interfered. + expect(seen.taken).toEqual(seen.context); + }); + + it("PA10b: a nested inside a spawn claims its own identities too", function* () { + const seen = record(); + yield* run( + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + ].join("\n"), + [probe(seen)], + nothing, + ); + + // Three sites, three identities, no refusal: a spawn nested inside a + // spawn owns its own frames as much as a top-level one does. + expect(seen.refusals).toEqual([]); + expect(seen.taken).toHaveLength(3); + expect(new Set(seen.taken).size).toBe(3); + expect(seen.taken).toEqual(seen.context); + }); +}); diff --git a/packages/core/tests/syntax-catalog.test.ts b/packages/core/tests/syntax-catalog.test.ts index ac42c99a..007ae215 100644 --- a/packages/core/tests/syntax-catalog.test.ts +++ b/packages/core/tests/syntax-catalog.test.ts @@ -377,6 +377,7 @@ describe("Tier SY: structural vocabulary", () => { // The reserved vocabulary in full, so a construct returning to the table // has to be written down here before this row can pass again. expect([...names(structural(catalog))].sort()).toEqual([ + "All", "Answer", "Answers", "Break", @@ -390,6 +391,7 @@ describe("Tier SY: structural vocabulary", () => { "Output", "PrintErrors", "Return", + "Spawn", "Switch", ]); for (const name of ["Terminal.Grid", "Terminal"]) { diff --git a/specs/executable-mdx-spec.md b/specs/executable-mdx-spec.md index df5e786b..6b7327e8 100644 --- a/specs/executable-mdx-spec.md +++ b/specs/executable-mdx-spec.md @@ -5496,6 +5496,38 @@ registration canonical resolution selected — recognized there by the identity the implementation this execution built, carried to the issuance through the engine's own import frame (§5.3), and settled to nothing when resolution did not happen, happened twice, or answered for a name the engine did not ask. + +An import frame belongs to the import that opened it, and the association is +that import's own **terminal**: expansion asks through a `Component` Api +descriptor it creates for this one import, whose core `importComponent` handler +closes over the frame. The descriptor carries the stable `Component` Api name, so +it receives the same installed middleware in the same order — a stable name +shares middleware and shares no terminal authority. Middleware may observe, +delegate, replace or refuse, and may run `next` in a scope of its own: `next` +terminates in the terminal that dispatch was created with, so the invocation +keeps its identity. A handler that calls the public import operation itself is +making a separate import rather than delegating the authored one, and that +separate import grants the authored one no identity. Nothing about an Effection +scope, a context, a token or a durable coroutine takes part. + +What this ownership adds is spawned concurrency. Two `` children under one +`` may have imports open at the same time; each has its own frame behind its +own terminal, so an ordinary `` written in each child receives its own +authentic identity, neither child can be handed the other's, and closing or +cancelling one spawn neither clears nor authorizes the other. Nesting needs no +stack, because an inner authored import is its own dispatch with its own +terminal. Two selections in one frame still settle to nothing. The engine holds +one domain per declared component per execution however many spawns invoke it. + +The windows a provider's claim is admitted in are owned the same way and by the +same kind of value — the explicit window object, captured by the request before +its handler runs — and serially: component answers are resolved in one capture +phase before any document expands, so one window is open at a time and a handler +delegating through a descendant scope answers the resolution it was opened in. +One installation may answer more than one import with the exact same definition, +while a second, *different* answer in either refuses. A concurrent document +import happens after capture, with no window open, so it may be answered as +ordinary middleware and acquires no profile identity. Forwarding the genuine issuance is ordinary delegation and stays supported; everything else refuses. None of it reads replaceable state, so middleware may short-circuit an import, redirect a name, replace a definition, shadow a @@ -6743,6 +6775,91 @@ a rejected element also ending the loop. Printed errors from `` and `` carry source locations on the same terms as ``. +#### `` and ``: running independent work at the same time + +`` runs its direct `` children at the same time and waits for every +one of them. Both names are the engine's own syntax: neither resolves a +component, so a repository file, a registration or a Plugin cannot supply +either. + +```markdown + + + +Draft the implementation plan. + + + + +Review the current change. + + + +``` + +Both children can be live together, and `` renders their markdown in the +order the spawns were written even when the second one finishes first. + +**Form.** Both constructs are written paired and accept no props. `` +requires at least two direct `` children; whitespace between them is +insignificant, and any other direct content — text, a component, a code block — +is refused. A `` written anywhere but directly inside an `` is +refused too. Every one of these is read from source, so a malformed construct +refuses before any child starts: a tripwire written inside either child never +runs. + +**Bindings and resources.** Every child starts from the bindings that reached +the ``, in an environment of its own, with its own eval scope. A binding it +makes, a binding it rebinds and a resource it retains stay available to that +child's own later work, and reach neither a sibling nor the work after +``. Children do not communicate through bindings, and `` takes no +`as` and produces no value. A retained resource is released when its child ends. + +**Output.** Each child expands into a private buffer. After every child +succeeds, `` emits their renderings in authored order; before that it emits +none of them. Scheduling may decide when a child's durable records append, and +never what the document renders. + +**Durable identity and replay.** In source order, each `` receives one +child coroutine of the coroutine that reached the ``, allocated before any +child starts; nesting produces the existing hierarchical identity (§5.6, and the +protocol's §7). Each child also gets a fresh block counter and an expansion path +extended by its authored ordinal, so two children may derive the same local block +id without colliding — the child coroutine namespaces it — and the work after +`` continues on the parent counter it would have had anyway. Each +successful child closes with the ordered runs it rendered: consecutive segments +of one disposition, each `{ text, exact }`, where `exact` says whether those +bytes are a program's approved source rather than prose (§5.3). The segments a +child marked never leave it, so the run is where that provenance crosses the +join — a close holding only text could not say what its bytes were, and a +replayed child's source would be published as prose. A complete replay runs no +spawned work and rebuilds the output from those closes; a partial replay +restores the children that closed and resumes only the unrecorded ones, under +the same identities. A retained close that does not read as that exact shape — +not a list, a member that is not a run, a run missing or exceeding its two +members — fails the run rather than contributing part of a document. Inserting, removing or reordering spawns is a definition +change and follows the ordinary divergence contract. + +**Failure and cancellation.** The join is fail-fast. The first child failure +fails the ``, cancels every unfinished sibling and waits for all of them to +finish tearing down. No child output is emitted, durable records acknowledged +before the failure stay durable, and interrupted work is never recorded as +complete. Cancelling the document cancels and joins every child; no task, +subscription, retained resource or output buffer survives its spawn. + +**Control flow.** `` is a boundary. A `` inside one cannot select +a value owned outside the ``, and a `` inside one cannot exit a +`` written outside it; both are refused from source, before anything runs. +A component invoked inside the child owns its own ``, and a `` +written wholly inside the child is ended by its own ``, exactly as +elsewhere. A `` below a `` neither satisfies nor violates the +enclosing body's own `returns` declaration: the body still owes the `` +it declared. + +Generated XMD receives the same syntax and the same rules. The whole-fragment +preflight walks every spawn — each from the bindings that reach the ``, and +merging nothing back — before the fragment performs its first effect. + ### 6.6 Eval binding interpolation Bare `{name}` references (no namespace prefix) resolve against @@ -13161,6 +13278,9 @@ Defined in §5.6, with the selection rule in §5.3. | CIV23 | The identity a provider states for its answer | A request states a claim on the exact object its handler returns, under the name and provider origin canonical execution fixed when it minted that request. A different object, a copy, an object edited after the claim, a competing provider installation, another key and another revision each identify nothing, and the first statement stands after every one of them. The provider states only key and revision, and a partial identity is refused rather than half-recorded. Backs FE14 | | CIV24 | Identification is one atomic answer | The claim and core's own claim-time copy of what was claimed come back from one call, so nothing downstream reads the chain's object again: an answer whose member alternates between the claimed value and a substitution is sealed as the claimed one, with the plant proven live by the next read. At execution scale, an answer whose props schema alternates validates the fragment against the claimed contract. Backs FE14 | | CIV25 | A request belongs to one resolution occurrence | Provider installation and the right to answer one occurrence are separate. An installation receives a registrar and installs import middleware once; every middleware invocation receives a fresh request with readonly name and position, plus `next`. The request captures the exact resolution-window object, provider-installation token, origin and name, and `claim` accepts only the answer plus key and revision. Its handler closes the request synchronously in `finally` on return, failure or cancellation, while an outer request stays live across `yield* next()` and may claim its replacement after the delegated handler returns. The enclosing resolution closes in its existing `finally`. A claim succeeds only during active execution, through an open request whose captured window is the exact current object and whose fixed name matches it, with no different statement by that provider in the window. Identification takes the expected window explicitly and accepts only that exact window and name. Thus a stale request from resolution N refuses during N+1 of the same name, a stale `Open` request cannot retag while `Other` is live, and a losing inner request refuses after its handler returns while the still-live outer request may claim after delegation. One installation answers several names and repeated same-name resolutions through distinct requests; unidentified replacements remain ordinary valid middleware answers outside fragment evaluation. Two owners live at once each answer only for what they recorded, and tearing one down leaves the other working. No Context, shared symbol, public brand or module-global registry participates. Backs FE15 | +| CIV26 | The terminal is the association | Import middleware that delegates the same name once from inside `scoped()` keeps the invocation's identity, and names the same one an undelegated import does. Two nested authored imports each own their terminal, so both are identified without a stack. Delegating twice, or delegating another name, reaches this import's own terminal and settles it to nothing | +| CIV27 | Two imports live at once | Two `` children are each suspended inside their own import before either resolves. Each dispatch selects into the frame its terminal carries, so both invocations are identified exactly once, their identities differ, and they are the identities the same two sites name when nothing holds their imports — in either release order | +| CIV28 | A cancelled import closes only its own frame | With two imports live, one is released and claims while the other is still suspended and is cancelled by a failing sibling. The survivor is refused nothing and names exactly what that site names when nothing is torn down beside it; the cancelled branch claims nothing, and nothing it left behind lends or withholds an identity afterwards | ### Tier NEX — Nested document executions (`specs/testing-spec.md`) @@ -13216,6 +13336,10 @@ what they observe is what a person's terminal would show. | DM50 | An ordinary declaration is prose | The same bytes from a declaration the host did not call exact are stripped, collapsed and formatted | | DM51 | A middleware answer cannot claim it | `Component.importComponent` middleware answering an open name with a definition carrying the disposition gets prose; nothing admitted that definition, so nothing about it is exact | | DM52 | A mark this engine did not make is nothing | A segment carrying the disposition as a field is not exact, whoever supplied it; expansion also rebuilds text segments, so such a field never reaches emission in the first place | +| DM53 | The record is not reachable by name | A component that builds a context with the record's name and writes to it publishes prose | +| DM54 | A spawned child's source is still source | A declared exact component rendered inside ``/`` keeps its bytes unpresented, and a prose sibling stays prose | +| DM55 | A replayed child restores what its bytes were | Each child's retained close holds its ordered `{ text, exact }` runs, and replaying that history reproduces the live output with the same provenance | +| DM56 | A close that will not read fails the run | A retained child close that is not a list of runs, or holds a member that is not one, refuses atomically and publishes nothing | ### Tier MDK — Declaring exact Markdown with `Markdown({…})` (§5.3) diff --git a/test-weights.json b/test-weights.json index cd8f17f3..1874ae49 100644 --- a/test-weights.json +++ b/test-weights.json @@ -1,8 +1,8 @@ { "version": 1, "source": { - "commit": "a403cc57170514db95722ce7926aec5515f92453", - "runUrl": "https://github.com/taras/executable.md/actions/runs/36417683621", + "commit": "1d317942eb24810816c172998c083033f5d1165b", + "runUrl": "https://github.com/taras/executable.md/actions/runs/36577302779", "attempt": 1, "runner": "ubuntu-latest", "deno": "deno 2.9.5 (stable, release, x86_64-unknown-linux-gnu)", @@ -11,1094 +11,1097 @@ }, "runtimes": { "deno": { - "packages/acp/tests/acceptance.test.ts": 4890, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 7990, - "packages/acp/tests/acpx-materialization.test.ts": 8093, - "packages/acp/tests/acpx-transient-env.test.ts": 1812, - "packages/acp/tests/adapter-materialization.test.ts": 77274, - "packages/acp/tests/adapter-protocol.test.ts": 59906, - "packages/acp/tests/codex-surfaces.test.ts": 1784, - "packages/acp/tests/loaded-copy.test.ts": 4628, - "packages/acp/tests/native-launch.test.ts": 5345, - "packages/acp/tests/permission-bridge.test.ts": 4076, - "packages/acp/tests/provider.test.ts": 5260, - "packages/acp/tests/serial-queue.test.ts": 1533, - "packages/acp/tests/session-key.test.ts": 3980, - "packages/acp/tests/session-route.test.ts": 4132, - "packages/acp/tests/terminal-screen.test.ts": 1731, - "packages/cli/tests/agent-adapters.test.ts": 5722, - "packages/cli/tests/agent-cli.test.ts": 36964, - "packages/cli/tests/agent-config.test.ts": 3639, - "packages/cli/tests/agent-options-cli.test.ts": 15109, - "packages/cli/tests/agent-options.test.ts": 3659, - "packages/cli/tests/agent-session-coordinator.test.ts": 4348, - "packages/cli/tests/cli-help.test.ts": 20236, - "packages/cli/tests/command.test.ts": 10301, - "packages/cli/tests/compiled-upgrade.test.ts": 10637, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 9094, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 120013, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 8572, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 12296, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 10476, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 15699, - "packages/cli/tests/evaluate-component.test.ts": 11856, - "packages/cli/tests/evaluate-workflow.test.ts": 5562, - "packages/cli/tests/fetch-cli.test.ts": 3864, - "packages/cli/tests/file-stream.test.ts": 1811, - "packages/cli/tests/github-zero-effect.test.ts": 7260, - "packages/cli/tests/inline-cli.test.ts": 33591, - "packages/cli/tests/launch-timeout.test.ts": 11660, - "packages/cli/tests/packaged-document.test.ts": 4899, - "packages/cli/tests/plan-args.test.ts": 1597, - "packages/cli/tests/plan-cli.test.ts": 97616, - "packages/cli/tests/plan-command-document.test.ts": 9852, - "packages/cli/tests/plan-component.test.ts": 12024, - "packages/cli/tests/plan-host-acts.test.ts": 5483, - "packages/cli/tests/plan.test.ts": 16125, - "packages/cli/tests/plugin-cli.test.ts": 40561, - "packages/cli/tests/plugin-host.test.ts": 6162, - "packages/cli/tests/plugin-modules.test.ts": 1540, - "packages/cli/tests/plugin-selection.test.ts": 1528, - "packages/cli/tests/process-retention.test.ts": 5307, - "packages/cli/tests/props-cli.test.ts": 30410, - "packages/cli/tests/props-schema.test.ts": 4087, - "packages/cli/tests/props-sources.test.ts": 2293, - "packages/cli/tests/pull-request-read-fork.test.ts": 7148, - "packages/cli/tests/repl-boundaries.test.ts": 7654, - "packages/cli/tests/repl-composition.test.ts": 2084, - "packages/cli/tests/repl-execution.test.ts": 5446, - "packages/cli/tests/repl-journey.test.ts": 20562, - "packages/cli/tests/repl-model.test.ts": 5271, - "packages/cli/tests/repl-route.test.ts": 4830, - "packages/cli/tests/repl-terminal.test.ts": 2940, - "packages/cli/tests/run-composition-deno.test.ts": 7865, - "packages/cli/tests/run-composition-nested.test.ts": 20765, - "packages/cli/tests/run-composition.test.ts": 7086, - "packages/cli/tests/run-deadline.test.ts": 8852, - "packages/cli/tests/run-profile.test.ts": 4456, - "packages/cli/tests/run-timeouts.test.ts": 13293, - "packages/cli/tests/secret-detection-cli.test.ts": 19637, - "packages/cli/tests/service-document.test.ts": 5349, - "packages/cli/tests/service-host.test.ts": 5227, - "packages/cli/tests/session-launch-cli.test.ts": 10605, - "packages/cli/tests/stdin-cli.test.ts": 37206, - "packages/cli/tests/stdout-delivery.test.ts": 1542, - "packages/cli/tests/syntax-cli.test.ts": 36546, - "packages/cli/tests/targets-cli.test.ts": 28883, - "packages/cli/tests/test-root-profile.test.ts": 7230, - "packages/cli/tests/test-target.test.ts": 37366, - "packages/cli/tests/testing-activation.test.ts": 4596, - "packages/cli/tests/testing-execution-host.test.ts": 56559, - "packages/cli/tests/upgrade-cli.test.ts": 21067, - "packages/cli/tests/upgrade-command-document.test.ts": 7332, - "packages/cli/tests/upgrade-output.test.ts": 4599, - "packages/cli/tests/value-root.test.ts": 17482, - "packages/cli/tests/verbose-component.test.ts": 5046, - "packages/cli/tests/workflow-agent-sessions.test.ts": 19205, - "packages/cli/tests/workflow-agent.test.ts": 15879, - "packages/cli/tests/workflow-cli.test.ts": 108223, - "packages/cli/tests/workflow-crash.test.ts": 81045, - "packages/cli/tests/workflow-declaration.test.ts": 4494, - "packages/cli/tests/workflow-export-publication.test.ts": 17358, - "packages/cli/tests/workflow-fetch.test.ts": 8151, - "packages/cli/tests/workflow-fork.test.ts": 85702, - "packages/cli/tests/workflow-host.test.ts": 14581, - "packages/cli/tests/workflow-inspection.test.ts": 116193, - "packages/cli/tests/workflow-installation.test.ts": 6320, - "packages/cli/tests/workflow-lifecycle-control.test.ts": 20687, - "packages/cli/tests/workflow-retention.test.ts": 22913, - "packages/cli/tests/workflow-suspension.test.ts": 7981, - "packages/code-review-agent/tests/doctor.test.ts": 1540, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1597, - "packages/code-review-agent/tests/parse-diff.test.ts": 1571, - "packages/code-review-agent/tests/parse-doctor.test.ts": 4103, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 1566, - "packages/code-review-agent/tests/policy.test.ts": 1556, - "packages/core/tests/agent-api.test.ts": 1576, - "packages/core/tests/agent-components.test.ts": 4731, - "packages/core/tests/agent-function-components.test.ts": 6430, - "packages/core/tests/agent-session-launch.test.ts": 6743, - "packages/core/tests/agent-session-placement.test.ts": 1607, - "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1718, - "packages/core/tests/agent-session-use.test.ts": 1588, - "packages/core/tests/agent-stack.test.ts": 4270, - "packages/core/tests/answer-identity.test.ts": 3719, - "packages/core/tests/answers-component.test.ts": 4881, - "packages/core/tests/answers-expansion-recursion.test.ts": 3883, - "packages/core/tests/capture-errors.test.ts": 4117, - "packages/core/tests/capture-props.test.ts": 3922, - "packages/core/tests/cli-journal.test.ts": 12311, - "packages/core/tests/code-block-component.test.ts": 5440, - "packages/core/tests/compiler-boundary.test.ts": 3925, - "packages/core/tests/component-api.test.ts": 3668, - "packages/core/tests/component-registration.test.ts": 4720, - "packages/core/tests/component-returns.test.ts": 5150, - "packages/core/tests/component-schema-conformance.test.ts": 3996, - "packages/core/tests/config-api.test.ts": 4074, - "packages/core/tests/construct-error-observation.test.ts": 4166, - "packages/core/tests/contextual-cwd.test.ts": 5197, - "packages/core/tests/daemon-integration.test.ts": 4655, - "packages/core/tests/daemon.test.ts": 3729, - "packages/core/tests/declared-markdown-component.test.ts": 5441, - "packages/core/tests/document-output-api.test.ts": 1545, - "packages/core/tests/document-target-execution.test.ts": 6773, - "packages/core/tests/document-targets.test.ts": 4046, - "packages/core/tests/document-validation.test.ts": 5420, - "packages/core/tests/documentation-index.test.ts": 3727, - "packages/core/tests/each.test.ts": 4043, - "packages/core/tests/elicit-component.test.ts": 4583, - "packages/core/tests/ephemeral-service.test.ts": 4478, - "packages/core/tests/eval-bindings.test.ts": 3943, - "packages/core/tests/eval-context.test.ts": 2191, - "packages/core/tests/eval-durable.test.ts": 3995, - "packages/core/tests/eval-error-mode.test.ts": 4245, - "packages/core/tests/eval-interpolate.test.ts": 1534, - "packages/core/tests/eval-middleware.test.ts": 3662, - "packages/core/tests/eval-persist.test.ts": 4113, - "packages/core/tests/eval-return.test.ts": 4024, - "packages/core/tests/eval-scope.test.ts": 3917, - "packages/core/tests/eval-timeout.test.ts": 4025, - "packages/core/tests/eval-transform.test.ts": 3582, - "packages/core/tests/evaluate-component.test.ts": 5882, - "packages/core/tests/evaluate-loaded-copy.test.ts": 4011, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 4134, - "packages/core/tests/evaluation-profile.test.ts": 3805, - "packages/core/tests/exec-timeout.test.ts": 4106, - "packages/core/tests/execute.test.ts": 4636, - "packages/core/tests/execution-protocol.test.ts": 5358, - "packages/core/tests/expand.test.ts": 4719, - "packages/core/tests/expansion-identity.test.ts": 4585, - "packages/core/tests/expansion-metadata.test.ts": 3837, - "packages/core/tests/expression-props.test.ts": 4475, - "packages/core/tests/fail-component.test.ts": 4210, - "packages/core/tests/failure-printing.test.ts": 4239, - "packages/core/tests/fatal-cause.test.ts": 3750, - "packages/core/tests/fetch-component.test.ts": 5335, - "packages/core/tests/file-component.test.ts": 5869, - "packages/core/tests/file-delete-component.test.ts": 4128, - "packages/core/tests/files-fatal.test.ts": 4916, - "packages/core/tests/foreground-exec.test.ts": 5059, - "packages/core/tests/frontmatter.test.ts": 3629, - "packages/core/tests/function-components.test.ts": 4481, - "packages/core/tests/generated-composition.test.ts": 4481, - "packages/core/tests/generated-xmd.test.ts": 5791, - "packages/core/tests/glob-api.test.ts": 1898, - "packages/core/tests/glob-component.test.ts": 4625, - "packages/core/tests/guarded-journal.test.ts": 3945, - "packages/core/tests/has-content.test.ts": 3812, - "packages/core/tests/heal.test.ts": 3799, - "packages/core/tests/if.test.ts": 4449, - "packages/core/tests/inline-root.test.ts": 3986, - "packages/core/tests/invocation-failures.test.ts": 3705, - "packages/core/tests/invocation-identity.test.ts": 4403, - "packages/core/tests/invocation-scope.test.ts": 4078, - "packages/core/tests/journal-source-position.test.ts": 4195, - "packages/core/tests/json-component.test.ts": 4217, - "packages/core/tests/json.test.ts": 3606, - "packages/core/tests/let.test.ts": 4016, - "packages/core/tests/loaded-copy-files.test.ts": 3900, - "packages/core/tests/loop.test.ts": 4825, - "packages/core/tests/named-slots.test.ts": 4729, + "packages/acp/tests/acceptance.test.ts": 5052, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 8265, + "packages/acp/tests/acpx-materialization.test.ts": 8084, + "packages/acp/tests/acpx-transient-env.test.ts": 1772, + "packages/acp/tests/adapter-materialization.test.ts": 76537, + "packages/acp/tests/adapter-protocol.test.ts": 60468, + "packages/acp/tests/codex-surfaces.test.ts": 1819, + "packages/acp/tests/loaded-copy.test.ts": 4625, + "packages/acp/tests/native-launch.test.ts": 5333, + "packages/acp/tests/permission-bridge.test.ts": 4193, + "packages/acp/tests/provider.test.ts": 5345, + "packages/acp/tests/serial-queue.test.ts": 1527, + "packages/acp/tests/session-key.test.ts": 4017, + "packages/acp/tests/session-route.test.ts": 4255, + "packages/acp/tests/terminal-screen.test.ts": 1693, + "packages/cli/tests/agent-adapters.test.ts": 5698, + "packages/cli/tests/agent-cli.test.ts": 37667, + "packages/cli/tests/agent-config.test.ts": 3713, + "packages/cli/tests/agent-options-cli.test.ts": 15490, + "packages/cli/tests/agent-options.test.ts": 3668, + "packages/cli/tests/agent-session-coordinator.test.ts": 4387, + "packages/cli/tests/cli-help.test.ts": 20250, + "packages/cli/tests/command.test.ts": 10303, + "packages/cli/tests/compiled-upgrade.test.ts": 10775, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 8964, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 119858, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 8472, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 12290, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 10519, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 15587, + "packages/cli/tests/evaluate-component.test.ts": 11840, + "packages/cli/tests/evaluate-workflow.test.ts": 5549, + "packages/cli/tests/fetch-cli.test.ts": 3840, + "packages/cli/tests/file-stream.test.ts": 1813, + "packages/cli/tests/github-zero-effect.test.ts": 7355, + "packages/cli/tests/inline-cli.test.ts": 33157, + "packages/cli/tests/launch-timeout.test.ts": 11665, + "packages/cli/tests/packaged-document.test.ts": 5100, + "packages/cli/tests/plan-args.test.ts": 1604, + "packages/cli/tests/plan-cli.test.ts": 99258, + "packages/cli/tests/plan-command-document.test.ts": 10015, + "packages/cli/tests/plan-component.test.ts": 12327, + "packages/cli/tests/plan-host-acts.test.ts": 5534, + "packages/cli/tests/plan.test.ts": 16548, + "packages/cli/tests/plugin-cli.test.ts": 41189, + "packages/cli/tests/plugin-host.test.ts": 6252, + "packages/cli/tests/plugin-modules.test.ts": 1573, + "packages/cli/tests/plugin-selection.test.ts": 1563, + "packages/cli/tests/process-retention.test.ts": 5369, + "packages/cli/tests/props-cli.test.ts": 30488, + "packages/cli/tests/props-schema.test.ts": 4164, + "packages/cli/tests/props-sources.test.ts": 2281, + "packages/cli/tests/pull-request-read-fork.test.ts": 7124, + "packages/cli/tests/repl-boundaries.test.ts": 7465, + "packages/cli/tests/repl-composition.test.ts": 2041, + "packages/cli/tests/repl-execution.test.ts": 5465, + "packages/cli/tests/repl-journey.test.ts": 20626, + "packages/cli/tests/repl-model.test.ts": 5262, + "packages/cli/tests/repl-route.test.ts": 4795, + "packages/cli/tests/repl-terminal.test.ts": 2918, + "packages/cli/tests/run-composition-deno.test.ts": 8040, + "packages/cli/tests/run-composition-nested.test.ts": 21027, + "packages/cli/tests/run-composition.test.ts": 7115, + "packages/cli/tests/run-deadline.test.ts": 8779, + "packages/cli/tests/run-profile.test.ts": 4429, + "packages/cli/tests/run-timeouts.test.ts": 13312, + "packages/cli/tests/secret-detection-cli.test.ts": 19647, + "packages/cli/tests/service-document.test.ts": 5296, + "packages/cli/tests/service-host.test.ts": 5176, + "packages/cli/tests/session-launch-cli.test.ts": 10798, + "packages/cli/tests/stdin-cli.test.ts": 37368, + "packages/cli/tests/stdout-delivery.test.ts": 1550, + "packages/cli/tests/syntax-cli.test.ts": 36390, + "packages/cli/tests/targets-cli.test.ts": 28754, + "packages/cli/tests/test-root-profile.test.ts": 7297, + "packages/cli/tests/test-target.test.ts": 37254, + "packages/cli/tests/testing-activation.test.ts": 4614, + "packages/cli/tests/testing-execution-host.test.ts": 57092, + "packages/cli/tests/upgrade-cli.test.ts": 21098, + "packages/cli/tests/upgrade-command-document.test.ts": 7460, + "packages/cli/tests/upgrade-output.test.ts": 4847, + "packages/cli/tests/value-root.test.ts": 17921, + "packages/cli/tests/verbose-component.test.ts": 5102, + "packages/cli/tests/workflow-agent-sessions.test.ts": 20280, + "packages/cli/tests/workflow-agent.test.ts": 16266, + "packages/cli/tests/workflow-cli.test.ts": 109626, + "packages/cli/tests/workflow-crash.test.ts": 81308, + "packages/cli/tests/workflow-declaration.test.ts": 4615, + "packages/cli/tests/workflow-export-publication.test.ts": 17473, + "packages/cli/tests/workflow-fetch.test.ts": 8201, + "packages/cli/tests/workflow-fork.test.ts": 85080, + "packages/cli/tests/workflow-host.test.ts": 14668, + "packages/cli/tests/workflow-inspection.test.ts": 115910, + "packages/cli/tests/workflow-installation.test.ts": 6162, + "packages/cli/tests/workflow-lifecycle-control.test.ts": 20775, + "packages/cli/tests/workflow-retention.test.ts": 23092, + "packages/cli/tests/workflow-suspension.test.ts": 8136, + "packages/code-review-agent/tests/doctor.test.ts": 1533, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 1611, + "packages/code-review-agent/tests/parse-diff.test.ts": 1601, + "packages/code-review-agent/tests/parse-doctor.test.ts": 4012, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 1540, + "packages/code-review-agent/tests/policy.test.ts": 1551, + "packages/core/tests/agent-api.test.ts": 1567, + "packages/core/tests/agent-components.test.ts": 4627, + "packages/core/tests/agent-function-components.test.ts": 6345, + "packages/core/tests/agent-session-launch.test.ts": 6716, + "packages/core/tests/agent-session-placement.test.ts": 1612, + "packages/core/tests/agent-session-use-loaded-copy.test.ts": 1674, + "packages/core/tests/agent-session-use.test.ts": 1583, + "packages/core/tests/agent-stack.test.ts": 4198, + "packages/core/tests/all.test.ts": 5454, + "packages/core/tests/answer-identity.test.ts": 3695, + "packages/core/tests/answers-component.test.ts": 4788, + "packages/core/tests/answers-expansion-recursion.test.ts": 3892, + "packages/core/tests/capture-errors.test.ts": 4191, + "packages/core/tests/capture-props.test.ts": 3892, + "packages/core/tests/cli-journal.test.ts": 12094, + "packages/core/tests/code-block-component.test.ts": 5375, + "packages/core/tests/compiler-boundary.test.ts": 3850, + "packages/core/tests/component-api.test.ts": 3541, + "packages/core/tests/component-registration.test.ts": 4641, + "packages/core/tests/component-returns.test.ts": 5098, + "packages/core/tests/component-schema-conformance.test.ts": 3949, + "packages/core/tests/config-api.test.ts": 4047, + "packages/core/tests/construct-error-observation.test.ts": 4317, + "packages/core/tests/contextual-cwd.test.ts": 5151, + "packages/core/tests/daemon-integration.test.ts": 4696, + "packages/core/tests/daemon.test.ts": 3692, + "packages/core/tests/declared-markdown-component.test.ts": 5313, + "packages/core/tests/document-output-api.test.ts": 1535, + "packages/core/tests/document-target-execution.test.ts": 6755, + "packages/core/tests/document-targets.test.ts": 4030, + "packages/core/tests/document-validation.test.ts": 5348, + "packages/core/tests/documentation-index.test.ts": 3766, + "packages/core/tests/each.test.ts": 4056, + "packages/core/tests/elicit-component.test.ts": 4571, + "packages/core/tests/ephemeral-service.test.ts": 4359, + "packages/core/tests/eval-bindings.test.ts": 3914, + "packages/core/tests/eval-context.test.ts": 2152, + "packages/core/tests/eval-durable.test.ts": 3942, + "packages/core/tests/eval-error-mode.test.ts": 4324, + "packages/core/tests/eval-interpolate.test.ts": 1501, + "packages/core/tests/eval-middleware.test.ts": 3588, + "packages/core/tests/eval-persist.test.ts": 4073, + "packages/core/tests/eval-return.test.ts": 3935, + "packages/core/tests/eval-scope.test.ts": 3898, + "packages/core/tests/eval-timeout.test.ts": 4012, + "packages/core/tests/eval-transform.test.ts": 3660, + "packages/core/tests/evaluate-component.test.ts": 5827, + "packages/core/tests/evaluate-loaded-copy.test.ts": 3994, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 4113, + "packages/core/tests/evaluation-profile.test.ts": 3779, + "packages/core/tests/exec-timeout.test.ts": 4022, + "packages/core/tests/execute.test.ts": 4458, + "packages/core/tests/execution-protocol.test.ts": 5301, + "packages/core/tests/expand.test.ts": 4673, + "packages/core/tests/expansion-identity.test.ts": 4548, + "packages/core/tests/expansion-metadata.test.ts": 3861, + "packages/core/tests/expression-props.test.ts": 4437, + "packages/core/tests/fail-component.test.ts": 4195, + "packages/core/tests/failure-printing.test.ts": 4156, + "packages/core/tests/fatal-cause.test.ts": 3759, + "packages/core/tests/fetch-component.test.ts": 5241, + "packages/core/tests/file-component.test.ts": 5891, + "packages/core/tests/file-delete-component.test.ts": 4132, + "packages/core/tests/files-fatal.test.ts": 4879, + "packages/core/tests/foreground-exec.test.ts": 4978, + "packages/core/tests/frontmatter.test.ts": 3609, + "packages/core/tests/function-components.test.ts": 4564, + "packages/core/tests/generated-composition.test.ts": 4502, + "packages/core/tests/generated-xmd.test.ts": 5865, + "packages/core/tests/glob-api.test.ts": 1870, + "packages/core/tests/glob-component.test.ts": 4612, + "packages/core/tests/guarded-journal.test.ts": 3953, + "packages/core/tests/has-content.test.ts": 3774, + "packages/core/tests/heal.test.ts": 3735, + "packages/core/tests/if.test.ts": 4420, + "packages/core/tests/inline-root.test.ts": 3999, + "packages/core/tests/invocation-failures.test.ts": 3708, + "packages/core/tests/invocation-identity.test.ts": 4385, + "packages/core/tests/invocation-scope.test.ts": 4060, + "packages/core/tests/journal-source-position.test.ts": 4210, + "packages/core/tests/json-component.test.ts": 4198, + "packages/core/tests/json.test.ts": 3686, + "packages/core/tests/let.test.ts": 4053, + "packages/core/tests/loaded-copy-files.test.ts": 3954, + "packages/core/tests/loop.test.ts": 4890, + "packages/core/tests/named-slots.test.ts": 4735, "packages/core/tests/output-error-mode.test.ts": 5015, - "packages/core/tests/output-normalize.test.ts": 1545, - "packages/core/tests/output-terminal.test.ts": 1661, - "packages/core/tests/parse-components.test.ts": 4054, - "packages/core/tests/plan-response.test.ts": 1590, - "packages/core/tests/plugin-api.test.ts": 3570, - "packages/core/tests/plugin.test.ts": 3779, - "packages/core/tests/props-binding.test.ts": 4161, - "packages/core/tests/protected-content.test.ts": 3610, - "packages/core/tests/registered-printed-errors.test.ts": 3784, - "packages/core/tests/replay-stream.test.ts": 1575, - "packages/core/tests/retain.test.ts": 3987, - "packages/core/tests/root-composition.test.ts": 4067, - "packages/core/tests/root-props.test.ts": 4128, - "packages/core/tests/root-provider.test.ts": 4123, - "packages/core/tests/sample-component.test.ts": 4798, - "packages/core/tests/scanner.test.ts": 3617, - "packages/core/tests/scope-local.test.ts": 1526, - "packages/core/tests/secret-detection.test.ts": 4746, - "packages/core/tests/secret-files.test.ts": 2071, - "packages/core/tests/secret-rules.test.ts": 2055, - "packages/core/tests/secret-scanner-baseline.test.ts": 1753, - "packages/core/tests/secret-scanner.test.ts": 1903, - "packages/core/tests/source-position.test.ts": 3848, - "packages/core/tests/state-ownership.test.ts": 3814, - "packages/core/tests/streaming-emission.test.ts": 3810, - "packages/core/tests/switch.test.ts": 4503, - "packages/core/tests/syntax-catalog.test.ts": 4532, - "packages/core/tests/syntax-component.test.ts": 5259, - "packages/core/tests/syntax-loaded-copy.test.ts": 4023, - "packages/core/tests/temp-dir.test.ts": 6377, - "packages/core/tests/temp-file-compiler.test.ts": 2196, - "packages/core/tests/text-interpolation.test.ts": 3757, - "packages/core/tests/try-content.test.ts": 3793, - "packages/core/tests/unused-in-diff.test.ts": 4133, - "packages/core/tests/validate.test.ts": 3870, - "packages/core/tests/validation-integration.test.ts": 3900, - "packages/core/tests/workflow-component-bundle.test.ts": 4243, - "packages/durable-streams/tests/context.test.ts": 1809, - "packages/durable-streams/tests/deterministic-id.test.ts": 1863, - "packages/durable-streams/tests/divergence-api.test.ts": 1830, - "packages/durable-streams/tests/divergence.test.ts": 1908, - "packages/durable-streams/tests/durable-each.test.ts": 1894, - "packages/durable-streams/tests/durable-position.test.ts": 1812, - "packages/durable-streams/tests/durable-run.test.ts": 1886, - "packages/durable-streams/tests/ephemeral.test.ts": 1825, - "packages/durable-streams/tests/fail-stop.test.ts": 1874, - "packages/durable-streams/tests/guard-stream.test.ts": 1952, - "packages/durable-streams/tests/live-coordinator.test.ts": 1854, - "packages/durable-streams/tests/parse.test.ts": 1812, - "packages/durable-streams/tests/replay-guard.test.ts": 1923, - "packages/durable-streams/tests/replay-index.test.ts": 1619, - "packages/durable-streams/tests/retained.test.ts": 1664, - "packages/durable-streams/tests/serialize-event.test.ts": 1803, - "packages/durable-streams/tests/smoke.test.ts": 1780, - "packages/durable-streams/tests/structured-concurrency.test.ts": 1977, - "packages/durable-streams/tests/terminal-boundary.test.ts": 1892, - "packages/durable-streams/tests/types.test.ts": 1520, - "packages/git/tests/ambient-authentication.test.ts": 27034, - "packages/git/tests/api-entrypoint.test.ts": 5078, - "packages/git/tests/credential-helper.test.ts": 6204, - "packages/git/tests/git-add-crash.test.ts": 7227, - "packages/git/tests/git-add-durability.test.ts": 26695, - "packages/git/tests/git-add.test.ts": 35508, - "packages/git/tests/git-commit-crash.test.ts": 7481, - "packages/git/tests/git-commit-durability.test.ts": 34971, - "packages/git/tests/git-commit.test.ts": 41874, - "packages/git/tests/git-host-effect.test.ts": 9161, - "packages/git/tests/git-push-crash.test.ts": 10272, - "packages/git/tests/git-push-durability.test.ts": 44877, - "packages/git/tests/git-push.test.ts": 53878, - "packages/git/tests/git-switch-crash.test.ts": 7271, - "packages/git/tests/git-switch-durability.test.ts": 22118, - "packages/git/tests/git-switch.test.ts": 23370, - "packages/git/tests/git.test.ts": 4434, - "packages/git/tests/github-activation.test.ts": 5703, - "packages/git/tests/github-issues.test.ts": 4432, - "packages/git/tests/github-pull-requests.test.ts": 4086, - "packages/git/tests/github-workflow-activation.test.ts": 6012, - "packages/git/tests/issue-markdown.test.ts": 11443, - "packages/git/tests/issue-records.test.ts": 4436, - "packages/git/tests/materialization.test.ts": 5130, - "packages/git/tests/module-partition.test.ts": 5472, - "packages/git/tests/plugin.test.ts": 4812, - "packages/git/tests/provider-neutrality.test.ts": 3598, - "packages/git/tests/public-entrypoint.test.ts": 9766, - "packages/git/tests/pull-request-crash.test.ts": 8575, - "packages/git/tests/pull-request-durability.test.ts": 42850, - "packages/git/tests/pull-request-read.test.ts": 8277, - "packages/git/tests/pull-request-records.test.ts": 4350, - "packages/git/tests/pull-request.test.ts": 73466, - "packages/git/tests/repository-components.test.ts": 12917, - "packages/git/tests/repository-control-plane.test.ts": 11733, - "packages/git/tests/repository-materialization.test.ts": 8745, - "packages/git/tests/repository-replay.test.ts": 16106, - "packages/git/tests/repository-storage.test.ts": 20115, - "packages/git/tests/run-composition-ambient.test.ts": 13438, - "packages/git/tests/run-composition-lazy.test.ts": 5937, - "packages/git/tests/run-composition-managed.test.ts": 15140, - "packages/git/tests/run-composition-remote.test.ts": 15293, - "packages/git/tests/selection-authentication.test.ts": 1841, - "packages/git/tests/worktree-replay.test.ts": 6981, - "packages/runtime/tests/agent-session-coordinator.test.ts": 2234, - "packages/runtime/tests/duration.test.ts": 1545, - "packages/runtime/tests/executable-observer.test.ts": 1928, - "packages/runtime/tests/fetch.test.ts": 2383, - "packages/runtime/tests/fs.test.ts": 1733, - "packages/runtime/tests/host-files.test.ts": 2589, - "packages/runtime/tests/native-launcher.test.ts": 3955, - "packages/runtime/tests/process.test.ts": 1740, - "packages/runtime/tests/service.test.ts": 1849, - "packages/test-agent/tests/acp-server.test.ts": 1719, - "packages/test-agent/tests/behavior-engine.test.ts": 4298, - "packages/test-agent/tests/bridge.test.ts": 3716, - "packages/test-agent/tests/command-lazy.test.ts": 8032, - "packages/test-agent/tests/components.test.ts": 61791, - "packages/test-agent/tests/controller.test.ts": 2881, - "packages/test-agent/tests/cross-package-resolution.test.ts": 6157, - "packages/test-agent/tests/native-launch.test.ts": 30913, - "packages/test-agent/tests/net.test.ts": 1613, - "packages/test-agent/tests/profile.test.ts": 4043, - "packages/test-agent/tests/protocol.test.ts": 2443, - "packages/test-agent/tests/provider.test.ts": 4376, - "packages/test-agent/tests/public-api.test.ts": 5102, - "packages/test-agent/tests/route-slot.test.ts": 1527, - "packages/test-agent/tests/smoke.test.ts": 21250, - "packages/test-agent/tests/template.test.ts": 4007, - "packages/test-agent/tests/worker-lifecycle.test.ts": 13359, - "packages/test-support/tests/journal.test.ts": 1846, - "packages/testing/tests/agent-composition.test.ts": 4367, - "packages/testing/tests/assert-throws.test.ts": 5605, - "packages/testing/tests/assert.test.ts": 1565, - "packages/testing/tests/assertions.test.ts": 6194, - "packages/testing/tests/boundary-contract.test.ts": 5041, - "packages/testing/tests/cli.test.ts": 6822, - "packages/testing/tests/execution-harness.test.ts": 8902, - "packages/testing/tests/replay.test.ts": 5555, - "packages/testing/tests/smoke.test.ts": 5707, - "packages/testing/tests/test-component.test.ts": 6164, - "packages/testing/tests/testing-activation.test.ts": 5833, - "packages/testing/tests/testing-boundary.test.ts": 4698, - "packages/testing/tests/testing-mode.test.ts": 6306, - "packages/testing/tests/use-testing.test.ts": 5192, + "packages/core/tests/output-normalize.test.ts": 1529, + "packages/core/tests/output-terminal.test.ts": 1659, + "packages/core/tests/parse-components.test.ts": 4034, + "packages/core/tests/plan-response.test.ts": 1561, + "packages/core/tests/plugin-api.test.ts": 3606, + "packages/core/tests/plugin.test.ts": 3795, + "packages/core/tests/props-binding.test.ts": 4064, + "packages/core/tests/protected-content.test.ts": 3574, + "packages/core/tests/registered-printed-errors.test.ts": 3690, + "packages/core/tests/replay-stream.test.ts": 1506, + "packages/core/tests/retain.test.ts": 3951, + "packages/core/tests/root-composition.test.ts": 4078, + "packages/core/tests/root-props.test.ts": 4101, + "packages/core/tests/root-provider.test.ts": 4105, + "packages/core/tests/sample-component.test.ts": 4739, + "packages/core/tests/scanner.test.ts": 3611, + "packages/core/tests/scope-local.test.ts": 1522, + "packages/core/tests/secret-detection.test.ts": 4752, + "packages/core/tests/secret-files.test.ts": 2054, + "packages/core/tests/secret-rules.test.ts": 2065, + "packages/core/tests/secret-scanner-baseline.test.ts": 1712, + "packages/core/tests/secret-scanner.test.ts": 1894, + "packages/core/tests/source-position.test.ts": 3886, + "packages/core/tests/state-ownership.test.ts": 3992, + "packages/core/tests/streaming-emission.test.ts": 3859, + "packages/core/tests/switch.test.ts": 4486, + "packages/core/tests/syntax-catalog.test.ts": 4543, + "packages/core/tests/syntax-component.test.ts": 5165, + "packages/core/tests/syntax-loaded-copy.test.ts": 3999, + "packages/core/tests/temp-dir.test.ts": 6398, + "packages/core/tests/temp-file-compiler.test.ts": 2199, + "packages/core/tests/text-interpolation.test.ts": 3718, + "packages/core/tests/try-content.test.ts": 3770, + "packages/core/tests/unused-in-diff.test.ts": 4055, + "packages/core/tests/validate.test.ts": 3855, + "packages/core/tests/validation-integration.test.ts": 3878, + "packages/core/tests/workflow-component-bundle.test.ts": 4177, + "packages/durable-streams/tests/context.test.ts": 1758, + "packages/durable-streams/tests/deterministic-id.test.ts": 1837, + "packages/durable-streams/tests/divergence-api.test.ts": 1795, + "packages/durable-streams/tests/divergence.test.ts": 1842, + "packages/durable-streams/tests/durable-each.test.ts": 1825, + "packages/durable-streams/tests/durable-position.test.ts": 1826, + "packages/durable-streams/tests/durable-run.test.ts": 1865, + "packages/durable-streams/tests/ephemeral.test.ts": 1833, + "packages/durable-streams/tests/fail-stop.test.ts": 1867, + "packages/durable-streams/tests/guard-stream.test.ts": 1921, + "packages/durable-streams/tests/live-coordinator.test.ts": 1889, + "packages/durable-streams/tests/parse.test.ts": 1805, + "packages/durable-streams/tests/replay-guard.test.ts": 1904, + "packages/durable-streams/tests/replay-index.test.ts": 1648, + "packages/durable-streams/tests/retained.test.ts": 1659, + "packages/durable-streams/tests/serialize-event.test.ts": 1795, + "packages/durable-streams/tests/smoke.test.ts": 1755, + "packages/durable-streams/tests/structured-concurrency.test.ts": 1917, + "packages/durable-streams/tests/terminal-boundary.test.ts": 1849, + "packages/durable-streams/tests/types.test.ts": 1474, + "packages/git/tests/ambient-authentication.test.ts": 26920, + "packages/git/tests/api-entrypoint.test.ts": 5024, + "packages/git/tests/credential-helper.test.ts": 6150, + "packages/git/tests/git-add-crash.test.ts": 7101, + "packages/git/tests/git-add-durability.test.ts": 26659, + "packages/git/tests/git-add.test.ts": 36546, + "packages/git/tests/git-commit-crash.test.ts": 7525, + "packages/git/tests/git-commit-durability.test.ts": 35279, + "packages/git/tests/git-commit.test.ts": 41445, + "packages/git/tests/git-host-effect.test.ts": 8853, + "packages/git/tests/git-push-crash.test.ts": 10168, + "packages/git/tests/git-push-durability.test.ts": 44331, + "packages/git/tests/git-push.test.ts": 52468, + "packages/git/tests/git-switch-crash.test.ts": 7212, + "packages/git/tests/git-switch-durability.test.ts": 22671, + "packages/git/tests/git-switch.test.ts": 23633, + "packages/git/tests/git.test.ts": 4425, + "packages/git/tests/github-activation.test.ts": 5638, + "packages/git/tests/github-issues.test.ts": 4456, + "packages/git/tests/github-pull-requests.test.ts": 4028, + "packages/git/tests/github-workflow-activation.test.ts": 6072, + "packages/git/tests/issue-markdown.test.ts": 11597, + "packages/git/tests/issue-records.test.ts": 4345, + "packages/git/tests/materialization.test.ts": 5100, + "packages/git/tests/module-partition.test.ts": 5568, + "packages/git/tests/plugin.test.ts": 4922, + "packages/git/tests/provider-neutrality.test.ts": 3530, + "packages/git/tests/public-entrypoint.test.ts": 9609, + "packages/git/tests/pull-request-crash.test.ts": 8456, + "packages/git/tests/pull-request-durability.test.ts": 42879, + "packages/git/tests/pull-request-read.test.ts": 8248, + "packages/git/tests/pull-request-records.test.ts": 4297, + "packages/git/tests/pull-request.test.ts": 76370, + "packages/git/tests/repository-components.test.ts": 12989, + "packages/git/tests/repository-control-plane.test.ts": 11769, + "packages/git/tests/repository-materialization.test.ts": 8521, + "packages/git/tests/repository-replay.test.ts": 16218, + "packages/git/tests/repository-storage.test.ts": 19972, + "packages/git/tests/run-composition-ambient.test.ts": 13475, + "packages/git/tests/run-composition-lazy.test.ts": 5858, + "packages/git/tests/run-composition-managed.test.ts": 15234, + "packages/git/tests/run-composition-remote.test.ts": 15401, + "packages/git/tests/selection-authentication.test.ts": 1789, + "packages/git/tests/worktree-replay.test.ts": 6885, + "packages/runtime/tests/agent-session-coordinator.test.ts": 2147, + "packages/runtime/tests/duration.test.ts": 1526, + "packages/runtime/tests/executable-observer.test.ts": 1867, + "packages/runtime/tests/fetch.test.ts": 2341, + "packages/runtime/tests/fs.test.ts": 1744, + "packages/runtime/tests/host-files.test.ts": 2608, + "packages/runtime/tests/native-launcher.test.ts": 3932, + "packages/runtime/tests/process.test.ts": 1698, + "packages/runtime/tests/service.test.ts": 1878, + "packages/test-agent/tests/acp-server.test.ts": 1670, + "packages/test-agent/tests/behavior-engine.test.ts": 4244, + "packages/test-agent/tests/bridge.test.ts": 3685, + "packages/test-agent/tests/command-lazy.test.ts": 7878, + "packages/test-agent/tests/components.test.ts": 61379, + "packages/test-agent/tests/controller.test.ts": 2867, + "packages/test-agent/tests/cross-package-resolution.test.ts": 6126, + "packages/test-agent/tests/native-launch.test.ts": 30721, + "packages/test-agent/tests/net.test.ts": 1612, + "packages/test-agent/tests/profile.test.ts": 3973, + "packages/test-agent/tests/protocol.test.ts": 2381, + "packages/test-agent/tests/provider.test.ts": 4345, + "packages/test-agent/tests/public-api.test.ts": 5052, + "packages/test-agent/tests/route-slot.test.ts": 1545, + "packages/test-agent/tests/smoke.test.ts": 21015, + "packages/test-agent/tests/template.test.ts": 3946, + "packages/test-agent/tests/worker-lifecycle.test.ts": 13274, + "packages/test-support/tests/journal.test.ts": 1790, + "packages/testing/tests/agent-composition.test.ts": 4374, + "packages/testing/tests/assert-throws.test.ts": 5586, + "packages/testing/tests/assert.test.ts": 1535, + "packages/testing/tests/assertions.test.ts": 6102, + "packages/testing/tests/boundary-contract.test.ts": 4997, + "packages/testing/tests/cli.test.ts": 6740, + "packages/testing/tests/execution-harness.test.ts": 8986, + "packages/testing/tests/replay.test.ts": 5551, + "packages/testing/tests/smoke.test.ts": 5689, + "packages/testing/tests/test-component.test.ts": 6283, + "packages/testing/tests/testing-activation.test.ts": 5898, + "packages/testing/tests/testing-boundary.test.ts": 4710, + "packages/testing/tests/testing-mode.test.ts": 6244, + "packages/testing/tests/use-testing.test.ts": 5237, "packages/web/tests/assets.test.ts": 1596, - "packages/web/tests/client-logic.test.ts": 1649, - "packages/web/tests/compile.test.ts": 4674, - "packages/web/tests/component.test.ts": 5194, - "packages/web/tests/declaration.test.ts": 4460, - "packages/web/tests/document.test.ts": 4731, - "packages/web/tests/elicitation.test.ts": 4488, - "packages/web/tests/live-form.test.ts": 4360, - "packages/web/tests/markdown.test.ts": 1790, - "packages/web/tests/opener.test.ts": 4349, - "packages/web/tests/page.test.ts": 1575, - "packages/web/tests/responder.test.ts": 4637, - "packages/web/tests/server-lifecycle.test.ts": 4495, - "packages/web/tests/server.test.ts": 4765, - "packages/workflow/tests/generated-agent-component.test.ts": 6806, - "packages/workflow/tests/generated-observations.test.ts": 4144, - "packages/workflow/tests/public-entrypoint.test.ts": 5919, - "packages/workflow/tests/retained-run.test.ts": 5048, - "packages/workflow/tests/service-denial.test.ts": 1865, - "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 6099, - "packages/workflow/tests/workflow-agent-sessions.test.ts": 5001, - "packages/workflow/tests/workflow-bundle.test.ts": 4467, - "packages/workflow/tests/workflow-checkpoint.test.ts": 6174, - "packages/workflow/tests/workflow-definition.test.ts": 4212, - "packages/workflow/tests/workflow-export.test.ts": 8073, - "packages/workflow/tests/workflow-fork-source.test.ts": 5140, - "packages/workflow/tests/workflow-fork.test.ts": 4118, - "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 6062, - "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6723, - "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 11202, - "packages/workflow/tests/workflow-run-journal.test.ts": 7244, - "packages/workflow/tests/workflow-run-storage.test.ts": 8184, - "packages/workflow/tests/workflow-run.test.ts": 4985, - "packages/workflow/tests/workflow-suspension-answer.test.ts": 6002, - "packages/workflow/tests/workflow-suspension.test.ts": 5685, - "packages/workflow/tests/workspace-crash-recovery.test.ts": 7784, - "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 5102, - "packages/workflow/tests/workspace-effect-transaction.test.ts": 5918, - "packages/workflow/tests/workspace-effect.test.ts": 6014, - "packages/workflow/tests/workspace-files.test.ts": 12933, - "packages/workflow/tests/workspace-root-restoration.test.ts": 6760, - "packages/workflow/tests/workspace-root.test.ts": 5094, - "packages/workflow/tests/workspace-transaction.test.ts": 5097, - "packages/workflow/tests/xmd-artifact.test.ts": 6972, - "scripts/tests/acpx-vendor.test.ts": 2160, - "scripts/tests/adapter-distribution.test.ts": 4586, - "scripts/tests/adapter-npm-package.test.ts": 29243, - "scripts/tests/adapter-vendor.test.ts": 4198, - "scripts/tests/bootstrap-npm-package.test.ts": 11443, - "scripts/tests/build-npm.test.ts": 40709, - "scripts/tests/build-web-client.test.ts": 30613, - "scripts/tests/bump-version.test.ts": 1659, - "scripts/tests/changed-paths.test.ts": 1583, - "scripts/tests/ci-workflow.test.ts": 4871, - "scripts/tests/cli-npm-bin.test.ts": 252922, - "scripts/tests/cloudflare-dofs-vendor.test.ts": 4431, - "scripts/tests/consumer-cycle.test.ts": 1661, - "scripts/tests/documentation-validation.test.ts": 6267, - "scripts/tests/filesystem-contract-workflow.test.ts": 1572, - "scripts/tests/frozen-entry.test.ts": 7664, - "scripts/tests/jsr-consumer-documentation.test.ts": 18170, - "scripts/tests/main-green.test.ts": 1936, - "scripts/tests/main-health.test.ts": 1715, - "scripts/tests/measure-test-weights.test.ts": 1992, - "scripts/tests/no-module-scoped-registry.test.ts": 2370, - "scripts/tests/no-redundant-test-scope.test.ts": 6520, - "scripts/tests/no-sync-filesystem.test.ts": 6462, - "scripts/tests/no-yield-in-finally.test.ts": 2377, - "scripts/tests/oxlint-policy.test.ts": 12237, - "scripts/tests/packaged-document.test.ts": 1618, - "scripts/tests/prefer-effection-operation.test.ts": 7468, - "scripts/tests/prefer-effection-result.test.ts": 5680, - "scripts/tests/prepared-state.test.ts": 2853, - "scripts/tests/publish-workflow-generator.test.ts": 6220, - "scripts/tests/publish-workflow-membership.test.ts": 1734, - "scripts/tests/publishable-membership-agreement.test.ts": 4083, - "scripts/tests/readme-targets.test.ts": 18898, - "scripts/tests/release-targets.test.ts": 1695, - "scripts/tests/review-infrastructure.test.ts": 6279, - "scripts/tests/runtime-exclusions.test.ts": 2097, - "scripts/tests/runtime-tests.test.ts": 1686, - "scripts/tests/scope-bound-event-registration.test.ts": 6340, - "scripts/tests/shard-execution.test.ts": 11463, - "scripts/tests/staged-write.test.ts": 1604, - "scripts/tests/test-file-discovery.test.ts": 1840, - "scripts/tests/test-shards.test.ts": 1619, - "scripts/tests/test-weights.test.ts": 1864, - "scripts/tests/tracked.test.ts": 1585, - "scripts/tests/verify-adapter.test.ts": 7915, - "scripts/tests/verify-clean.test.ts": 1806, - "scripts/tests/verify-coordinator.test.ts": 1992, - "scripts/tests/version-lockstep.test.ts": 1782, - "scripts/tests/web-client-module.test.ts": 1570, - "scripts/tests/workspace.test.ts": 1581 + "packages/web/tests/client-logic.test.ts": 1641, + "packages/web/tests/compile.test.ts": 4685, + "packages/web/tests/component.test.ts": 5161, + "packages/web/tests/declaration.test.ts": 4462, + "packages/web/tests/document.test.ts": 4704, + "packages/web/tests/elicitation.test.ts": 4446, + "packages/web/tests/live-form.test.ts": 4335, + "packages/web/tests/markdown.test.ts": 1739, + "packages/web/tests/opener.test.ts": 4387, + "packages/web/tests/page.test.ts": 1617, + "packages/web/tests/responder.test.ts": 4673, + "packages/web/tests/server-lifecycle.test.ts": 4609, + "packages/web/tests/server.test.ts": 4878, + "packages/workflow/tests/generated-agent-component.test.ts": 6951, + "packages/workflow/tests/generated-observations.test.ts": 4117, + "packages/workflow/tests/public-entrypoint.test.ts": 5937, + "packages/workflow/tests/retained-run.test.ts": 5062, + "packages/workflow/tests/service-denial.test.ts": 1840, + "packages/workflow/tests/workflow-agent-checkpoints.test.ts": 6196, + "packages/workflow/tests/workflow-agent-sessions.test.ts": 5043, + "packages/workflow/tests/workflow-bundle.test.ts": 4453, + "packages/workflow/tests/workflow-checkpoint.test.ts": 6455, + "packages/workflow/tests/workflow-definition.test.ts": 4303, + "packages/workflow/tests/workflow-export.test.ts": 8192, + "packages/workflow/tests/workflow-fork-source.test.ts": 5083, + "packages/workflow/tests/workflow-fork.test.ts": 4149, + "packages/workflow/tests/workflow-lifecycle-authority.test.ts": 6054, + "packages/workflow/tests/workflow-lifecycle-control.test.ts": 6620, + "packages/workflow/tests/workflow-lifecycle-inspection.test.ts": 11366, + "packages/workflow/tests/workflow-run-journal.test.ts": 7268, + "packages/workflow/tests/workflow-run-storage.test.ts": 8669, + "packages/workflow/tests/workflow-run.test.ts": 5002, + "packages/workflow/tests/workflow-suspension-answer.test.ts": 5957, + "packages/workflow/tests/workflow-suspension.test.ts": 5651, + "packages/workflow/tests/workspace-crash-recovery.test.ts": 7782, + "packages/workflow/tests/workspace-effect-loaded-copy.test.ts": 5088, + "packages/workflow/tests/workspace-effect-transaction.test.ts": 6058, + "packages/workflow/tests/workspace-effect.test.ts": 5885, + "packages/workflow/tests/workspace-files.test.ts": 12944, + "packages/workflow/tests/workspace-root-restoration.test.ts": 6768, + "packages/workflow/tests/workspace-root.test.ts": 5232, + "packages/workflow/tests/workspace-transaction.test.ts": 5253, + "packages/workflow/tests/xmd-artifact.test.ts": 7086, + "scripts/tests/acpx-vendor.test.ts": 2133, + "scripts/tests/adapter-distribution.test.ts": 4533, + "scripts/tests/adapter-npm-package.test.ts": 29375, + "scripts/tests/adapter-vendor.test.ts": 4164, + "scripts/tests/bootstrap-npm-package.test.ts": 11332, + "scripts/tests/build-npm.test.ts": 40632, + "scripts/tests/build-web-client.test.ts": 30546, + "scripts/tests/bump-version.test.ts": 1665, + "scripts/tests/changed-paths.test.ts": 1507, + "scripts/tests/ci-workflow.test.ts": 4789, + "scripts/tests/cli-npm-bin.test.ts": 254860, + "scripts/tests/cloudflare-dofs-vendor.test.ts": 4318, + "scripts/tests/consumer-cycle.test.ts": 1629, + "scripts/tests/documentation-validation.test.ts": 6250, + "scripts/tests/filesystem-contract-workflow.test.ts": 1587, + "scripts/tests/frozen-entry.test.ts": 8636, + "scripts/tests/jsr-consumer-documentation.test.ts": 18081, + "scripts/tests/main-green.test.ts": 1952, + "scripts/tests/main-health.test.ts": 1704, + "scripts/tests/measure-test-weights.test.ts": 1951, + "scripts/tests/no-module-scoped-registry.test.ts": 2386, + "scripts/tests/no-redundant-test-scope.test.ts": 6499, + "scripts/tests/no-sync-filesystem.test.ts": 6361, + "scripts/tests/no-yield-in-finally.test.ts": 2391, + "scripts/tests/oxlint-policy.test.ts": 12056, + "scripts/tests/packaged-document.test.ts": 1592, + "scripts/tests/prefer-effection-operation.test.ts": 7576, + "scripts/tests/prefer-effection-result.test.ts": 5674, + "scripts/tests/prepared-state.test.ts": 2741, + "scripts/tests/publish-workflow-generator.test.ts": 6106, + "scripts/tests/publish-workflow-membership.test.ts": 1724, + "scripts/tests/publishable-membership-agreement.test.ts": 4059, + "scripts/tests/readme-targets.test.ts": 18856, + "scripts/tests/release-targets.test.ts": 1661, + "scripts/tests/review-infrastructure.test.ts": 6247, + "scripts/tests/runtime-exclusions.test.ts": 2075, + "scripts/tests/runtime-tests.test.ts": 1699, + "scripts/tests/scope-bound-event-registration.test.ts": 6375, + "scripts/tests/shard-execution.test.ts": 11387, + "scripts/tests/staged-write.test.ts": 1543, + "scripts/tests/test-file-discovery.test.ts": 1781, + "scripts/tests/test-shards.test.ts": 1561, + "scripts/tests/test-weights.test.ts": 1863, + "scripts/tests/tracked.test.ts": 1526, + "scripts/tests/verify-adapter.test.ts": 7643, + "scripts/tests/verify-clean.test.ts": 1751, + "scripts/tests/verify-coordinator.test.ts": 1943, + "scripts/tests/version-lockstep.test.ts": 1743, + "scripts/tests/web-client-module.test.ts": 1573, + "scripts/tests/workspace.test.ts": 1528 }, "node": { - "packages/acp/tests/acceptance.test.ts": 2214, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4877, - "packages/acp/tests/acpx-materialization.test.ts": 7085, - "packages/acp/tests/acpx-transient-env.test.ts": 831, - "packages/acp/tests/adapter-materialization.test.ts": 74897, - "packages/acp/tests/adapter-protocol.test.ts": 55440, - "packages/acp/tests/codex-surfaces.test.ts": 670, - "packages/acp/tests/native-launch.test.ts": 2283, - "packages/acp/tests/permission-bridge.test.ts": 1272, - "packages/acp/tests/provider.test.ts": 2223, - "packages/acp/tests/serial-queue.test.ts": 499, - "packages/acp/tests/session-key.test.ts": 551, - "packages/acp/tests/terminal-screen.test.ts": 624, - "packages/cli/tests/agent-adapters.test.ts": 2528, - "packages/cli/tests/agent-cli.test.ts": 43989, - "packages/cli/tests/agent-config.test.ts": 487, - "packages/cli/tests/agent-options-cli.test.ts": 16335, - "packages/cli/tests/agent-options.test.ts": 494, - "packages/cli/tests/agent-session-coordinator.test.ts": 1365, - "packages/cli/tests/cli-help.test.ts": 40794, - "packages/cli/tests/command.test.ts": 14816, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4258, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 172135, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3627, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 9948, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 6186, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 18155, - "packages/cli/tests/evaluate-component.test.ts": 15406, - "packages/cli/tests/fetch-cli.test.ts": 4942, - "packages/cli/tests/file-stream.test.ts": 536, - "packages/cli/tests/inline-cli.test.ts": 67652, - "packages/cli/tests/launch-timeout.test.ts": 20546, - "packages/cli/tests/packaged-document.test.ts": 1594, - "packages/cli/tests/plan-args.test.ts": 507, - "packages/cli/tests/plan-cli.test.ts": 118889, - "packages/cli/tests/plan-command-document.test.ts": 8078, - "packages/cli/tests/plan-component.test.ts": 11071, - "packages/cli/tests/plan-host-acts.test.ts": 2359, - "packages/cli/tests/plan.test.ts": 15760, - "packages/cli/tests/plugin-cli.test.ts": 82043, - "packages/cli/tests/plugin-host.test.ts": 3277, - "packages/cli/tests/plugin-modules.test.ts": 487, - "packages/cli/tests/plugin-selection.test.ts": 514, - "packages/cli/tests/process-retention.test.ts": 4876, - "packages/cli/tests/props-cli.test.ts": 63573, - "packages/cli/tests/props-schema.test.ts": 1342, - "packages/cli/tests/props-sources.test.ts": 690, - "packages/cli/tests/repl-boundaries.test.ts": 2486, - "packages/cli/tests/repl-composition.test.ts": 725, - "packages/cli/tests/repl-execution.test.ts": 2896, - "packages/cli/tests/repl-journey.test.ts": 11109, - "packages/cli/tests/repl-model.test.ts": 2703, - "packages/cli/tests/repl-route.test.ts": 2270, - "packages/cli/tests/repl-terminal.test.ts": 1276, - "packages/cli/tests/run-composition.test.ts": 4115, + "packages/acp/tests/acceptance.test.ts": 2110, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4869, + "packages/acp/tests/acpx-materialization.test.ts": 7089, + "packages/acp/tests/acpx-transient-env.test.ts": 828, + "packages/acp/tests/adapter-materialization.test.ts": 73589, + "packages/acp/tests/adapter-protocol.test.ts": 54177, + "packages/acp/tests/codex-surfaces.test.ts": 657, + "packages/acp/tests/native-launch.test.ts": 2222, + "packages/acp/tests/permission-bridge.test.ts": 1220, + "packages/acp/tests/provider.test.ts": 2161, + "packages/acp/tests/serial-queue.test.ts": 493, + "packages/acp/tests/session-key.test.ts": 549, + "packages/acp/tests/terminal-screen.test.ts": 611, + "packages/cli/tests/agent-adapters.test.ts": 2453, + "packages/cli/tests/agent-cli.test.ts": 43285, + "packages/cli/tests/agent-config.test.ts": 486, + "packages/cli/tests/agent-options-cli.test.ts": 16242, + "packages/cli/tests/agent-options.test.ts": 481, + "packages/cli/tests/agent-session-coordinator.test.ts": 1353, + "packages/cli/tests/cli-help.test.ts": 40747, + "packages/cli/tests/command.test.ts": 14935, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 4260, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 171781, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 3738, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 10122, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 6100, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 18177, + "packages/cli/tests/evaluate-component.test.ts": 15487, + "packages/cli/tests/fetch-cli.test.ts": 4949, + "packages/cli/tests/file-stream.test.ts": 533, + "packages/cli/tests/inline-cli.test.ts": 68083, + "packages/cli/tests/launch-timeout.test.ts": 20547, + "packages/cli/tests/packaged-document.test.ts": 1584, + "packages/cli/tests/plan-args.test.ts": 506, + "packages/cli/tests/plan-cli.test.ts": 119226, + "packages/cli/tests/plan-command-document.test.ts": 8084, + "packages/cli/tests/plan-component.test.ts": 11022, + "packages/cli/tests/plan-host-acts.test.ts": 2346, + "packages/cli/tests/plan.test.ts": 15829, + "packages/cli/tests/plugin-cli.test.ts": 82240, + "packages/cli/tests/plugin-host.test.ts": 3281, + "packages/cli/tests/plugin-modules.test.ts": 505, + "packages/cli/tests/plugin-selection.test.ts": 512, + "packages/cli/tests/process-retention.test.ts": 4997, + "packages/cli/tests/props-cli.test.ts": 63707, + "packages/cli/tests/props-schema.test.ts": 1350, + "packages/cli/tests/props-sources.test.ts": 683, + "packages/cli/tests/repl-boundaries.test.ts": 2529, + "packages/cli/tests/repl-composition.test.ts": 751, + "packages/cli/tests/repl-execution.test.ts": 2941, + "packages/cli/tests/repl-journey.test.ts": 11042, + "packages/cli/tests/repl-model.test.ts": 2778, + "packages/cli/tests/repl-route.test.ts": 2307, + "packages/cli/tests/repl-terminal.test.ts": 1256, + "packages/cli/tests/run-composition.test.ts": 4152, "packages/cli/tests/run-deadline.test.ts": 3685, - "packages/cli/tests/run-profile.test.ts": 1236, - "packages/cli/tests/run-timeouts.test.ts": 20388, - "packages/cli/tests/secret-detection-cli.test.ts": 37989, - "packages/cli/tests/service-document.test.ts": 2573, - "packages/cli/tests/service-host.test.ts": 2340, - "packages/cli/tests/session-launch-cli.test.ts": 11070, - "packages/cli/tests/stdin-cli.test.ts": 63409, - "packages/cli/tests/stdout-delivery.test.ts": 497, - "packages/cli/tests/syntax-cli.test.ts": 62826, - "packages/cli/tests/targets-cli.test.ts": 51186, - "packages/cli/tests/test-root-profile.test.ts": 7084, - "packages/cli/tests/test-target.test.ts": 74365, - "packages/cli/tests/testing-activation.test.ts": 1516, - "packages/cli/tests/testing-execution-host.test.ts": 93380, - "packages/cli/tests/upgrade-cli.test.ts": 47918, - "packages/cli/tests/upgrade-command-document.test.ts": 5587, - "packages/cli/tests/upgrade-output.test.ts": 1896, - "packages/cli/tests/value-root.test.ts": 31919, - "packages/cli/tests/verbose-component.test.ts": 1944, - "packages/cli/tests/workflow-declaration.test.ts": 1458, - "packages/cli/tests/workflow-host.test.ts": 10902, - "packages/code-review-agent/tests/doctor.test.ts": 489, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 515, - "packages/code-review-agent/tests/parse-diff.test.ts": 507, - "packages/code-review-agent/tests/parse-doctor.test.ts": 1157, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 477, - "packages/code-review-agent/tests/policy.test.ts": 494, - "packages/core/tests/agent-api.test.ts": 514, - "packages/core/tests/agent-components.test.ts": 2041, - "packages/core/tests/agent-function-components.test.ts": 4301, - "packages/core/tests/agent-session-launch.test.ts": 4590, - "packages/core/tests/agent-session-placement.test.ts": 517, - "packages/core/tests/agent-session-use.test.ts": 503, - "packages/core/tests/agent-stack.test.ts": 1569, - "packages/core/tests/answer-identity.test.ts": 513, - "packages/core/tests/answers-component.test.ts": 2342, - "packages/core/tests/answers-expansion-recursion.test.ts": 979, - "packages/core/tests/capture-errors.test.ts": 1261, - "packages/core/tests/capture-props.test.ts": 991, - "packages/core/tests/cli-journal.test.ts": 22526, - "packages/core/tests/code-block-component.test.ts": 3318, - "packages/core/tests/compiler-boundary.test.ts": 1179, - "packages/core/tests/component-api.test.ts": 559, - "packages/core/tests/component-registration.test.ts": 2126, - "packages/core/tests/component-returns.test.ts": 2950, - "packages/core/tests/component-schema-conformance.test.ts": 1126, - "packages/core/tests/config-api.test.ts": 1317, - "packages/core/tests/construct-error-observation.test.ts": 1432, - "packages/core/tests/contextual-cwd.test.ts": 2499, - "packages/core/tests/daemon-integration.test.ts": 1955, - "packages/core/tests/daemon.test.ts": 595, - "packages/core/tests/declared-markdown-component.test.ts": 3319, - "packages/core/tests/document-output-api.test.ts": 520, - "packages/core/tests/document-target-execution.test.ts": 4941, - "packages/core/tests/document-targets.test.ts": 1246, - "packages/core/tests/document-validation.test.ts": 3151, - "packages/core/tests/documentation-index.test.ts": 904, - "packages/core/tests/each.test.ts": 1348, - "packages/core/tests/elicit-component.test.ts": 2100, - "packages/core/tests/ephemeral-service.test.ts": 1907, - "packages/core/tests/eval-bindings.test.ts": 1306, - "packages/core/tests/eval-context.test.ts": 673, - "packages/core/tests/eval-durable.test.ts": 1382, - "packages/core/tests/eval-error-mode.test.ts": 1732, - "packages/core/tests/eval-interpolate.test.ts": 521, - "packages/core/tests/eval-middleware.test.ts": 604, - "packages/core/tests/eval-persist.test.ts": 1475, - "packages/core/tests/eval-return.test.ts": 1359, - "packages/core/tests/eval-scope.test.ts": 1173, - "packages/core/tests/eval-timeout.test.ts": 1347, - "packages/core/tests/eval-transform.test.ts": 590, - "packages/core/tests/evaluate-component.test.ts": 3762, - "packages/core/tests/evaluate-loaded-copy.test.ts": 1404, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 1467, - "packages/core/tests/evaluation-profile.test.ts": 898, - "packages/core/tests/exec-timeout.test.ts": 1413, - "packages/core/tests/execute.test.ts": 2063, - "packages/core/tests/execution-protocol.test.ts": 2931, - "packages/core/tests/expand.test.ts": 2017, - "packages/core/tests/expansion-identity.test.ts": 2065, - "packages/core/tests/expansion-metadata.test.ts": 1026, - "packages/core/tests/expression-props.test.ts": 1884, - "packages/core/tests/fail-component.test.ts": 1678, - "packages/core/tests/failure-printing.test.ts": 1484, - "packages/core/tests/fatal-cause.test.ts": 893, - "packages/core/tests/fetch-component.test.ts": 2938, - "packages/core/tests/file-component.test.ts": 3461, - "packages/core/tests/file-delete-component.test.ts": 1504, - "packages/core/tests/files-fatal.test.ts": 2437, - "packages/core/tests/foreground-exec.test.ts": 2319, - "packages/core/tests/frontmatter.test.ts": 553, - "packages/core/tests/function-components.test.ts": 2057, - "packages/core/tests/generated-composition.test.ts": 2059, - "packages/core/tests/generated-xmd.test.ts": 3590, - "packages/core/tests/glob-api.test.ts": 595, - "packages/core/tests/glob-component.test.ts": 2107, - "packages/core/tests/guarded-journal.test.ts": 1330, - "packages/core/tests/has-content.test.ts": 949, - "packages/core/tests/heal.test.ts": 945, - "packages/core/tests/if.test.ts": 1850, - "packages/core/tests/inline-root.test.ts": 1347, - "packages/core/tests/invocation-failures.test.ts": 590, - "packages/core/tests/invocation-identity.test.ts": 1686, - "packages/core/tests/invocation-scope.test.ts": 1215, - "packages/core/tests/journal-source-position.test.ts": 1456, - "packages/core/tests/json-component.test.ts": 1549, - "packages/core/tests/json.test.ts": 500, - "packages/core/tests/let.test.ts": 1354, - "packages/core/tests/loop.test.ts": 2322, - "packages/core/tests/named-slots.test.ts": 2291, - "packages/core/tests/output-error-mode.test.ts": 2574, - "packages/core/tests/output-normalize.test.ts": 523, - "packages/core/tests/output-terminal.test.ts": 678, - "packages/core/tests/parse-components.test.ts": 1418, - "packages/core/tests/plan-response.test.ts": 617, - "packages/core/tests/plugin-api.test.ts": 524, - "packages/core/tests/plugin.test.ts": 994, - "packages/core/tests/props-binding.test.ts": 1442, - "packages/core/tests/protected-content.test.ts": 550, - "packages/core/tests/registered-printed-errors.test.ts": 849, - "packages/core/tests/replay-stream.test.ts": 492, - "packages/core/tests/retain.test.ts": 1158, - "packages/core/tests/root-composition.test.ts": 1382, - "packages/core/tests/root-props.test.ts": 1478, - "packages/core/tests/root-provider.test.ts": 1360, - "packages/core/tests/sample-component.test.ts": 2419, - "packages/core/tests/scanner.test.ts": 602, - "packages/core/tests/scope-local.test.ts": 513, - "packages/core/tests/secret-detection.test.ts": 2190, - "packages/core/tests/secret-files.test.ts": 770, - "packages/core/tests/secret-rules.test.ts": 841, - "packages/core/tests/secret-scanner-baseline.test.ts": 790, - "packages/core/tests/secret-scanner.test.ts": 718, - "packages/core/tests/source-position.test.ts": 1169, - "packages/core/tests/state-ownership.test.ts": 1257, - "packages/core/tests/streaming-emission.test.ts": 1186, - "packages/core/tests/switch.test.ts": 1908, - "packages/core/tests/syntax-catalog.test.ts": 1912, - "packages/core/tests/syntax-component.test.ts": 2776, - "packages/core/tests/temp-dir.test.ts": 3772, - "packages/core/tests/temp-file-compiler.test.ts": 575, - "packages/core/tests/text-interpolation.test.ts": 947, - "packages/core/tests/try-content.test.ts": 957, - "packages/core/tests/unused-in-diff.test.ts": 1369, - "packages/core/tests/validate.test.ts": 807, - "packages/core/tests/validation-integration.test.ts": 1211, - "packages/core/tests/workflow-component-bundle.test.ts": 1592, - "packages/durable-streams/tests/context.test.ts": 529, - "packages/durable-streams/tests/deterministic-id.test.ts": 557, - "packages/durable-streams/tests/divergence-api.test.ts": 553, - "packages/durable-streams/tests/divergence.test.ts": 586, - "packages/durable-streams/tests/durable-each.test.ts": 591, - "packages/durable-streams/tests/durable-position.test.ts": 554, - "packages/durable-streams/tests/durable-run.test.ts": 564, - "packages/durable-streams/tests/ephemeral.test.ts": 550, - "packages/durable-streams/tests/fail-stop.test.ts": 617, - "packages/durable-streams/tests/guard-stream.test.ts": 630, - "packages/durable-streams/tests/live-coordinator.test.ts": 553, - "packages/durable-streams/tests/parse.test.ts": 582, + "packages/cli/tests/run-profile.test.ts": 1253, + "packages/cli/tests/run-timeouts.test.ts": 20254, + "packages/cli/tests/secret-detection-cli.test.ts": 37931, + "packages/cli/tests/service-document.test.ts": 2565, + "packages/cli/tests/service-host.test.ts": 2347, + "packages/cli/tests/session-launch-cli.test.ts": 11044, + "packages/cli/tests/stdin-cli.test.ts": 63445, + "packages/cli/tests/stdout-delivery.test.ts": 493, + "packages/cli/tests/syntax-cli.test.ts": 62013, + "packages/cli/tests/targets-cli.test.ts": 51063, + "packages/cli/tests/test-root-profile.test.ts": 7125, + "packages/cli/tests/test-target.test.ts": 73776, + "packages/cli/tests/testing-activation.test.ts": 1497, + "packages/cli/tests/testing-execution-host.test.ts": 91050, + "packages/cli/tests/upgrade-cli.test.ts": 46673, + "packages/cli/tests/upgrade-command-document.test.ts": 5530, + "packages/cli/tests/upgrade-output.test.ts": 1845, + "packages/cli/tests/value-root.test.ts": 31600, + "packages/cli/tests/verbose-component.test.ts": 1940, + "packages/cli/tests/workflow-declaration.test.ts": 1449, + "packages/cli/tests/workflow-host.test.ts": 10859, + "packages/code-review-agent/tests/doctor.test.ts": 478, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 522, + "packages/code-review-agent/tests/parse-diff.test.ts": 517, + "packages/code-review-agent/tests/parse-doctor.test.ts": 1167, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 482, + "packages/code-review-agent/tests/policy.test.ts": 484, + "packages/core/tests/agent-api.test.ts": 493, + "packages/core/tests/agent-components.test.ts": 2035, + "packages/core/tests/agent-function-components.test.ts": 4189, + "packages/core/tests/agent-session-launch.test.ts": 4584, + "packages/core/tests/agent-session-placement.test.ts": 495, + "packages/core/tests/agent-session-use.test.ts": 483, + "packages/core/tests/agent-stack.test.ts": 1530, + "packages/core/tests/all.test.ts": 3001, + "packages/core/tests/answer-identity.test.ts": 578, + "packages/core/tests/answers-component.test.ts": 2373, + "packages/core/tests/answers-expansion-recursion.test.ts": 970, + "packages/core/tests/capture-errors.test.ts": 1288, + "packages/core/tests/capture-props.test.ts": 993, + "packages/core/tests/cli-journal.test.ts": 21990, + "packages/core/tests/code-block-component.test.ts": 3154, + "packages/core/tests/compiler-boundary.test.ts": 1162, + "packages/core/tests/component-api.test.ts": 557, + "packages/core/tests/component-registration.test.ts": 2067, + "packages/core/tests/component-returns.test.ts": 2830, + "packages/core/tests/component-schema-conformance.test.ts": 1092, + "packages/core/tests/config-api.test.ts": 1242, + "packages/core/tests/construct-error-observation.test.ts": 1361, + "packages/core/tests/contextual-cwd.test.ts": 2435, + "packages/core/tests/daemon-integration.test.ts": 1891, + "packages/core/tests/daemon.test.ts": 578, + "packages/core/tests/declared-markdown-component.test.ts": 3168, + "packages/core/tests/document-output-api.test.ts": 495, + "packages/core/tests/document-target-execution.test.ts": 4535, + "packages/core/tests/document-targets.test.ts": 1191, + "packages/core/tests/document-validation.test.ts": 3100, + "packages/core/tests/documentation-index.test.ts": 819, + "packages/core/tests/each.test.ts": 1263, + "packages/core/tests/elicit-component.test.ts": 1996, + "packages/core/tests/ephemeral-service.test.ts": 1791, + "packages/core/tests/eval-bindings.test.ts": 1271, + "packages/core/tests/eval-context.test.ts": 572, + "packages/core/tests/eval-durable.test.ts": 1276, + "packages/core/tests/eval-error-mode.test.ts": 1635, + "packages/core/tests/eval-interpolate.test.ts": 502, + "packages/core/tests/eval-middleware.test.ts": 595, + "packages/core/tests/eval-persist.test.ts": 1380, + "packages/core/tests/eval-return.test.ts": 1313, + "packages/core/tests/eval-scope.test.ts": 1207, + "packages/core/tests/eval-timeout.test.ts": 1278, + "packages/core/tests/eval-transform.test.ts": 573, + "packages/core/tests/evaluate-component.test.ts": 3608, + "packages/core/tests/evaluate-loaded-copy.test.ts": 1339, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 1438, + "packages/core/tests/evaluation-profile.test.ts": 861, + "packages/core/tests/exec-timeout.test.ts": 1308, + "packages/core/tests/execute.test.ts": 1969, + "packages/core/tests/execution-protocol.test.ts": 2822, + "packages/core/tests/expand.test.ts": 1968, + "packages/core/tests/expansion-identity.test.ts": 2011, + "packages/core/tests/expansion-metadata.test.ts": 1010, + "packages/core/tests/expression-props.test.ts": 1863, + "packages/core/tests/fail-component.test.ts": 1631, + "packages/core/tests/failure-printing.test.ts": 1468, + "packages/core/tests/fatal-cause.test.ts": 890, + "packages/core/tests/fetch-component.test.ts": 2900, + "packages/core/tests/file-component.test.ts": 3333, + "packages/core/tests/file-delete-component.test.ts": 1461, + "packages/core/tests/files-fatal.test.ts": 2432, + "packages/core/tests/foreground-exec.test.ts": 2309, + "packages/core/tests/frontmatter.test.ts": 546, + "packages/core/tests/function-components.test.ts": 1982, + "packages/core/tests/generated-composition.test.ts": 2014, + "packages/core/tests/generated-xmd.test.ts": 3536, + "packages/core/tests/glob-api.test.ts": 625, + "packages/core/tests/glob-component.test.ts": 2058, + "packages/core/tests/guarded-journal.test.ts": 1304, + "packages/core/tests/has-content.test.ts": 924, + "packages/core/tests/heal.test.ts": 939, + "packages/core/tests/if.test.ts": 1837, + "packages/core/tests/inline-root.test.ts": 1327, + "packages/core/tests/invocation-failures.test.ts": 597, + "packages/core/tests/invocation-identity.test.ts": 1761, + "packages/core/tests/invocation-scope.test.ts": 1193, + "packages/core/tests/journal-source-position.test.ts": 1461, + "packages/core/tests/json-component.test.ts": 1541, + "packages/core/tests/json.test.ts": 499, + "packages/core/tests/let.test.ts": 1290, + "packages/core/tests/loop.test.ts": 2310, + "packages/core/tests/named-slots.test.ts": 2304, + "packages/core/tests/output-error-mode.test.ts": 2652, + "packages/core/tests/output-normalize.test.ts": 552, + "packages/core/tests/output-terminal.test.ts": 704, + "packages/core/tests/parse-components.test.ts": 1449, + "packages/core/tests/plan-response.test.ts": 609, + "packages/core/tests/plugin-api.test.ts": 532, + "packages/core/tests/plugin.test.ts": 1004, + "packages/core/tests/props-binding.test.ts": 1505, + "packages/core/tests/protected-content.test.ts": 551, + "packages/core/tests/registered-printed-errors.test.ts": 854, + "packages/core/tests/replay-stream.test.ts": 495, + "packages/core/tests/retain.test.ts": 1153, + "packages/core/tests/root-composition.test.ts": 1451, + "packages/core/tests/root-props.test.ts": 1490, + "packages/core/tests/root-provider.test.ts": 1343, + "packages/core/tests/sample-component.test.ts": 2330, + "packages/core/tests/scanner.test.ts": 590, + "packages/core/tests/scope-local.test.ts": 489, + "packages/core/tests/secret-detection.test.ts": 2104, + "packages/core/tests/secret-files.test.ts": 742, + "packages/core/tests/secret-rules.test.ts": 831, + "packages/core/tests/secret-scanner-baseline.test.ts": 747, + "packages/core/tests/secret-scanner.test.ts": 701, + "packages/core/tests/source-position.test.ts": 1126, + "packages/core/tests/state-ownership.test.ts": 1193, + "packages/core/tests/streaming-emission.test.ts": 1174, + "packages/core/tests/switch.test.ts": 1868, + "packages/core/tests/syntax-catalog.test.ts": 1826, + "packages/core/tests/syntax-component.test.ts": 2697, + "packages/core/tests/temp-dir.test.ts": 3741, + "packages/core/tests/temp-file-compiler.test.ts": 555, + "packages/core/tests/text-interpolation.test.ts": 907, + "packages/core/tests/try-content.test.ts": 905, + "packages/core/tests/unused-in-diff.test.ts": 1388, + "packages/core/tests/validate.test.ts": 795, + "packages/core/tests/validation-integration.test.ts": 1219, + "packages/core/tests/workflow-component-bundle.test.ts": 1577, + "packages/durable-streams/tests/context.test.ts": 528, + "packages/durable-streams/tests/deterministic-id.test.ts": 556, + "packages/durable-streams/tests/divergence-api.test.ts": 536, + "packages/durable-streams/tests/divergence.test.ts": 571, + "packages/durable-streams/tests/durable-each.test.ts": 567, + "packages/durable-streams/tests/durable-position.test.ts": 582, + "packages/durable-streams/tests/durable-run.test.ts": 566, + "packages/durable-streams/tests/ephemeral.test.ts": 551, + "packages/durable-streams/tests/fail-stop.test.ts": 586, + "packages/durable-streams/tests/guard-stream.test.ts": 619, + "packages/durable-streams/tests/live-coordinator.test.ts": 558, + "packages/durable-streams/tests/parse.test.ts": 586, "packages/durable-streams/tests/replay-guard.test.ts": 587, - "packages/durable-streams/tests/replay-index.test.ts": 546, - "packages/durable-streams/tests/retained.test.ts": 530, - "packages/durable-streams/tests/serialize-event.test.ts": 546, - "packages/durable-streams/tests/smoke.test.ts": 540, - "packages/durable-streams/tests/structured-concurrency.test.ts": 611, + "packages/durable-streams/tests/replay-index.test.ts": 534, + "packages/durable-streams/tests/retained.test.ts": 535, + "packages/durable-streams/tests/serialize-event.test.ts": 536, + "packages/durable-streams/tests/smoke.test.ts": 521, + "packages/durable-streams/tests/structured-concurrency.test.ts": 604, "packages/durable-streams/tests/terminal-boundary.test.ts": 577, - "packages/durable-streams/tests/types.test.ts": 522, - "packages/git/tests/api-entrypoint.test.ts": 1280, - "packages/git/tests/git-host-effect.test.ts": 7174, - "packages/git/tests/git.test.ts": 1304, - "packages/git/tests/github-activation.test.ts": 1694, - "packages/git/tests/github-issues.test.ts": 1218, - "packages/git/tests/github-pull-requests.test.ts": 605, - "packages/git/tests/issue-markdown.test.ts": 10086, - "packages/git/tests/issue-records.test.ts": 1178, - "packages/git/tests/module-partition.test.ts": 3239, - "packages/git/tests/plugin.test.ts": 1737, - "packages/git/tests/provider-neutrality.test.ts": 2298, - "packages/git/tests/pull-request-records.test.ts": 1273, - "packages/git/tests/run-composition-lazy.test.ts": 1993, - "packages/git/tests/selection-authentication.test.ts": 497, - "packages/runtime/tests/duration.test.ts": 476, - "packages/runtime/tests/fetch.test.ts": 1115, - "packages/runtime/tests/fs.test.ts": 569, - "packages/runtime/tests/host-files.test.ts": 1317, - "packages/runtime/tests/native-launcher.test.ts": 2885, - "packages/runtime/tests/process.test.ts": 542, - "packages/runtime/tests/service.test.ts": 555, - "packages/test-agent/tests/acp-server.test.ts": 684, - "packages/test-agent/tests/behavior-engine.test.ts": 1616, - "packages/test-agent/tests/bridge.test.ts": 527, - "packages/test-agent/tests/command-lazy.test.ts": 6698, - "packages/test-agent/tests/components.test.ts": 108320, - "packages/test-agent/tests/controller.test.ts": 807, - "packages/test-agent/tests/cross-package-resolution.test.ts": 2949, - "packages/test-agent/tests/native-launch.test.ts": 55467, - "packages/test-agent/tests/net.test.ts": 597, - "packages/test-agent/tests/profile.test.ts": 1185, - "packages/test-agent/tests/protocol.test.ts": 607, - "packages/test-agent/tests/provider.test.ts": 1400, - "packages/test-agent/tests/public-api.test.ts": 1576, - "packages/test-agent/tests/route-slot.test.ts": 510, - "packages/test-agent/tests/smoke.test.ts": 29490, - "packages/test-agent/tests/template.test.ts": 1181, - "packages/test-agent/tests/worker-lifecycle.test.ts": 24412, - "packages/test-support/tests/journal.test.ts": 516, - "packages/testing/tests/agent-composition.test.ts": 1550, - "packages/testing/tests/assert-throws.test.ts": 3272, - "packages/testing/tests/assert.test.ts": 546, - "packages/testing/tests/assertions.test.ts": 3984, - "packages/testing/tests/boundary-contract.test.ts": 2633, - "packages/testing/tests/cli.test.ts": 11610, - "packages/testing/tests/execution-harness.test.ts": 7778, - "packages/testing/tests/replay.test.ts": 3111, - "packages/testing/tests/smoke.test.ts": 3510, - "packages/testing/tests/test-component.test.ts": 4058, - "packages/testing/tests/testing-activation.test.ts": 3530, - "packages/testing/tests/testing-boundary.test.ts": 2114, - "packages/testing/tests/testing-mode.test.ts": 4196, - "packages/testing/tests/use-testing.test.ts": 2982, - "packages/web/tests/assets.test.ts": 505, - "packages/web/tests/client-logic.test.ts": 564, - "packages/web/tests/compile.test.ts": 2013, - "packages/web/tests/component.test.ts": 2622, - "packages/web/tests/declaration.test.ts": 1740, - "packages/web/tests/document.test.ts": 1828, - "packages/web/tests/elicitation.test.ts": 1633, - "packages/web/tests/live-form.test.ts": 1516, - "packages/web/tests/markdown.test.ts": 719, - "packages/web/tests/opener.test.ts": 1551, - "packages/web/tests/page.test.ts": 497, - "packages/web/tests/responder.test.ts": 1899, - "packages/web/tests/server-lifecycle.test.ts": 1696, - "packages/web/tests/server.test.ts": 2090, - "packages/workflow/tests/generated-observations.test.ts": 1528, - "packages/workflow/tests/retained-run.test.ts": 2350, - "packages/workflow/tests/service-denial.test.ts": 536, - "packages/workflow/tests/workflow-bundle.test.ts": 1645, - "packages/workflow/tests/workflow-definition.test.ts": 1368, - "packages/workflow/tests/workflow-fork.test.ts": 1228, - "packages/workflow/tests/workflow-run.test.ts": 2138, - "packages/workflow/tests/workspace-effect.test.ts": 3176, - "packages/workflow/tests/xmd-artifact.test.ts": 3981, - "scripts/tests/adapter-vendor.test.ts": 748, - "scripts/tests/bootstrap-npm-package.test.ts": 8714, - "scripts/tests/bump-version.test.ts": 640, - "scripts/tests/changed-paths.test.ts": 555, - "scripts/tests/ci-workflow.test.ts": 3812, - "scripts/tests/consumer-cycle.test.ts": 695, - "scripts/tests/filesystem-contract-workflow.test.ts": 598, - "scripts/tests/main-green.test.ts": 797, - "scripts/tests/main-health.test.ts": 626, - "scripts/tests/no-module-scoped-registry.test.ts": 1399, - "scripts/tests/no-redundant-test-scope.test.ts": 5451, - "scripts/tests/no-sync-filesystem.test.ts": 5830, - "scripts/tests/no-yield-in-finally.test.ts": 1331, - "scripts/tests/oxlint-policy.test.ts": 11385, - "scripts/tests/packaged-document.test.ts": 636, - "scripts/tests/prefer-effection-operation.test.ts": 6792, - "scripts/tests/prefer-effection-result.test.ts": 4734, - "scripts/tests/publish-workflow-membership.test.ts": 681, - "scripts/tests/review-infrastructure.test.ts": 3482, - "scripts/tests/runtime-exclusions.test.ts": 1041, - "scripts/tests/runtime-tests.test.ts": 585, - "scripts/tests/scope-bound-event-registration.test.ts": 5632, - "scripts/tests/staged-write.test.ts": 551, - "scripts/tests/test-file-discovery.test.ts": 877, - "scripts/tests/test-shards.test.ts": 570, - "scripts/tests/test-weights.test.ts": 762, - "scripts/tests/tracked.test.ts": 541, - "scripts/tests/verify-coordinator.test.ts": 769, - "scripts/tests/version-lockstep.test.ts": 739, - "scripts/tests/web-client-module.test.ts": 541, - "scripts/tests/workspace.test.ts": 522 + "packages/durable-streams/tests/types.test.ts": 479, + "packages/git/tests/api-entrypoint.test.ts": 1319, + "packages/git/tests/git-host-effect.test.ts": 7178, + "packages/git/tests/git.test.ts": 1302, + "packages/git/tests/github-activation.test.ts": 1658, + "packages/git/tests/github-issues.test.ts": 1228, + "packages/git/tests/github-pull-requests.test.ts": 604, + "packages/git/tests/issue-markdown.test.ts": 9924, + "packages/git/tests/issue-records.test.ts": 1197, + "packages/git/tests/module-partition.test.ts": 3241, + "packages/git/tests/plugin.test.ts": 1731, + "packages/git/tests/provider-neutrality.test.ts": 2312, + "packages/git/tests/pull-request-records.test.ts": 1278, + "packages/git/tests/run-composition-lazy.test.ts": 1935, + "packages/git/tests/selection-authentication.test.ts": 499, + "packages/runtime/tests/duration.test.ts": 487, + "packages/runtime/tests/fetch.test.ts": 1112, + "packages/runtime/tests/fs.test.ts": 552, + "packages/runtime/tests/host-files.test.ts": 1286, + "packages/runtime/tests/native-launcher.test.ts": 2872, + "packages/runtime/tests/process.test.ts": 551, + "packages/runtime/tests/service.test.ts": 522, + "packages/test-agent/tests/acp-server.test.ts": 671, + "packages/test-agent/tests/behavior-engine.test.ts": 1586, + "packages/test-agent/tests/bridge.test.ts": 510, + "packages/test-agent/tests/command-lazy.test.ts": 6555, + "packages/test-agent/tests/components.test.ts": 107217, + "packages/test-agent/tests/controller.test.ts": 795, + "packages/test-agent/tests/cross-package-resolution.test.ts": 2908, + "packages/test-agent/tests/native-launch.test.ts": 54180, + "packages/test-agent/tests/net.test.ts": 588, + "packages/test-agent/tests/profile.test.ts": 1219, + "packages/test-agent/tests/protocol.test.ts": 577, + "packages/test-agent/tests/provider.test.ts": 1340, + "packages/test-agent/tests/public-api.test.ts": 1560, + "packages/test-agent/tests/route-slot.test.ts": 481, + "packages/test-agent/tests/smoke.test.ts": 28732, + "packages/test-agent/tests/template.test.ts": 1180, + "packages/test-agent/tests/worker-lifecycle.test.ts": 23743, + "packages/test-support/tests/journal.test.ts": 490, + "packages/testing/tests/agent-composition.test.ts": 1468, + "packages/testing/tests/assert-throws.test.ts": 3147, + "packages/testing/tests/assert.test.ts": 500, + "packages/testing/tests/assertions.test.ts": 3776, + "packages/testing/tests/boundary-contract.test.ts": 2504, + "packages/testing/tests/cli.test.ts": 10930, + "packages/testing/tests/execution-harness.test.ts": 7583, + "packages/testing/tests/replay.test.ts": 3031, + "packages/testing/tests/smoke.test.ts": 3238, + "packages/testing/tests/test-component.test.ts": 3984, + "packages/testing/tests/testing-activation.test.ts": 3433, + "packages/testing/tests/testing-boundary.test.ts": 2039, + "packages/testing/tests/testing-mode.test.ts": 4040, + "packages/testing/tests/use-testing.test.ts": 2862, + "packages/web/tests/assets.test.ts": 478, + "packages/web/tests/client-logic.test.ts": 543, + "packages/web/tests/compile.test.ts": 2041, + "packages/web/tests/component.test.ts": 2472, + "packages/web/tests/declaration.test.ts": 1651, + "packages/web/tests/document.test.ts": 1811, + "packages/web/tests/elicitation.test.ts": 1657, + "packages/web/tests/live-form.test.ts": 1487, + "packages/web/tests/markdown.test.ts": 686, + "packages/web/tests/opener.test.ts": 1477, + "packages/web/tests/page.test.ts": 480, + "packages/web/tests/responder.test.ts": 1832, + "packages/web/tests/server-lifecycle.test.ts": 1727, + "packages/web/tests/server.test.ts": 2061, + "packages/workflow/tests/generated-observations.test.ts": 1458, + "packages/workflow/tests/retained-run.test.ts": 2195, + "packages/workflow/tests/service-denial.test.ts": 508, + "packages/workflow/tests/workflow-bundle.test.ts": 1638, + "packages/workflow/tests/workflow-definition.test.ts": 1319, + "packages/workflow/tests/workflow-fork.test.ts": 1209, + "packages/workflow/tests/workflow-run.test.ts": 2081, + "packages/workflow/tests/workspace-effect.test.ts": 3067, + "packages/workflow/tests/xmd-artifact.test.ts": 3662, + "scripts/tests/adapter-vendor.test.ts": 721, + "scripts/tests/bootstrap-npm-package.test.ts": 8295, + "scripts/tests/bump-version.test.ts": 611, + "scripts/tests/changed-paths.test.ts": 512, + "scripts/tests/ci-workflow.test.ts": 3638, + "scripts/tests/consumer-cycle.test.ts": 694, + "scripts/tests/filesystem-contract-workflow.test.ts": 587, + "scripts/tests/main-green.test.ts": 771, + "scripts/tests/main-health.test.ts": 619, + "scripts/tests/no-module-scoped-registry.test.ts": 1311, + "scripts/tests/no-redundant-test-scope.test.ts": 5343, + "scripts/tests/no-sync-filesystem.test.ts": 5664, + "scripts/tests/no-yield-in-finally.test.ts": 1317, + "scripts/tests/oxlint-policy.test.ts": 10912, + "scripts/tests/packaged-document.test.ts": 619, + "scripts/tests/prefer-effection-operation.test.ts": 6307, + "scripts/tests/prefer-effection-result.test.ts": 4550, + "scripts/tests/publish-workflow-membership.test.ts": 671, + "scripts/tests/review-infrastructure.test.ts": 3238, + "scripts/tests/runtime-exclusions.test.ts": 988, + "scripts/tests/runtime-tests.test.ts": 546, + "scripts/tests/scope-bound-event-registration.test.ts": 5495, + "scripts/tests/staged-write.test.ts": 537, + "scripts/tests/test-file-discovery.test.ts": 845, + "scripts/tests/test-shards.test.ts": 534, + "scripts/tests/test-weights.test.ts": 713, + "scripts/tests/tracked.test.ts": 542, + "scripts/tests/verify-coordinator.test.ts": 717, + "scripts/tests/version-lockstep.test.ts": 698, + "scripts/tests/web-client-module.test.ts": 530, + "scripts/tests/workspace.test.ts": 506 }, "bun": { - "packages/acp/tests/acceptance.test.ts": 848, - "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4324, - "packages/acp/tests/acpx-materialization.test.ts": 6530, - "packages/acp/tests/acpx-transient-env.test.ts": 278, - "packages/acp/tests/adapter-materialization.test.ts": 72466, - "packages/acp/tests/adapter-protocol.test.ts": 54696, - "packages/acp/tests/codex-surfaces.test.ts": 128, - "packages/acp/tests/native-launch.test.ts": 1014, - "packages/acp/tests/permission-bridge.test.ts": 376, - "packages/acp/tests/provider.test.ts": 1144, - "packages/acp/tests/serial-queue.test.ts": 113, - "packages/acp/tests/session-key.test.ts": 66, + "packages/acp/tests/acceptance.test.ts": 881, + "packages/acp/tests/acpx-checkpoint-meta.test.ts": 4309, + "packages/acp/tests/acpx-materialization.test.ts": 6504, + "packages/acp/tests/acpx-transient-env.test.ts": 255, + "packages/acp/tests/adapter-materialization.test.ts": 70550, + "packages/acp/tests/adapter-protocol.test.ts": 53397, + "packages/acp/tests/codex-surfaces.test.ts": 124, + "packages/acp/tests/native-launch.test.ts": 1004, + "packages/acp/tests/permission-bridge.test.ts": 367, + "packages/acp/tests/provider.test.ts": 1110, + "packages/acp/tests/serial-queue.test.ts": 65, + "packages/acp/tests/session-key.test.ts": 70, "packages/acp/tests/terminal-screen.test.ts": 92, - "packages/cli/tests/agent-adapters.test.ts": 1259, - "packages/cli/tests/agent-cli.test.ts": 21242, - "packages/cli/tests/agent-config.test.ts": 31, - "packages/cli/tests/agent-options-cli.test.ts": 8066, - "packages/cli/tests/agent-options.test.ts": 34, - "packages/cli/tests/agent-session-coordinator.test.ts": 395, - "packages/cli/tests/cli-help.test.ts": 16491, - "packages/cli/tests/command.test.ts": 8086, - "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 3158, - "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 108257, - "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2354, - "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 5383, - "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 4959, - "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 9210, - "packages/cli/tests/evaluate-component.test.ts": 8317, - "packages/cli/tests/fetch-cli.test.ts": 2541, - "packages/cli/tests/file-stream.test.ts": 60, - "packages/cli/tests/inline-cli.test.ts": 34088, - "packages/cli/tests/launch-timeout.test.ts": 10106, - "packages/cli/tests/packaged-document.test.ts": 496, - "packages/cli/tests/plan-args.test.ts": 40, - "packages/cli/tests/plan-cli.test.ts": 60861, - "packages/cli/tests/plan-command-document.test.ts": 5509, - "packages/cli/tests/plan-component.test.ts": 9021, - "packages/cli/tests/plan-host-acts.test.ts": 1232, - "packages/cli/tests/plan.test.ts": 12888, - "packages/cli/tests/plugin-cli.test.ts": 40868, - "packages/cli/tests/plugin-host.test.ts": 2176, - "packages/cli/tests/plugin-modules.test.ts": 39, - "packages/cli/tests/plugin-selection.test.ts": 38, - "packages/cli/tests/process-retention.test.ts": 2572, - "packages/cli/tests/props-cli.test.ts": 29734, - "packages/cli/tests/props-schema.test.ts": 441, - "packages/cli/tests/props-sources.test.ts": 145, - "packages/cli/tests/repl-boundaries.test.ts": 1250, - "packages/cli/tests/repl-composition.test.ts": 148, - "packages/cli/tests/repl-execution.test.ts": 2066, - "packages/cli/tests/repl-journey.test.ts": 10452, - "packages/cli/tests/repl-model.test.ts": 1788, - "packages/cli/tests/repl-route.test.ts": 1362, - "packages/cli/tests/repl-terminal.test.ts": 870, - "packages/cli/tests/run-composition.test.ts": 3267, - "packages/cli/tests/run-deadline.test.ts": 2464, - "packages/cli/tests/run-profile.test.ts": 329, - "packages/cli/tests/run-timeouts.test.ts": 11631, - "packages/cli/tests/secret-detection-cli.test.ts": 18885, - "packages/cli/tests/service-document.test.ts": 1561, - "packages/cli/tests/service-host.test.ts": 1429, - "packages/cli/tests/session-launch-cli.test.ts": 5781, - "packages/cli/tests/stdin-cli.test.ts": 32151, - "packages/cli/tests/stdout-delivery.test.ts": 53, - "packages/cli/tests/syntax-cli.test.ts": 34163, - "packages/cli/tests/targets-cli.test.ts": 22514, - "packages/cli/tests/test-root-profile.test.ts": 3779, - "packages/cli/tests/test-target.test.ts": 38358, - "packages/cli/tests/testing-activation.test.ts": 623, - "packages/cli/tests/testing-execution-host.test.ts": 51169, - "packages/cli/tests/upgrade-cli.test.ts": 19117, - "packages/cli/tests/upgrade-command-document.test.ts": 3715, - "packages/cli/tests/upgrade-output.test.ts": 849, - "packages/cli/tests/value-root.test.ts": 16527, - "packages/cli/tests/verbose-component.test.ts": 871, - "packages/cli/tests/workflow-declaration.test.ts": 586, - "packages/cli/tests/workflow-host.test.ts": 4412, + "packages/cli/tests/agent-adapters.test.ts": 1152, + "packages/cli/tests/agent-cli.test.ts": 21790, + "packages/cli/tests/agent-config.test.ts": 30, + "packages/cli/tests/agent-options-cli.test.ts": 8164, + "packages/cli/tests/agent-options.test.ts": 33, + "packages/cli/tests/agent-session-coordinator.test.ts": 396, + "packages/cli/tests/cli-help.test.ts": 15775, + "packages/cli/tests/command.test.ts": 7938, + "packages/cli/tests/document-suites/inline/inline-markdown.test.ts": 2930, + "packages/cli/tests/document-suites/plan/plan-markdown.test.ts": 106383, + "packages/cli/tests/document-suites/props/props-markdown.test.ts": 2384, + "packages/cli/tests/document-suites/syntax/syntax-markdown.test.ts": 5311, + "packages/cli/tests/document-suites/targets/targets-markdown.test.ts": 5016, + "packages/cli/tests/document-suites/verbose/verbose-markdown.test.ts": 9107, + "packages/cli/tests/evaluate-component.test.ts": 8013, + "packages/cli/tests/fetch-cli.test.ts": 2379, + "packages/cli/tests/file-stream.test.ts": 56, + "packages/cli/tests/inline-cli.test.ts": 32942, + "packages/cli/tests/launch-timeout.test.ts": 10096, + "packages/cli/tests/packaged-document.test.ts": 465, + "packages/cli/tests/plan-args.test.ts": 39, + "packages/cli/tests/plan-cli.test.ts": 61713, + "packages/cli/tests/plan-command-document.test.ts": 5437, + "packages/cli/tests/plan-component.test.ts": 8825, + "packages/cli/tests/plan-host-acts.test.ts": 1167, + "packages/cli/tests/plan.test.ts": 12886, + "packages/cli/tests/plugin-cli.test.ts": 39744, + "packages/cli/tests/plugin-host.test.ts": 2006, + "packages/cli/tests/plugin-modules.test.ts": 36, + "packages/cli/tests/plugin-selection.test.ts": 36, + "packages/cli/tests/process-retention.test.ts": 2607, + "packages/cli/tests/props-cli.test.ts": 29136, + "packages/cli/tests/props-schema.test.ts": 422, + "packages/cli/tests/props-sources.test.ts": 128, + "packages/cli/tests/repl-boundaries.test.ts": 1229, + "packages/cli/tests/repl-composition.test.ts": 145, + "packages/cli/tests/repl-execution.test.ts": 1935, + "packages/cli/tests/repl-journey.test.ts": 10392, + "packages/cli/tests/repl-model.test.ts": 1784, + "packages/cli/tests/repl-route.test.ts": 1261, + "packages/cli/tests/repl-terminal.test.ts": 821, + "packages/cli/tests/run-composition.test.ts": 3038, + "packages/cli/tests/run-deadline.test.ts": 2382, + "packages/cli/tests/run-profile.test.ts": 312, + "packages/cli/tests/run-timeouts.test.ts": 11377, + "packages/cli/tests/secret-detection-cli.test.ts": 18381, + "packages/cli/tests/service-document.test.ts": 1532, + "packages/cli/tests/service-host.test.ts": 1390, + "packages/cli/tests/session-launch-cli.test.ts": 6066, + "packages/cli/tests/stdin-cli.test.ts": 31362, + "packages/cli/tests/stdout-delivery.test.ts": 52, + "packages/cli/tests/syntax-cli.test.ts": 33188, + "packages/cli/tests/targets-cli.test.ts": 21933, + "packages/cli/tests/test-root-profile.test.ts": 3814, + "packages/cli/tests/test-target.test.ts": 36435, + "packages/cli/tests/testing-activation.test.ts": 578, + "packages/cli/tests/testing-execution-host.test.ts": 49246, + "packages/cli/tests/upgrade-cli.test.ts": 18227, + "packages/cli/tests/upgrade-command-document.test.ts": 3569, + "packages/cli/tests/upgrade-output.test.ts": 831, + "packages/cli/tests/value-root.test.ts": 15949, + "packages/cli/tests/verbose-component.test.ts": 820, + "packages/cli/tests/workflow-declaration.test.ts": 538, + "packages/cli/tests/workflow-host.test.ts": 4406, "packages/code-review-agent/tests/doctor.test.ts": 30, - "packages/code-review-agent/tests/parse-diagnostics.test.ts": 44, - "packages/code-review-agent/tests/parse-diff.test.ts": 40, - "packages/code-review-agent/tests/parse-doctor.test.ts": 305, - "packages/code-review-agent/tests/parse-oxlint.test.ts": 31, - "packages/code-review-agent/tests/policy.test.ts": 32, - "packages/core/tests/agent-api.test.ts": 39, - "packages/core/tests/agent-components.test.ts": 1350, - "packages/core/tests/agent-function-components.test.ts": 3753, - "packages/core/tests/agent-session-launch.test.ts": 3650, + "packages/code-review-agent/tests/parse-diagnostics.test.ts": 39, + "packages/code-review-agent/tests/parse-diff.test.ts": 36, + "packages/code-review-agent/tests/parse-doctor.test.ts": 297, + "packages/code-review-agent/tests/parse-oxlint.test.ts": 30, + "packages/code-review-agent/tests/policy.test.ts": 34, + "packages/core/tests/agent-api.test.ts": 38, + "packages/core/tests/agent-components.test.ts": 1287, + "packages/core/tests/agent-function-components.test.ts": 3532, + "packages/core/tests/agent-session-launch.test.ts": 3433, "packages/core/tests/agent-session-placement.test.ts": 38, - "packages/core/tests/agent-session-use.test.ts": 32, - "packages/core/tests/agent-stack.test.ts": 739, - "packages/core/tests/answer-identity.test.ts": 44, - "packages/core/tests/answers-component.test.ts": 1492, - "packages/core/tests/answers-expansion-recursion.test.ts": 342, - "packages/core/tests/capture-errors.test.ts": 622, - "packages/core/tests/capture-props.test.ts": 353, - "packages/core/tests/cli-journal.test.ts": 10935, - "packages/core/tests/code-block-component.test.ts": 2418, - "packages/core/tests/compiler-boundary.test.ts": 350, + "packages/core/tests/agent-session-use.test.ts": 47, + "packages/core/tests/agent-stack.test.ts": 785, + "packages/core/tests/all.test.ts": 2166, + "packages/core/tests/answer-identity.test.ts": 68, + "packages/core/tests/answers-component.test.ts": 1361, + "packages/core/tests/answers-expansion-recursion.test.ts": 327, + "packages/core/tests/capture-errors.test.ts": 569, + "packages/core/tests/capture-props.test.ts": 337, + "packages/core/tests/cli-journal.test.ts": 10559, + "packages/core/tests/code-block-component.test.ts": 2336, + "packages/core/tests/compiler-boundary.test.ts": 352, "packages/core/tests/component-api.test.ts": 57, - "packages/core/tests/component-registration.test.ts": 1189, - "packages/core/tests/component-returns.test.ts": 1729, - "packages/core/tests/component-schema-conformance.test.ts": 588, - "packages/core/tests/config-api.test.ts": 353, - "packages/core/tests/construct-error-observation.test.ts": 755, - "packages/core/tests/contextual-cwd.test.ts": 1556, - "packages/core/tests/daemon-integration.test.ts": 1057, + "packages/core/tests/component-registration.test.ts": 1096, + "packages/core/tests/component-returns.test.ts": 1602, + "packages/core/tests/component-schema-conformance.test.ts": 553, + "packages/core/tests/config-api.test.ts": 339, + "packages/core/tests/construct-error-observation.test.ts": 710, + "packages/core/tests/contextual-cwd.test.ts": 1531, + "packages/core/tests/daemon-integration.test.ts": 1026, "packages/core/tests/daemon.test.ts": 72, - "packages/core/tests/declared-markdown-component.test.ts": 2229, - "packages/core/tests/document-output-api.test.ts": 39, - "packages/core/tests/document-target-execution.test.ts": 3709, - "packages/core/tests/document-targets.test.ts": 460, - "packages/core/tests/document-validation.test.ts": 2283, - "packages/core/tests/documentation-index.test.ts": 168, - "packages/core/tests/each.test.ts": 444, - "packages/core/tests/elicit-component.test.ts": 1213, - "packages/core/tests/ephemeral-service.test.ts": 862, - "packages/core/tests/eval-bindings.test.ts": 438, - "packages/core/tests/eval-context.test.ts": 94, - "packages/core/tests/eval-durable.test.ts": 456, - "packages/core/tests/eval-error-mode.test.ts": 722, - "packages/core/tests/eval-interpolate.test.ts": 38, - "packages/core/tests/eval-middleware.test.ts": 87, - "packages/core/tests/eval-persist.test.ts": 525, - "packages/core/tests/eval-return.test.ts": 446, - "packages/core/tests/eval-scope.test.ts": 354, - "packages/core/tests/eval-timeout.test.ts": 461, - "packages/core/tests/eval-transform.test.ts": 67, - "packages/core/tests/evaluate-component.test.ts": 2725, - "packages/core/tests/evaluate-loaded-copy.test.ts": 543, - "packages/core/tests/evaluate-provider-lifetime.test.ts": 677, - "packages/core/tests/evaluation-profile.test.ts": 197, - "packages/core/tests/exec-timeout.test.ts": 522, - "packages/core/tests/execute.test.ts": 1101, - "packages/core/tests/execution-protocol.test.ts": 1668, - "packages/core/tests/expand.test.ts": 1284, - "packages/core/tests/expansion-identity.test.ts": 1262, - "packages/core/tests/expansion-metadata.test.ts": 354, - "packages/core/tests/expression-props.test.ts": 904, - "packages/core/tests/fail-component.test.ts": 778, - "packages/core/tests/failure-printing.test.ts": 712, - "packages/core/tests/fatal-cause.test.ts": 195, - "packages/core/tests/fetch-component.test.ts": 1808, - "packages/core/tests/file-component.test.ts": 2448, - "packages/core/tests/file-delete-component.test.ts": 655, - "packages/core/tests/files-fatal.test.ts": 1553, - "packages/core/tests/foreground-exec.test.ts": 1280, + "packages/core/tests/declared-markdown-component.test.ts": 1988, + "packages/core/tests/document-output-api.test.ts": 38, + "packages/core/tests/document-target-execution.test.ts": 3473, + "packages/core/tests/document-targets.test.ts": 407, + "packages/core/tests/document-validation.test.ts": 2083, + "packages/core/tests/documentation-index.test.ts": 167, + "packages/core/tests/each.test.ts": 421, + "packages/core/tests/elicit-component.test.ts": 1083, + "packages/core/tests/ephemeral-service.test.ts": 831, + "packages/core/tests/eval-bindings.test.ts": 401, + "packages/core/tests/eval-context.test.ts": 86, + "packages/core/tests/eval-durable.test.ts": 426, + "packages/core/tests/eval-error-mode.test.ts": 690, + "packages/core/tests/eval-interpolate.test.ts": 39, + "packages/core/tests/eval-middleware.test.ts": 76, + "packages/core/tests/eval-persist.test.ts": 506, + "packages/core/tests/eval-return.test.ts": 424, + "packages/core/tests/eval-scope.test.ts": 334, + "packages/core/tests/eval-timeout.test.ts": 433, + "packages/core/tests/eval-transform.test.ts": 74, + "packages/core/tests/evaluate-component.test.ts": 2514, + "packages/core/tests/evaluate-loaded-copy.test.ts": 517, + "packages/core/tests/evaluate-provider-lifetime.test.ts": 576, + "packages/core/tests/evaluation-profile.test.ts": 193, + "packages/core/tests/exec-timeout.test.ts": 502, + "packages/core/tests/execute.test.ts": 993, + "packages/core/tests/execution-protocol.test.ts": 1607, + "packages/core/tests/expand.test.ts": 1247, + "packages/core/tests/expansion-identity.test.ts": 1237, + "packages/core/tests/expansion-metadata.test.ts": 333, + "packages/core/tests/expression-props.test.ts": 891, + "packages/core/tests/fail-component.test.ts": 754, + "packages/core/tests/failure-printing.test.ts": 683, + "packages/core/tests/fatal-cause.test.ts": 188, + "packages/core/tests/fetch-component.test.ts": 1772, + "packages/core/tests/file-component.test.ts": 2425, + "packages/core/tests/file-delete-component.test.ts": 620, + "packages/core/tests/files-fatal.test.ts": 1468, + "packages/core/tests/foreground-exec.test.ts": 1236, "packages/core/tests/frontmatter.test.ts": 48, - "packages/core/tests/function-components.test.ts": 953, - "packages/core/tests/generated-composition.test.ts": 1238, - "packages/core/tests/generated-xmd.test.ts": 2487, - "packages/core/tests/glob-api.test.ts": 102, - "packages/core/tests/glob-component.test.ts": 1268, - "packages/core/tests/guarded-journal.test.ts": 456, - "packages/core/tests/has-content.test.ts": 283, - "packages/core/tests/heal.test.ts": 267, - "packages/core/tests/if.test.ts": 859, - "packages/core/tests/inline-root.test.ts": 525, - "packages/core/tests/invocation-failures.test.ts": 107, - "packages/core/tests/invocation-identity.test.ts": 833, - "packages/core/tests/invocation-scope.test.ts": 551, - "packages/core/tests/journal-source-position.test.ts": 564, - "packages/core/tests/json-component.test.ts": 2397, - "packages/core/tests/json.test.ts": 34, - "packages/core/tests/let.test.ts": 431, - "packages/core/tests/loop.test.ts": 1360, - "packages/core/tests/named-slots.test.ts": 1386, - "packages/core/tests/output-error-mode.test.ts": 1660, - "packages/core/tests/output-normalize.test.ts": 42, + "packages/core/tests/function-components.test.ts": 912, + "packages/core/tests/generated-composition.test.ts": 1135, + "packages/core/tests/generated-xmd.test.ts": 2338, + "packages/core/tests/glob-api.test.ts": 99, + "packages/core/tests/glob-component.test.ts": 1236, + "packages/core/tests/guarded-journal.test.ts": 445, + "packages/core/tests/has-content.test.ts": 270, + "packages/core/tests/heal.test.ts": 266, + "packages/core/tests/if.test.ts": 827, + "packages/core/tests/inline-root.test.ts": 493, + "packages/core/tests/invocation-failures.test.ts": 104, + "packages/core/tests/invocation-identity.test.ts": 825, + "packages/core/tests/invocation-scope.test.ts": 550, + "packages/core/tests/journal-source-position.test.ts": 599, + "packages/core/tests/json-component.test.ts": 2337, + "packages/core/tests/json.test.ts": 35, + "packages/core/tests/let.test.ts": 423, + "packages/core/tests/loop.test.ts": 1241, + "packages/core/tests/named-slots.test.ts": 1274, + "packages/core/tests/output-error-mode.test.ts": 1565, + "packages/core/tests/output-normalize.test.ts": 40, "packages/core/tests/output-terminal.test.ts": 161, - "packages/core/tests/parse-components.test.ts": 607, - "packages/core/tests/plan-response.test.ts": 88, - "packages/core/tests/plugin-api.test.ts": 49, - "packages/core/tests/plugin.test.ts": 192, - "packages/core/tests/props-binding.test.ts": 552, - "packages/core/tests/protected-content.test.ts": 55, - "packages/core/tests/registered-printed-errors.test.ts": 212, - "packages/core/tests/replay-stream.test.ts": 39, - "packages/core/tests/retain.test.ts": 482, - "packages/core/tests/root-composition.test.ts": 587, - "packages/core/tests/root-props.test.ts": 594, - "packages/core/tests/root-provider.test.ts": 682, - "packages/core/tests/sample-component.test.ts": 1135, - "packages/core/tests/scanner.test.ts": 79, - "packages/core/tests/scope-local.test.ts": 37, - "packages/core/tests/secret-detection.test.ts": 1155, - "packages/core/tests/secret-files.test.ts": 135, - "packages/core/tests/secret-rules.test.ts": 155, - "packages/core/tests/secret-scanner-baseline.test.ts": 148, - "packages/core/tests/secret-scanner.test.ts": 101, - "packages/core/tests/source-position.test.ts": 348, - "packages/core/tests/state-ownership.test.ts": 407, - "packages/core/tests/streaming-emission.test.ts": 326, - "packages/core/tests/switch.test.ts": 853, - "packages/core/tests/syntax-catalog.test.ts": 903, - "packages/core/tests/syntax-component.test.ts": 1824, - "packages/core/tests/temp-dir.test.ts": 2847, - "packages/core/tests/temp-file-compiler.test.ts": 81, - "packages/core/tests/text-interpolation.test.ts": 245, - "packages/core/tests/try-content.test.ts": 283, - "packages/core/tests/unused-in-diff.test.ts": 501, - "packages/core/tests/validate.test.ts": 332, - "packages/core/tests/validation-integration.test.ts": 378, - "packages/core/tests/workflow-component-bundle.test.ts": 736, - "packages/durable-streams/tests/context.test.ts": 40, - "packages/durable-streams/tests/deterministic-id.test.ts": 72, - "packages/durable-streams/tests/divergence-api.test.ts": 47, - "packages/durable-streams/tests/divergence.test.ts": 57, - "packages/durable-streams/tests/durable-each.test.ts": 71, - "packages/durable-streams/tests/durable-position.test.ts": 59, + "packages/core/tests/parse-components.test.ts": 664, + "packages/core/tests/plan-response.test.ts": 96, + "packages/core/tests/plugin-api.test.ts": 47, + "packages/core/tests/plugin.test.ts": 191, + "packages/core/tests/props-binding.test.ts": 533, + "packages/core/tests/protected-content.test.ts": 49, + "packages/core/tests/registered-printed-errors.test.ts": 214, + "packages/core/tests/replay-stream.test.ts": 35, + "packages/core/tests/retain.test.ts": 483, + "packages/core/tests/root-composition.test.ts": 572, + "packages/core/tests/root-props.test.ts": 569, + "packages/core/tests/root-provider.test.ts": 580, + "packages/core/tests/sample-component.test.ts": 1104, + "packages/core/tests/scanner.test.ts": 77, + "packages/core/tests/scope-local.test.ts": 35, + "packages/core/tests/secret-detection.test.ts": 1148, + "packages/core/tests/secret-files.test.ts": 128, + "packages/core/tests/secret-rules.test.ts": 150, + "packages/core/tests/secret-scanner-baseline.test.ts": 152, + "packages/core/tests/secret-scanner.test.ts": 103, + "packages/core/tests/source-position.test.ts": 376, + "packages/core/tests/state-ownership.test.ts": 404, + "packages/core/tests/streaming-emission.test.ts": 332, + "packages/core/tests/switch.test.ts": 817, + "packages/core/tests/syntax-catalog.test.ts": 894, + "packages/core/tests/syntax-component.test.ts": 1764, + "packages/core/tests/temp-dir.test.ts": 2800, + "packages/core/tests/temp-file-compiler.test.ts": 74, + "packages/core/tests/text-interpolation.test.ts": 229, + "packages/core/tests/try-content.test.ts": 270, + "packages/core/tests/unused-in-diff.test.ts": 502, + "packages/core/tests/validate.test.ts": 322, + "packages/core/tests/validation-integration.test.ts": 366, + "packages/core/tests/workflow-component-bundle.test.ts": 706, + "packages/durable-streams/tests/context.test.ts": 35, + "packages/durable-streams/tests/deterministic-id.test.ts": 68, + "packages/durable-streams/tests/divergence-api.test.ts": 46, + "packages/durable-streams/tests/divergence.test.ts": 56, + "packages/durable-streams/tests/durable-each.test.ts": 69, + "packages/durable-streams/tests/durable-position.test.ts": 58, "packages/durable-streams/tests/durable-run.test.ts": 65, - "packages/durable-streams/tests/ephemeral.test.ts": 63, - "packages/durable-streams/tests/fail-stop.test.ts": 80, - "packages/durable-streams/tests/guard-stream.test.ts": 108, + "packages/durable-streams/tests/ephemeral.test.ts": 60, + "packages/durable-streams/tests/fail-stop.test.ts": 73, + "packages/durable-streams/tests/guard-stream.test.ts": 109, "packages/durable-streams/tests/live-coordinator.test.ts": 59, - "packages/durable-streams/tests/parse.test.ts": 50, - "packages/durable-streams/tests/replay-guard.test.ts": 75, - "packages/durable-streams/tests/replay-index.test.ts": 46, - "packages/durable-streams/tests/retained.test.ts": 103, - "packages/durable-streams/tests/serialize-event.test.ts": 52, - "packages/durable-streams/tests/smoke.test.ts": 34, - "packages/durable-streams/tests/structured-concurrency.test.ts": 97, - "packages/durable-streams/tests/terminal-boundary.test.ts": 82, - "packages/durable-streams/tests/types.test.ts": 29, - "packages/git/tests/api-entrypoint.test.ts": 311, - "packages/git/tests/git-host-effect.test.ts": 6642, - "packages/git/tests/git.test.ts": 375, - "packages/git/tests/github-activation.test.ts": 745, - "packages/git/tests/github-issues.test.ts": 326, - "packages/git/tests/github-pull-requests.test.ts": 83, - "packages/git/tests/issue-markdown.test.ts": 8625, + "packages/durable-streams/tests/parse.test.ts": 51, + "packages/durable-streams/tests/replay-guard.test.ts": 70, + "packages/durable-streams/tests/replay-index.test.ts": 48, + "packages/durable-streams/tests/retained.test.ts": 44, + "packages/durable-streams/tests/serialize-event.test.ts": 41, + "packages/durable-streams/tests/smoke.test.ts": 33, + "packages/durable-streams/tests/structured-concurrency.test.ts": 94, + "packages/durable-streams/tests/terminal-boundary.test.ts": 77, + "packages/durable-streams/tests/types.test.ts": 30, + "packages/git/tests/api-entrypoint.test.ts": 306, + "packages/git/tests/git-host-effect.test.ts": 6749, + "packages/git/tests/git.test.ts": 376, + "packages/git/tests/github-activation.test.ts": 697, + "packages/git/tests/github-issues.test.ts": 317, + "packages/git/tests/github-pull-requests.test.ts": 100, + "packages/git/tests/issue-markdown.test.ts": 8692, "packages/git/tests/issue-records.test.ts": 314, - "packages/git/tests/module-partition.test.ts": 1952, - "packages/git/tests/plugin.test.ts": 855, - "packages/git/tests/provider-neutrality.test.ts": 1105, - "packages/git/tests/pull-request-records.test.ts": 335, - "packages/git/tests/run-composition-lazy.test.ts": 827, + "packages/git/tests/module-partition.test.ts": 1883, + "packages/git/tests/plugin.test.ts": 799, + "packages/git/tests/provider-neutrality.test.ts": 1090, + "packages/git/tests/pull-request-records.test.ts": 332, + "packages/git/tests/run-composition-lazy.test.ts": 785, "packages/git/tests/selection-authentication.test.ts": 39, "packages/runtime/tests/duration.test.ts": 30, - "packages/runtime/tests/fetch.test.ts": 613, + "packages/runtime/tests/fetch.test.ts": 620, "packages/runtime/tests/fs.test.ts": 78, - "packages/runtime/tests/host-files.test.ts": 737, - "packages/runtime/tests/native-launcher.test.ts": 2414, - "packages/runtime/tests/process.test.ts": 89, - "packages/runtime/tests/service.test.ts": 67, - "packages/test-agent/tests/acp-server.test.ts": 109, - "packages/test-agent/tests/behavior-engine.test.ts": 695, + "packages/runtime/tests/host-files.test.ts": 732, + "packages/runtime/tests/native-launcher.test.ts": 2404, + "packages/runtime/tests/process.test.ts": 82, + "packages/runtime/tests/service.test.ts": 66, + "packages/test-agent/tests/acp-server.test.ts": 102, + "packages/test-agent/tests/behavior-engine.test.ts": 662, "packages/test-agent/tests/bridge.test.ts": 68, - "packages/test-agent/tests/command-lazy.test.ts": 3360, - "packages/test-agent/tests/components.test.ts": 53390, - "packages/test-agent/tests/controller.test.ts": 210, - "packages/test-agent/tests/cross-package-resolution.test.ts": 1966, - "packages/test-agent/tests/native-launch.test.ts": 24823, - "packages/test-agent/tests/net.test.ts": 109, - "packages/test-agent/tests/profile.test.ts": 305, - "packages/test-agent/tests/protocol.test.ts": 60, - "packages/test-agent/tests/provider.test.ts": 442, - "packages/test-agent/tests/public-api.test.ts": 483, + "packages/test-agent/tests/command-lazy.test.ts": 3306, + "packages/test-agent/tests/components.test.ts": 52683, + "packages/test-agent/tests/controller.test.ts": 197, + "packages/test-agent/tests/cross-package-resolution.test.ts": 1798, + "packages/test-agent/tests/native-launch.test.ts": 24490, + "packages/test-agent/tests/net.test.ts": 110, + "packages/test-agent/tests/profile.test.ts": 321, + "packages/test-agent/tests/protocol.test.ts": 61, + "packages/test-agent/tests/provider.test.ts": 405, + "packages/test-agent/tests/public-api.test.ts": 476, "packages/test-agent/tests/route-slot.test.ts": 59, - "packages/test-agent/tests/smoke.test.ts": 15697, - "packages/test-agent/tests/template.test.ts": 303, - "packages/test-agent/tests/worker-lifecycle.test.ts": 9536, - "packages/test-support/tests/journal.test.ts": 43, - "packages/testing/tests/agent-composition.test.ts": 629, - "packages/testing/tests/assert-throws.test.ts": 2381, - "packages/testing/tests/assert.test.ts": 58, - "packages/testing/tests/assertions.test.ts": 3069, - "packages/testing/tests/boundary-contract.test.ts": 1670, - "packages/testing/tests/cli.test.ts": 5153, - "packages/testing/tests/execution-harness.test.ts": 6520, - "packages/testing/tests/replay.test.ts": 2317, - "packages/testing/tests/smoke.test.ts": 1858, - "packages/testing/tests/test-component.test.ts": 3031, - "packages/testing/tests/testing-activation.test.ts": 2654, - "packages/testing/tests/testing-boundary.test.ts": 1131, - "packages/testing/tests/testing-mode.test.ts": 3241, - "packages/testing/tests/use-testing.test.ts": 1882, - "packages/web/tests/assets.test.ts": 33, - "packages/web/tests/client-logic.test.ts": 38, - "packages/web/tests/compile.test.ts": 900, - "packages/web/tests/component.test.ts": 1465, - "packages/web/tests/declaration.test.ts": 687, - "packages/web/tests/document.test.ts": 794, - "packages/web/tests/elicitation.test.ts": 560, - "packages/web/tests/live-form.test.ts": 508, - "packages/web/tests/markdown.test.ts": 135, - "packages/web/tests/opener.test.ts": 505, - "packages/web/tests/page.test.ts": 34, - "packages/web/tests/responder.test.ts": 862, - "packages/web/tests/server-lifecycle.test.ts": 658, - "packages/web/tests/server.test.ts": 1077, - "packages/workflow/tests/generated-observations.test.ts": 540, - "packages/workflow/tests/retained-run.test.ts": 1212, - "packages/workflow/tests/service-denial.test.ts": 64, - "packages/workflow/tests/workflow-bundle.test.ts": 730, - "packages/workflow/tests/workflow-definition.test.ts": 359, - "packages/workflow/tests/workflow-fork.test.ts": 312, - "packages/workflow/tests/workflow-run.test.ts": 1068, - "packages/workflow/tests/workspace-effect.test.ts": 1523, - "scripts/tests/adapter-vendor.test.ts": 162, - "scripts/tests/bootstrap-npm-package.test.ts": 5570, + "packages/test-agent/tests/smoke.test.ts": 15508, + "packages/test-agent/tests/template.test.ts": 295, + "packages/test-agent/tests/worker-lifecycle.test.ts": 9251, + "packages/test-support/tests/journal.test.ts": 41, + "packages/testing/tests/agent-composition.test.ts": 610, + "packages/testing/tests/assert-throws.test.ts": 2263, + "packages/testing/tests/assert.test.ts": 56, + "packages/testing/tests/assertions.test.ts": 2965, + "packages/testing/tests/boundary-contract.test.ts": 1659, + "packages/testing/tests/cli.test.ts": 4908, + "packages/testing/tests/execution-harness.test.ts": 6397, + "packages/testing/tests/replay.test.ts": 2206, + "packages/testing/tests/smoke.test.ts": 1784, + "packages/testing/tests/test-component.test.ts": 2982, + "packages/testing/tests/testing-activation.test.ts": 2597, + "packages/testing/tests/testing-boundary.test.ts": 1088, + "packages/testing/tests/testing-mode.test.ts": 3165, + "packages/testing/tests/use-testing.test.ts": 1880, + "packages/web/tests/assets.test.ts": 30, + "packages/web/tests/client-logic.test.ts": 37, + "packages/web/tests/compile.test.ts": 997, + "packages/web/tests/component.test.ts": 1441, + "packages/web/tests/declaration.test.ts": 649, + "packages/web/tests/document.test.ts": 791, + "packages/web/tests/elicitation.test.ts": 559, + "packages/web/tests/live-form.test.ts": 510, + "packages/web/tests/markdown.test.ts": 137, + "packages/web/tests/opener.test.ts": 508, + "packages/web/tests/page.test.ts": 32, + "packages/web/tests/responder.test.ts": 832, + "packages/web/tests/server-lifecycle.test.ts": 632, + "packages/web/tests/server.test.ts": 1031, + "packages/workflow/tests/generated-observations.test.ts": 533, + "packages/workflow/tests/retained-run.test.ts": 1282, + "packages/workflow/tests/service-denial.test.ts": 58, + "packages/workflow/tests/workflow-bundle.test.ts": 695, + "packages/workflow/tests/workflow-definition.test.ts": 348, + "packages/workflow/tests/workflow-fork.test.ts": 301, + "packages/workflow/tests/workflow-run.test.ts": 1066, + "packages/workflow/tests/workspace-effect.test.ts": 1462, + "scripts/tests/adapter-vendor.test.ts": 157, + "scripts/tests/bootstrap-npm-package.test.ts": 5374, "scripts/tests/bump-version.test.ts": 88, - "scripts/tests/changed-paths.test.ts": 40, - "scripts/tests/ci-workflow.test.ts": 1939, - "scripts/tests/consumer-cycle.test.ts": 108, - "scripts/tests/filesystem-contract-workflow.test.ts": 76, - "scripts/tests/main-green.test.ts": 297, - "scripts/tests/main-health.test.ts": 104, - "scripts/tests/no-module-scoped-registry.test.ts": 891, - "scripts/tests/no-redundant-test-scope.test.ts": 4993, - "scripts/tests/no-sync-filesystem.test.ts": 4628, - "scripts/tests/no-yield-in-finally.test.ts": 883, - "scripts/tests/oxlint-policy.test.ts": 10564, - "scripts/tests/packaged-document.test.ts": 74, - "scripts/tests/prefer-effection-operation.test.ts": 6080, - "scripts/tests/prefer-effection-result.test.ts": 4219, - "scripts/tests/publish-workflow-membership.test.ts": 100, - "scripts/tests/review-infrastructure.test.ts": 1963, - "scripts/tests/runtime-exclusions.test.ts": 348, - "scripts/tests/runtime-tests.test.ts": 88, - "scripts/tests/scope-bound-event-registration.test.ts": 4633, - "scripts/tests/staged-write.test.ts": 71, - "scripts/tests/test-file-discovery.test.ts": 254, - "scripts/tests/test-shards.test.ts": 46, - "scripts/tests/test-weights.test.ts": 207, - "scripts/tests/tracked.test.ts": 38, - "scripts/tests/verify-coordinator.test.ts": 230, - "scripts/tests/version-lockstep.test.ts": 124, - "scripts/tests/web-client-module.test.ts": 63, - "scripts/tests/workspace.test.ts": 48 + "scripts/tests/changed-paths.test.ts": 39, + "scripts/tests/ci-workflow.test.ts": 1886, + "scripts/tests/consumer-cycle.test.ts": 105, + "scripts/tests/filesystem-contract-workflow.test.ts": 82, + "scripts/tests/main-green.test.ts": 292, + "scripts/tests/main-health.test.ts": 107, + "scripts/tests/no-module-scoped-registry.test.ts": 856, + "scripts/tests/no-redundant-test-scope.test.ts": 4891, + "scripts/tests/no-sync-filesystem.test.ts": 4519, + "scripts/tests/no-yield-in-finally.test.ts": 884, + "scripts/tests/oxlint-policy.test.ts": 10422, + "scripts/tests/packaged-document.test.ts": 75, + "scripts/tests/prefer-effection-operation.test.ts": 5919, + "scripts/tests/prefer-effection-result.test.ts": 4091, + "scripts/tests/publish-workflow-membership.test.ts": 94, + "scripts/tests/review-infrastructure.test.ts": 1854, + "scripts/tests/runtime-exclusions.test.ts": 326, + "scripts/tests/runtime-tests.test.ts": 80, + "scripts/tests/scope-bound-event-registration.test.ts": 4515, + "scripts/tests/staged-write.test.ts": 69, + "scripts/tests/test-file-discovery.test.ts": 248, + "scripts/tests/test-shards.test.ts": 47, + "scripts/tests/test-weights.test.ts": 206, + "scripts/tests/tracked.test.ts": 39, + "scripts/tests/verify-coordinator.test.ts": 225, + "scripts/tests/version-lockstep.test.ts": 125, + "scripts/tests/web-client-module.test.ts": 96, + "scripts/tests/workspace.test.ts": 70 } } }