@jterrazz/test 15.3.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Scalar } from "yaml";
2
2
  import { DeepMockProxy, MockProxy } from "vitest-mock-extended";
3
3
  import { ReactNode as ComponentUi } from "react";
4
4
  import "vitest/browser";
5
- //#region src/specification/contracts/types.d.ts
5
+ //#region src/model/contracts/types.d.ts
6
6
  /**
7
7
  * The observed outgoing request, reduced to what contract matchers inspect.
8
8
  * Built once per request by the engine (MSW on api/jobs, the stub backend on
@@ -104,7 +104,7 @@ type ContractResponder = (request: MatchableRequest) => ContractResponse;
104
104
  */
105
105
  type ContractResponseValue = ContractResponder | ContractResponse;
106
106
  //#endregion
107
- //#region src/specification/contracts/contract.d.ts
107
+ //#region src/model/contracts/contract.d.ts
108
108
  /**
109
109
  * A declared external interaction: what to match (`request`) and what to reply
110
110
  * (`response`), together in one named artifact. Contracts live in TypeScript
@@ -189,12 +189,25 @@ declare function defineContract(contract: Contract): Contract;
189
189
  */
190
190
  declare function defineContracts(...items: ContractInput[]): Contracts;
191
191
  //#endregion
192
- //#region src/integrations/msw/scope.d.ts
192
+ //#region src/model/contracts/intercept.d.ts
193
193
  /**
194
194
  * One block's worth of declared network. Disposed at the end of the scope that
195
195
  * declared it: the handlers are dropped, and an unmatched request is thrown.
196
196
  */
197
197
  type InterceptScope = AsyncDisposable;
198
+ /** What a scope may state about the network it declares. */
199
+ type InterceptOptions = {
200
+ /**
201
+ * The base a RELATIVE request resolves against, for the life of the scope.
202
+ *
203
+ * A module that calls `fetch('/api/posts')` is browser code: under node the
204
+ * URL cannot be parsed at all, so the request fails before any contract can
205
+ * answer it and the test's only way out was to replace `fetch` itself. With
206
+ * an origin stated, the relative call resolves here and the declared
207
+ * contracts — path form or absolute — match it as they would any other.
208
+ */
209
+ origin?: string;
210
+ };
198
211
  /**
199
212
  * The module-scope network double. Resolves once the engine is listening —
200
213
  * which is why it is a promise, and why the canonical form awaits it:
@@ -207,9 +220,9 @@ type InterceptScope = AsyncDisposable;
207
220
  * first handler is in place, so the shape refuses the racy spelling: a promise
208
221
  * is not an `AsyncDisposable`, and the compiler says so.
209
222
  */
210
- type Intercept = ((contracts: ContractInput) => Promise<InterceptScope>) & ((request: ContractRequest, response: ContractResponder | ContractResponse) => Promise<InterceptScope>);
223
+ type Intercept = ((contracts: ContractInput, options?: InterceptOptions) => Promise<InterceptScope>) & ((request: ContractRequest, response: ContractResponder | ContractResponse, options?: InterceptOptions) => Promise<InterceptScope>);
211
224
  //#endregion
212
- //#region src/specification/matching/match.d.ts
225
+ //#region src/model/matching/match.d.ts
213
226
  /**
214
227
  * Code-side dynamic-value matchers (`match.*`) and the capture scope shared
215
228
  * by every assertion of a single spec execution.
@@ -291,7 +304,52 @@ declare class CaptureScope {
291
304
  set(name: string, value: unknown): void;
292
305
  }
293
306
  //#endregion
294
- //#region src/specification/facets/_common/result/json.d.ts
307
+ //#region src/model/result/directory.d.ts
308
+ /**
309
+ * Read-only accessor for a directory produced by a spec action.
310
+ *
311
+ * Assertions go through `expect()` (async — they walk the disk):
312
+ * `await expect(result.directory('out')).toMatch('scaffold/out')`
313
+ * compares the tree against the fixture directory `_expected/<name>/`.
314
+ */
315
+ declare class DirectoryAccessor {
316
+ /** @internal Ref-capture scope shared by the current spec execution. */
317
+ readonly captures: CaptureScope;
318
+ /** @internal Absolute path of the directory under assertion. */
319
+ readonly root: string;
320
+ /** @internal Test-file directory — fixture resolution root for matchers. */
321
+ readonly testDir: string;
322
+ constructor(absPath: string, testDir: string, captures?: CaptureScope);
323
+ /** List all files (recursively) under the directory, sorted. */
324
+ files(options?: {
325
+ ignore?: string[];
326
+ }): Promise<string[]>;
327
+ }
328
+ //#endregion
329
+ //#region src/model/result/filesystem.d.ts
330
+ /**
331
+ * Read-only accessor for the whole temporary working directory used by a
332
+ * command spec.
333
+ *
334
+ * Assertions go through `expect()` (async — they walk the disk):
335
+ * `await expect(result.filesystem).toMatch('scaffolded')` compares the
336
+ * tree against the fixture directory `_expected/<name>/`.
337
+ */
338
+ declare class FilesystemAccessor {
339
+ /** @internal Ref-capture scope shared by the current spec execution. */
340
+ readonly captures: CaptureScope;
341
+ /** The absolute path of the temporary working directory. */
342
+ readonly cwd: string;
343
+ /** @internal Test-file directory — fixture resolution root for matchers. */
344
+ readonly testDir: string;
345
+ constructor(cwd: string, testDir: string, captures?: CaptureScope);
346
+ /** List all files (recursively) under the working directory, sorted. */
347
+ files(options?: {
348
+ ignore?: string[];
349
+ }): Promise<string[]>;
350
+ }
351
+ //#endregion
352
+ //#region src/model/result/json.d.ts
295
353
  /**
296
354
  * Read-only accessor for a JSON payload parsed from a text stream (stdout).
297
355
  *
@@ -327,214 +385,220 @@ declare class JsonAccessor<Value = unknown> {
327
385
  get value(): Value;
328
386
  }
329
387
  //#endregion
330
- //#region src/specification/ports/browser.port.d.ts
331
- /**
332
- * The landmark roles — the standard set of page regions, and the only
333
- * containers a scope can name. Closed on purpose: ARIA defines exactly these,
334
- * so `within()` never becomes a second selector language.
335
- */
336
- type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
388
+ //#region src/model/result/match-options.d.ts
337
389
  /**
338
- * The interactive/textual element kinds — what a visitor actually acts on.
390
+ * The per-call option both `toMatch` implementations honour.
339
391
  *
340
- * `dialog`, `status`, `table`, `row`, `listitem` and `option` are ARIA roles
341
- * like the landmarks, but they are not CONTAINERS of a page: they are things a
342
- * visitor reads and acts on, and they are the roles the estate's UI specs were
343
- * already reaching for through `getByRole` or, worse, through `.first()`.
392
+ * A golden is read from disk under node and through a server command from
393
+ * inside a page, and BOTH builds must answer `{ frozen: true }` the same way —
394
+ * so the option is declared here, where the node matchers and the page's
395
+ * matchers can each reach it, rather than in the runner's own folder, which a
396
+ * seam may not import (rule I1).
344
397
  */
345
- type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
346
398
  /**
347
- * A user-facing element descriptor — pure data, built by the element
348
- * vocabulary (`button()`, `link()`, `field()`, …) and translated into
349
- * concrete locators by the browser integration. CSS/XPath selectors are
350
- * deliberately not expressible: user-facing elements are the only surface.
351
- *
352
- * A descriptor must designate exactly ONE element at action time; see
353
- * {@link ElementMatch} and CONVENTIONS W3.
399
+ * Per-call options for the fixture-file `toMatch` subjects. `frozen` opts a
400
+ * single fixture OUT of update-mode rewriting: a frozen fixture is NEVER
401
+ * written under `TEST_UPDATE=1` (or vitest `-u`) — in update mode a frozen
402
+ * mismatch still throws its diff, and a frozen missing fixture still throws its
403
+ * "does not exist" error. This is what makes a DELIBERATELY-WRONG fixture (the
404
+ * subject of a negative test that asserts the mismatch/error rendering)
405
+ * survivable across update runs instead of being silently overwritten with the
406
+ * actual output.
354
407
  */
355
- type ElementRef = {
356
- /**
357
- * Match the accessible name as a whole string rather than a substring.
358
- * Default (`false`) mirrors playwright: `link('Articles')` also matches
359
- * "Read Articles".
360
- */
361
- exact?: boolean;
362
- /**
363
- * Assert about the element's FOCUS rather than its presence — built by
364
- * `focused(element)`. The accessibility tree carries no focus state, so
365
- * where the keyboard is cannot be goldened; it is asserted by a verb.
366
- */
367
- focused?: boolean;
368
- /**
369
- * Narrows to "and it is (not) accepting input" — built by `disabled(x)` /
370
- * `enabled(x)`. Like `focused`, a STATE the vocabulary can already name the
371
- * element of; unlike it, one the ARIA tree carries too (`[disabled]`), so
372
- * this modifier is what names the ONE control a test is about, and it
373
- * reaches the direction a behavioural substitute cannot — clicking a
374
- * disabled control is a timeout, never an answer.
375
- */
376
- disabled?: boolean;
377
- kind: ElementKind | LandmarkKind;
378
- /** Landmarks may be anonymous (`main()`, `banner()`); everything else is named. */
379
- name?: string;
380
- /**
381
- * Narrows to "and it is (not) the chosen one" — built by `selected(x)`.
382
- * An option of a COLLAPSED select is in the tree and in the document, and
383
- * it has no box on the screen, so this facet of it is read from the node
384
- * (`:checked`) rather than from what a visitor could point at.
385
- */
386
- selected?: boolean;
387
- /**
388
- * The value the field must hold — built by `valued(field('Title'), '…')`.
389
- * A field's value is a PROPERTY of the node: a controlled input's `value`
390
- * attribute never moves, so nothing but the live value answers for it.
391
- */
392
- value?: string;
393
- /**
394
- * Restrict the search to the elements of another descriptor — built by
395
- * `within(scope, target)`. Chains: a scope may itself carry a scope.
396
- */
397
- scope?: ElementRef;
408
+ type MatchFixtureOptions = {
409
+ frozen?: boolean;
398
410
  };
411
+ //#endregion
412
+ //#region src/model/ports/server.port.d.ts
399
413
  /**
400
- * One candidate captured when a descriptor matched more than one element —
401
- * the evidence the ambiguity error enumerates so the author can disambiguate
402
- * without opening a browser.
414
+ * HTTP response returned by a server port, with parsed JSON body.
403
415
  */
404
- type ElementMatch = {
405
- /**
406
- * The accessible name, when it differs from the text — an `aria-label` is
407
- * what a role descriptor MATCHED on, and printing the text alone sends the
408
- * author to disambiguate a name they cannot see.
409
- */
410
- accessibleName?: string | undefined;
411
- /** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
412
- context?: string | undefined;
413
- /** The attribute that disambiguates most — `href` for links, `name` for fields. */
414
- detail?: string | undefined;
415
- /** Tag name, lower-cased. */
416
- tag: string;
417
- /** Text content, whitespace-collapsed and truncated. */
418
- text: string;
419
- };
420
- /** A `<link>` element captured from the rendered document's head. */
421
- type BrowserLinkElement = {
422
- href: string;
423
- hreflang?: string | undefined;
424
- rel: string;
425
- type?: string | undefined;
426
- };
427
- /** A `<meta>` element captured from the rendered document's head. */
428
- type BrowserMetaElement = {
429
- content: string;
430
- name?: string | undefined;
431
- property?: string | undefined;
432
- };
433
- /** A console message emitted while the page loaded or the scenario ran. */
434
- type BrowserConsoleMessage = {
435
- text: string;
436
- type: string;
416
+ type ServerResponse = {
417
+ /** HTTP status code. */
418
+ status: number;
419
+ /** Parsed JSON response body, or null if parsing failed. */
420
+ body: unknown;
421
+ /** Response headers as a flat key-value map. */
422
+ headers: Record<string, string>;
437
423
  };
438
424
  /**
439
- * The visitor — the interaction vocabulary handed to a visit scenario.
440
- * Every action auto-waits (playwright actionability); `see()` is the single
441
- * synchronization primitive: it retries until the element is visible and
442
- * fails at the timeout. There is no sleep and no conditional helper.
425
+ * Abstract server interface for specification runners.
426
+ * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
443
427
  */
444
- type Visitor = {
445
- /** Check a checkbox or radio. */
446
- check: (element: ElementRef) => Promise<void>;
447
- /** Click the element. */
448
- click: (element: ElementRef) => Promise<void>;
449
- /** Fill a form field with a value. */
450
- fill: (element: ElementRef, value: string) => Promise<void>;
451
- /**
452
- * Wait until the element is GONE — hidden, or never in the document. The
453
- * absence primitive: `see()` cannot answer "the dialog closed", because an
454
- * element that never appears and one that disappeared read the same to it.
455
- */
456
- gone: (element: ElementRef) => Promise<void>;
457
- /** Navigate to a path of the site under test. */
458
- goto: (path: string) => Promise<void>;
459
- /** Hover the element. */
460
- hover: (element: ElementRef) => Promise<void>;
461
- /** Press a key (e.g. `Enter`). */
462
- press: (key: string) => Promise<void>;
463
- /** Wait until the element is visible — the only synchronization primitive. */
464
- see: (element: ElementRef) => Promise<void>;
465
- /** Select an option in a select field. */
466
- select: (element: ElementRef, option: string) => Promise<void>;
467
- };
468
- /** The behavior of a visit — the When of the spec; assertions stay in the Then. */
469
- type VisitScenario = (visitor: Visitor) => Promise<void>;
470
- /** Per-visit options forwarded to the browser context. */
471
- type BrowserOpenOptions = {
472
- /**
473
- * Extra origins the `external: 'block'` policy lets through — the
474
- * declared stub backend the page legitimately fetches from.
475
- */
476
- allowedOrigins?: string[] | undefined;
477
- /**
478
- * Base URL of the site under test — the origin `goto()` resolves against
479
- * and the boundary of the `external` policy.
480
- */
481
- baseUrl: string;
482
- /**
483
- * Pin the page's own `Date` at this instant before the first byte is
484
- * parsed — the calendar the site's scripts read. `.clock()` on the chain.
485
- */
486
- clock?: string | undefined;
487
- /**
488
- * Cross-origin request policy. `'block'` aborts every request leaving
489
- * the site under test (analytics, CDNs) — the browser-side analog of
490
- * strict intercepts. `'allow'` lets them through (deployed-site mode).
491
- */
492
- external: 'allow' | 'block';
493
- /** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
494
- headers?: Record<string, string> | undefined;
495
- /** The interaction scenario to run after load; the capture reflects the final state. */
496
- scenario?: undefined | VisitScenario;
428
+ type ServerPort = {
429
+ /** Send an HTTP request and return the parsed response. */
430
+ request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
497
431
  };
432
+ //#endregion
433
+ //#region src/model/result/response.d.ts
498
434
  /**
499
- * The rendered page captured by a browser visit — the FINAL state when a
500
- * scenario ran. Extraction happens in-page (the browser IS the HTML
501
- * parser); interpretation of the raw elements belongs to core.
435
+ * Read-only accessor for an HTTP response.
436
+ *
437
+ * Assertions go through `expect()`:
438
+ * `expect(result.response).toMatch('created.http')` compares status,
439
+ * a subset of headers, and the JSON body against `_expected/<name>` —
440
+ * with `{{placeholder}}` support in all three.
502
441
  */
503
- type BrowserPage = {
504
- /** Console messages emitted while loading and interacting, in order. */
505
- consoleMessages: BrowserConsoleMessage[];
506
- /** Serialized DOM after rendering (`document.documentElement.outerHTML`). */
507
- html: string;
508
- /** Raw text content of every `application/ld+json` script, in DOM order. */
509
- jsonLdBlocks: string[];
510
- /** `<link>` elements of the head, in DOM order. */
511
- links: BrowserLinkElement[];
512
- /** `<meta>` elements of the head, in DOM order. */
513
- metas: BrowserMetaElement[];
514
- /** HTTP status of the main document response (0 when unavailable). */
515
- status: number;
516
- /** Rendered `document.body.innerText`. */
517
- text: string;
518
- /** The ARIA snapshot of the rendered `<body>` — the outline a golden pins. */
519
- tree: string;
520
- /** `document.title` after rendering. */
521
- title: string;
522
- /** Final URL after redirects and scenario navigation. */
523
- url: string;
442
+ declare class ResponseAccessor {
443
+ /** The parsed JSON response body (null when parsing failed). */
444
+ readonly body: unknown;
445
+ /** @internal Ref-capture scope shared by the current spec execution. */
446
+ readonly captures: CaptureScope;
447
+ /** Response headers as a flat, lower-cased key-value map. */
448
+ readonly headers: Record<string, string>;
449
+ /** The HTTP response status code. */
450
+ readonly status: number;
451
+ /** @internal Test-file directory — fixture resolution root for matchers. */
452
+ readonly testDir: string;
453
+ constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
454
+ }
455
+ //#endregion
456
+ //#region src/model/ports/database.port.d.ts
457
+ /**
458
+ * Abstract database interface for specification runners.
459
+ * Implement this to plug in your database stack (e.g. Postgres, SQLite).
460
+ */
461
+ type DatabasePort = {
462
+ /** Execute raw SQL (for seeding test data). */
463
+ seed: (sql: string) => Promise<void>;
464
+ /** Query a table and return rows as arrays of values. */
465
+ query: (table: string, columns: string[]) => Promise<unknown[][]>;
466
+ /** Reset database to clean state between tests. */
467
+ reset: () => Promise<void>;
524
468
  };
469
+ //#endregion
470
+ //#region src/model/result/table.d.ts
525
471
  /**
526
- * Abstract browser interface for the website specification runner.
527
- * One implementation lives in `integrations/playwright/` — a single shared
528
- * browser instance per runner; each `open()` gets a fresh, isolated context.
472
+ * Read-only accessor for a database table after a specification run.
473
+ *
474
+ * Assertions go through `expect()` (async — they query the database):
475
+ *
476
+ * await expect(result.table('users', { database: 'db' })).toMatchRows({
477
+ * columns: ['name'],
478
+ * rows: [['Alice']],
479
+ * });
480
+ * await expect(result.table('users', { database: 'db' })).toBeEmpty();
529
481
  */
530
- type BrowserPort = {
531
- /** Close the shared browser instance (idempotent). */
532
- close: () => Promise<void>;
533
- /** Load `url` in a fresh context, run the scenario, capture the final page. */
534
- open: (url: string, options: BrowserOpenOptions) => Promise<BrowserPage>;
482
+ declare class TableAccessor {
483
+ /** @internal Ref-capture scope shared by the current spec execution. */
484
+ readonly captures: CaptureScope;
485
+ /** @internal */
486
+ private readonly db;
487
+ /** The table name this accessor reads. */
488
+ readonly name: string;
489
+ constructor(name: string, db: DatabasePort, captures?: CaptureScope);
490
+ /** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
491
+ query(columns: string[]): Promise<unknown[][]>;
492
+ }
493
+ //#endregion
494
+ //#region src/model/result/text.d.ts
495
+ /**
496
+ * Read-only accessor for a captured text handle — THE universal one: stdout,
497
+ * stderr, container logs, and file text all surface as a `TextAccessor`.
498
+ *
499
+ * Backed by a primitive string, exposed via {@link text}, but also provides
500
+ * `toString()` / `valueOf()` for string coercion, so common patterns like
501
+ * `String(result.stdout)` and template-literal interpolation still work.
502
+ *
503
+ * ANSI escape sequences are stripped by default before every comparison
504
+ * (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
505
+ * an escape hatch for application noise not covered by the `{{token}}`
506
+ * grammar.
507
+ *
508
+ * Text operations are **closed** over the type: `.grep(pattern)` returns a
509
+ * `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
510
+ * context and the `transform`, so results are chainable
511
+ * (`result.stdout.grep(a).grep(b)`) and snapshot-able
512
+ * (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
513
+ *
514
+ * Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
515
+ * and `expect(result.stdout).toMatch('name.txt')` (resolved against
516
+ * `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
517
+ */
518
+ declare class TextAccessor {
519
+ /** @internal Ref-capture scope shared by the current spec execution. */
520
+ readonly captures: CaptureScope;
521
+ /** @internal Stream label used in failure messages ("stdout" / "stderr"). */
522
+ readonly streamName: string;
523
+ /** @internal Test-file directory — fixture resolution root for matchers. */
524
+ readonly testDir: string;
525
+ /** The raw captured text (never transformed, ANSI preserved). */
526
+ readonly text: string;
527
+ /** @internal Normaliser applied before comparisons, never to fixtures. */
528
+ readonly transform?: ((text: string) => string) | undefined;
529
+ constructor(value: string, streamName: string, testDir: string, options?: {
530
+ captures?: CaptureScope | undefined;
531
+ transform?: ((text: string) => string) | undefined;
532
+ });
533
+ /** @internal The text as compared by matchers — ANSI stripped, transform applied. */
534
+ get comparableText(): string;
535
+ /**
536
+ * Keep only the blank-line-separated blocks of the text that contain
537
+ * `pattern` (how linter/compiler output is structured), returned as a new
538
+ * `TextAccessor` — the same subject type, so the result is chainable and
539
+ * snapshot-able. The scalpel for probing large tool outputs; snapshot the
540
+ * whole surface by default, reach for `.grep()` for targeted checks.
541
+ */
542
+ grep(pattern: string): TextAccessor;
543
+ toString(): string;
544
+ valueOf(): string;
545
+ }
546
+ //#endregion
547
+ //#region src/model/result/result.d.ts
548
+ /** Read-only handle to a single file produced by a spec action. */
549
+ type FileAccessor = {
550
+ /** The UTF-8 text content. Throws if the file does not exist. */
551
+ readonly content: string;
552
+ readonly exists: boolean;
553
+ /** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
554
+ grep: (pattern: string) => TextAccessor;
555
+ };
556
+ type BaseResultOptions = {
557
+ config: SpecificationConfig;
558
+ testDir: string;
559
+ workDir?: string | undefined;
535
560
  };
561
+ /**
562
+ * Base result - common accessors available after any action type.
563
+ * Extended by HttpResult, CliResult, and used directly by job results.
564
+ *
565
+ * Every result carries a fresh {@link CaptureScope}: `match.ref()` captures
566
+ * and `{{type#ref}}` placeholders are scoped to one spec execution.
567
+ */
568
+ declare class BaseResult {
569
+ /** @internal Ref-capture scope shared by every assertion on this result. */
570
+ readonly captures: CaptureScope;
571
+ protected config: SpecificationConfig;
572
+ protected testDir: string;
573
+ protected workDir?: string | undefined;
574
+ constructor(options: BaseResultOptions);
575
+ /** Access a directory (relative to the working directory) for snapshot assertions. */
576
+ directory(path?: string): DirectoryAccessor;
577
+ /** Access a single file (relative to the working directory) for content assertions. */
578
+ file(path: string): FileAccessor;
579
+ /** Access a database table for row-level assertions via expect() matchers. */
580
+ table(tableName: string, options?: {
581
+ database?: string;
582
+ }): TableAccessor;
583
+ private resolveDatabase;
584
+ }
585
+ //#endregion
586
+ //#region src/facets/api/api.result.d.ts
587
+ /** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
588
+ declare class HttpResult extends BaseResult {
589
+ private readonly responseData;
590
+ constructor(options: {
591
+ config: SpecificationConfig;
592
+ response: ServerResponse;
593
+ testDir: string;
594
+ });
595
+ /** Access the HTTP response (status, headers, body) for assertions. */
596
+ get response(): ResponseAccessor;
597
+ /** The HTTP response status code. */
598
+ get status(): number;
599
+ }
536
600
  //#endregion
537
- //#region src/specification/ports/cli.port.d.ts
601
+ //#region src/model/ports/cli.port.d.ts
538
602
  /**
539
603
  * Raw output from a command execution, including exit code and captured output streams.
540
604
  */
@@ -585,516 +649,436 @@ type CliPort = {
585
649
  watch: (args: string, cwd: string, options: ExecOptions, env?: CliEnv) => Promise<CliOutput>;
586
650
  };
587
651
  //#endregion
588
- //#region src/specification/ports/database.port.d.ts
652
+ //#region src/facets/cli/literate.d.ts
589
653
  /**
590
- * Abstract database interface for specification runners.
591
- * Implement this to plug in your database stack (e.g. Postgres, SQLite).
654
+ * Running a `<case>.spec.yaml` document: one scenario, one working directory,
655
+ * one set of servers, every run executed and asserted.
656
+ *
657
+ * The engine composes pieces that already exist rather than adding a second of
658
+ * anything — {@link copyPlan} for `fixture:`, the runner's own command adapter
659
+ * for each run, {@link ServeAdapter} for `serve:`, the `{{token}}` engine for
660
+ * every comparison, and {@link mergeTextPreservingPlaceholders} for the
661
+ * rewrite. What is new here is only the ORCHESTRATION the chain cannot express:
662
+ * several commands sharing one working directory, each judged the moment it
663
+ * ends — so what a run states about that directory is what IT left there, and
664
+ * the first run to disagree is the one the failure names.
592
665
  */
593
- type DatabasePort = {
594
- /** Execute raw SQL (for seeding test data). */
595
- seed: (sql: string) => Promise<void>;
596
- /** Query a table and return rows as arrays of values. */
597
- query: (table: string, columns: string[]) => Promise<unknown[][]>;
598
- /** Reset database to clean state between tests. */
599
- reset: () => Promise<void>;
666
+ /**
667
+ * A server a document may start by name (`serve: mcp`), registered once per app
668
+ * in the `serve` option of `specification.cli()`.
669
+ */
670
+ type LiterateServeRegistration = {
671
+ /** Shell command that starts the server, run from the project root. */
672
+ command: string;
673
+ /** The variable the resolved URL is bound to in every run's child env. */
674
+ env: string;
675
+ /**
676
+ * Matched against the server's output; the FIRST capture group is the port
677
+ * it chose — named (`(?<port>\d+)`) or not, it is group 1 either way.
678
+ */
679
+ ready: RegExp;
680
+ /** Builds the URL bound to {@link env} from the announced port. */
681
+ url: (port: number) => string;
682
+ };
683
+ /** Per-call options for {@link runSpecDocument} / `cli.run()`. */
684
+ type LiterateRunFlags = {
685
+ /**
686
+ * Opt this document OUT of the update-mode rewrite. A frozen document is
687
+ * NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
688
+ * That is what makes a DELIBERATELY-WRONG `.spec.yaml` — one whose failure
689
+ * rendering is the subject of a negative test — survive an update run
690
+ * instead of being silently corrected into a passing file. The document's
691
+ * mirror of `toMatch(name, { frozen: true })`.
692
+ */
693
+ frozen?: boolean | undefined;
600
694
  };
601
695
  //#endregion
602
- //#region src/specification/ports/device.port.d.ts
696
+ //#region src/facets/integration/integration.result.d.ts
697
+ /** What `.call()` produced: the value it returned, or the error it threw. */
698
+ type CallOutcome = {
699
+ error: unknown;
700
+ threw: boolean;
701
+ value: unknown;
702
+ };
703
+ type CallResultOptions = BaseResultOptions & {
704
+ outcome: CallOutcome;
705
+ };
603
706
  /**
604
- * The element kinds a mobile screen can designate — the structural subset of
605
- * {@link ElementRef} kinds that map onto the XCUITest accessibility tree.
606
- * There is ONE element vocabulary across facets: `button('Bookmark')` works
607
- * in a visit scenario and a mobile scenario alike. Landmarks have no iOS
608
- * analog — passing one to a mobile verb refuses at runtime.
707
+ * What `result.value` is, decided by what the call returned: a string reads as
708
+ * a stream, anything else as JSON. `.call<T>()` already knows T, so the test
709
+ * reads one field (`result.value.value.ok`) without narrowing a union first.
609
710
  */
610
- type MobileElementKind = 'button' | 'field' | 'testId' | 'text';
711
+ type CallValue<Returned> = Returned extends string ? TextAccessor : JsonAccessor<Returned>;
611
712
  /**
612
- * A user-facing element descriptor for mobile scenarios — pure data, built by
613
- * the shared element vocabulary (`button()`, `field()`, `content()`,
614
- * `testId()`, `within()`) and translated into iOS predicate strings by the
615
- * device integration. Structurally the same ref type as the browser's, so the
616
- * vocabulary stays single; kinds outside {@link MobileElementKind} (the ARIA
617
- * landmarks) are refused at runtime with a message naming the boundary.
713
+ * The result of an in-process call — the integration facet's whole surface.
618
714
  *
619
- * A descriptor must designate exactly ONE element when a verb ACTS on it
620
- * (`tap`, `fill`) — see {@link MobileElementMatch} and CONVENTIONS W3.
621
- * `see()` acts on nothing, so any visible match satisfies it: XCUITest trees
622
- * legitimately duplicate a label across container and child.
623
- */
624
- type MobileElementRef = ElementRef;
625
- /**
626
- * One candidate captured when a descriptor matched more than one element —
627
- * the evidence the ambiguity error enumerates so the author can disambiguate
628
- * without opening the simulator.
715
+ * Two readings, never both: `value` is what the call RETURNED, `error` is
716
+ * what it THREW. A module that refuses is specified the same way a module
717
+ * that answers is, and neither needs a `try`/`catch` in the test — a shape
718
+ * that costs a refusal spec three times the size of the happy one, and that
719
+ * quietly passes when nothing throws at all.
720
+ *
721
+ * Both are the package's ordinary accessors, so the golden mechanism reaches
722
+ * them, and `Returned` is what `.call<T>()` returned — `result.value.value` is
723
+ * that type, not `unknown`, so a one-field reading needs no golden file: `toMatch('<name>.json'|'<name>.txt')`, the `{{token}}` grammar,
724
+ * `{ frozen }` and `TEST_UPDATE=1` all work here exactly as on an HTTP
725
+ * response or a command's stdout.
629
726
  */
630
- type MobileElementMatch = {
631
- /** The accessibility identifier (`testId()` target), when one is set. */
632
- identifier?: string;
633
- /** The accessible label, whitespace-collapsed and truncated. */
634
- label?: string;
635
- /** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
636
- type: string;
637
- /** The element value (text-field content, adjustable value), when present. */
638
- value?: string;
639
- };
640
- /**
641
- * The visitor — the interaction vocabulary handed to a mobile scenario.
642
- * Every verb auto-waits by polling until a visible match exists; acting
643
- * verbs then enforce exactly-one (W3), while `see()` — the single
644
- * synchronization primitive — is satisfied by any visible match. There is
645
- * no sleep and no conditional helper.
646
- */
647
- type MobileVisitor = {
648
- /** Fill a text field with a value. */
649
- fill: (element: MobileElementRef, value: string) => Promise<void>;
650
- /** Wait until the element is visible — the only synchronization primitive. */
651
- see: (element: MobileElementRef) => Promise<void>;
652
- /** Tap the element. */
653
- tap: (element: MobileElementRef) => Promise<void>;
654
- };
655
- /** The behavior of an open — the When of the spec; assertions stay in the Then. */
656
- type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
657
- /**
658
- * One node of the projected accessibility tree — the XCUITest page source
659
- * with its noise collapsed: unlabeled, identifier-less, valueless wrapper
660
- * nodes are dropped and their children hoisted, so the projection stays
661
- * stable and golden-friendly. Type names lose the `XCUIElementType` prefix.
662
- */
663
- type ScreenNode = {
664
- children?: ScreenNode[];
665
- /** The accessibility identifier, only when it differs from the label. */
666
- identifier?: string;
667
- label?: string;
668
- /** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
669
- type: string;
670
- value?: string;
671
- };
672
- /**
673
- * The screen captured by a device open — the FINAL state when a scenario
674
- * ran. The tree is the projected page source; `texts` are the visible
675
- * labels/values in document order, consecutive duplicates collapsed.
676
- */
677
- type DeviceScreen = {
678
- texts: string[];
679
- tree: ScreenNode;
680
- };
681
- /** Per-open options forwarded to the device session. */
682
- type DeviceOpenOptions = {
683
- /** The app under test — terminated and relaunched for a fresh, deterministic state. */
684
- bundleId: string;
685
- /** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
686
- deepLink?: string | undefined;
687
- /** The interaction scenario to run after launch; the capture reflects the final state. */
688
- scenario?: MobileScenario | undefined;
689
- };
690
- /**
691
- * How long a device session waits, in milliseconds. Every field is optional
692
- * and falls back to the framework's own default, so a runner states only the
693
- * wait it needs to move — the defaults suit a release build, and a project
694
- * that drives a DEV build pays for its bundler's cold boot.
695
- */
696
- type DeviceTimeouts = {
727
+ declare class CallResult<Returned = unknown> extends BaseResult {
728
+ private readonly outcome;
729
+ constructor(options: CallResultOptions);
697
730
  /**
698
- * How long every verb polls for a visible match before refusing.
699
- * Default: `30_000` — enough for a cold app boot on a release bundle.
731
+ * What the call THREW, as text — the message of an `Error`, the value
732
+ * itself when something else was thrown, and empty when it returned.
733
+ *
734
+ * Empty rather than absent on purpose: `await expect(result.error).toBeEmpty()`
735
+ * is how a spec says "and it did not refuse" — awaited, because the
736
+ * matcher answers a promise on every subject it takes, and a dropped one
737
+ * passes the test while the failure surfaces as an unhandled rejection
738
+ * (D2 refuses the bare form).
700
739
  */
701
- action?: number | undefined;
740
+ get error(): TextAccessor;
702
741
  /**
703
- * How long the FIRST session waits for WebDriverAgent to build and
704
- * launch on the simulator. Default: `240_000`.
742
+ * What the call RETURNED. A string reads as a stream (`<name>.txt`),
743
+ * anything else as JSON (`<name>.json`) — the same two subjects every
744
+ * other facet's results are built from.
745
+ *
746
+ * A call that threw returns the empty value: what it produced is
747
+ * {@link CallResult.error}.
705
748
  */
706
- launch?: number | undefined;
707
- };
708
- /**
709
- * Abstract device interface for the mobile specification runner.
710
- * One implementation lives in `integrations/appium/` — a single driver
711
- * session per runner, created on the first `open()` and reused; each open
712
- * terminates and relaunches the app for a deterministic fresh state.
713
- */
714
- type DevicePort = {
715
- /** End the driver session (idempotent). */
716
- close: () => Promise<void>;
717
- /** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
718
- open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
719
- };
749
+ get value(): CallValue<Returned>;
750
+ }
720
751
  //#endregion
721
- //#region src/specification/ports/server.port.d.ts
752
+ //#region src/model/ports/browser.port.d.ts
722
753
  /**
723
- * HTTP response returned by a server port, with parsed JSON body.
754
+ * The landmark roles — the standard set of page regions, and the only
755
+ * containers a scope can name. Closed on purpose: ARIA defines exactly these,
756
+ * so `within()` never becomes a second selector language.
724
757
  */
725
- type ServerResponse = {
726
- /** HTTP status code. */
727
- status: number;
728
- /** Parsed JSON response body, or null if parsing failed. */
729
- body: unknown;
730
- /** Response headers as a flat key-value map. */
731
- headers: Record<string, string>;
732
- };
758
+ type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
733
759
  /**
734
- * Abstract server interface for specification runners.
735
- * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
760
+ * The interactive/textual element kinds — what a visitor actually acts on.
761
+ *
762
+ * `dialog`, `status`, `table`, `row`, `listitem` and `option` are ARIA roles
763
+ * like the landmarks, but they are not CONTAINERS of a page: they are things a
764
+ * visitor reads and acts on, and they are the roles the estate's UI specs were
765
+ * already reaching for through `getByRole` or, worse, through `.first()`.
736
766
  */
737
- type ServerPort = {
738
- /** Send an HTTP request and return the parsed response. */
739
- request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
740
- };
741
- //#endregion
742
- //#region src/specification/ports/isolation.port.d.ts
767
+ type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
743
768
  /**
744
- * Strategy for isolating service state across parallel test workers.
745
- *
746
- * Each service handle provides an isolation strategy. The framework
747
- * calls `acquire()` once per vitest worker, `reset()` before each
748
- * `spec.run()`, and `release()` when the worker shuts down.
769
+ * A user-facing element descriptor — pure data, built by the element
770
+ * vocabulary (`button()`, `link()`, `field()`, …) and translated into
771
+ * concrete locators by the browser integration. CSS/XPath selectors are
772
+ * deliberately not expressible: user-facing elements are the only surface.
749
773
  *
750
- * Implement this interface to support new service types (e.g. MongoDB,
751
- * Elasticsearch, S3).
774
+ * A descriptor must designate exactly ONE element at action time; see
775
+ * {@link ElementMatch} and CONVENTIONS W3.
752
776
  */
753
- type IsolationStrategy = {
777
+ type ElementRef = {
754
778
  /**
755
- * Create an isolated namespace for this worker.
756
- * Called once when the worker starts — e.g. clone a template database,
757
- * set a Redis key prefix.
779
+ * Match the accessible name as a whole string rather than a substring.
758
780
  *
759
- * @param workerId - Unique identifier for this vitest worker.
781
+ * ABSENT means "the author said nothing", which the adapters read as the
782
+ * default — exact — while keeping the information that no choice was made,
783
+ * so a name that matches only as a substring can be reported once with the
784
+ * transitional warning.
760
785
  */
761
- acquire: (workerId: string) => Promise<void>;
786
+ exact?: boolean;
762
787
  /**
763
- * Fast reset within the namespace between `spec.run()` calls.
764
- * E.g. truncate tables (without dropping the database).
788
+ * Assert about the element's FOCUS rather than its presence — built by
789
+ * `focused(element)`. The accessibility tree carries no focus state, so
790
+ * where the keyboard is cannot be goldened; it is asserted by a verb.
765
791
  */
766
- reset: () => Promise<void>;
792
+ focused?: boolean;
767
793
  /**
768
- * Tear down the isolated namespace.
769
- * Called once when the worker shuts down — e.g. drop the cloned database.
794
+ * Narrows to "and it is (not) accepting input" — built by `disabled(x)` /
795
+ * `enabled(x)`. Like `focused`, a STATE the vocabulary can already name the
796
+ * element of; unlike it, one the ARIA tree carries too (`[disabled]`), so
797
+ * this modifier is what names the ONE control a test is about, and it
798
+ * reaches the direction a behavioural substitute cannot — clicking a
799
+ * disabled control is a timeout, never an answer.
770
800
  */
771
- release: () => Promise<void>;
772
- };
773
- //#endregion
774
- //#region src/specification/ports/service.port.d.ts
775
- /**
776
- * A service handle — returned by factory functions like postgres(), redis().
777
- * Mutable: connectionString is populated after the orchestrator starts containers.
778
- */
779
- type ServiceHandle = {
780
- /** Service type identifier. */
781
- readonly type: string;
801
+ disabled?: boolean;
802
+ kind: ElementKind | LandmarkKind;
803
+ /** Landmarks may be anonymous (`main()`, `banner()`); everything else is named. */
804
+ name?: string;
782
805
  /**
783
- * Compose service name. Left `null` until the orchestrator binds it at
784
- * start time (CONVENTIONS A6): a handle with no explicit `composeService`
785
- * links to the compose service named exactly like its record key, else the
786
- * kebab-case conversion of the key — so `{ analyticsDb: postgres() }` binds
787
- * to the compose service `analytics-db` without any option. Set
788
- * `composeService` explicitly only for names the key cannot derive.
806
+ * Narrows to "and it is (not) the chosen one" — built by `selected(x)`.
807
+ * An option of a COLLAPSED select is in the tree and in the document, and
808
+ * it has no box on the screen, so this facet of it is read from the node
809
+ * (`:checked`) rather than from what a visitor could point at.
789
810
  */
790
- composeName: null | string;
791
- /** Default container port for this service type. */
792
- readonly defaultPort: number;
793
- /** Default Docker image for this service type. */
794
- readonly defaultImage: string;
795
- /** Environment variables to pass to the container. */
796
- readonly environment: Record<string, string>;
797
- /** Connection string — populated after start. */
798
- connectionString: string;
799
- /** Whether this service has been started. */
800
- started: boolean;
801
- /** Build the connection string from host and port. */
802
- buildConnectionString: (host: string, port: number) => string;
803
- /** Create a DatabasePort adapter (if this is a database). Returns null otherwise. */
804
- createDatabaseAdapter: () => DatabasePort | null;
805
- /** Verify the service is ready and accepting connections. Throws with context if not. */
806
- healthcheck: () => Promise<void>;
811
+ selected?: boolean;
807
812
  /**
808
- * Run initialization scripts (e.g., init.sql). Throws with SQL error
809
- * context if it fails.
810
- *
811
- * `composeDir` is where the compose file lives — where a service reads its
812
- * `init.sql` from. `root` is the project root the specification resolved
813
- * (A9), handed down so a service that CACHES something writes it under the
814
- * project's own `.artifacts/`, never in a machine-global directory two
815
- * checkouts would share.
813
+ * The value the field must hold — built by `valued(field('Title'), '…')`.
814
+ * A field's value is a PROPERTY of the node: a controlled input's `value`
815
+ * attribute never moves, so nothing but the live value answers for it.
816
816
  */
817
- initialize: (composeDir: string, root: string) => Promise<void>;
818
- /** Reset state between tests (truncate tables, flush cache, etc.) */
819
- reset: () => Promise<void>;
820
- /** Get the isolation strategy for parallel test execution. */
821
- isolation: () => IsolationStrategy;
817
+ value?: string;
818
+ /**
819
+ * Restrict the search to the elements of another descriptor — built by
820
+ * `within(scope, target)`. Chains: a scope may itself carry a scope.
821
+ */
822
+ scope?: ElementRef;
822
823
  };
823
- //#endregion
824
- //#region src/specification/facets/_common/result/response.d.ts
825
- /**
826
- * Read-only accessor for an HTTP response.
827
- *
828
- * Assertions go through `expect()`:
829
- * `expect(result.response).toMatch('created.http')` compares status,
830
- * a subset of headers, and the JSON body against `_expected/<name>` —
831
- * with `{{placeholder}}` support in all three.
832
- */
833
- declare class ResponseAccessor {
834
- /** The parsed JSON response body (null when parsing failed). */
835
- readonly body: unknown;
836
- /** @internal Ref-capture scope shared by the current spec execution. */
837
- readonly captures: CaptureScope;
838
- /** Response headers as a flat, lower-cased key-value map. */
839
- readonly headers: Record<string, string>;
840
- /** The HTTP response status code. */
841
- readonly status: number;
842
- /** @internal Test-file directory — fixture resolution root for matchers. */
843
- readonly testDir: string;
844
- constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
845
- }
846
- //#endregion
847
- //#region src/specification/facets/api/result.d.ts
848
- /** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
849
- declare class HttpResult extends BaseResult {
850
- private readonly responseData;
851
- constructor(options: {
852
- config: SpecificationConfig;
853
- response: ServerResponse;
854
- testDir: string;
855
- });
856
- /** Access the HTTP response (status, headers, body) for assertions. */
857
- get response(): ResponseAccessor;
858
- /** The HTTP response status code. */
859
- get status(): number;
860
- }
861
- //#endregion
862
- //#region src/specification/facets/_common/result/filesystem.d.ts
863
824
  /**
864
- * Read-only accessor for the whole temporary working directory used by a
865
- * command spec.
866
- *
867
- * Assertions go through `expect()` (async — they walk the disk):
868
- * `await expect(result.filesystem).toMatch('scaffolded')` compares the
869
- * tree against the fixture directory `_expected/<name>/`.
870
- */
871
- declare class FilesystemAccessor {
872
- /** @internal Ref-capture scope shared by the current spec execution. */
873
- readonly captures: CaptureScope;
874
- /** The absolute path of the temporary working directory. */
875
- readonly cwd: string;
876
- /** @internal Test-file directory — fixture resolution root for matchers. */
877
- readonly testDir: string;
878
- constructor(cwd: string, testDir: string, captures?: CaptureScope);
879
- /** List all files (recursively) under the working directory, sorted. */
880
- files(options?: {
881
- ignore?: string[];
882
- }): Promise<string[]>;
883
- }
884
- //#endregion
885
- //#region src/specification/facets/_common/result/text.d.ts
886
- /**
887
- * Read-only accessor for a captured text handle — THE universal one: stdout,
888
- * stderr, container logs, and file text all surface as a `TextAccessor`.
889
- *
890
- * Backed by a primitive string, exposed via {@link text}, but also provides
891
- * `toString()` / `valueOf()` for string coercion, so common patterns like
892
- * `String(result.stdout)` and template-literal interpolation still work.
893
- *
894
- * ANSI escape sequences are stripped by default before every comparison
895
- * (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
896
- * an escape hatch for application noise not covered by the `{{token}}`
897
- * grammar.
898
- *
899
- * Text operations are **closed** over the type: `.grep(pattern)` returns a
900
- * `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
901
- * context and the `transform`, so results are chainable
902
- * (`result.stdout.grep(a).grep(b)`) and snapshot-able
903
- * (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
904
- *
905
- * Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
906
- * and `expect(result.stdout).toMatch('name.txt')` (resolved against
907
- * `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
825
+ * One candidate captured when a descriptor matched more than one element —
826
+ * the evidence the ambiguity error enumerates so the author can disambiguate
827
+ * without opening a browser.
908
828
  */
909
- declare class TextAccessor {
910
- /** @internal Ref-capture scope shared by the current spec execution. */
911
- readonly captures: CaptureScope;
912
- /** @internal Stream label used in failure messages ("stdout" / "stderr"). */
913
- readonly streamName: string;
914
- /** @internal Test-file directory — fixture resolution root for matchers. */
915
- readonly testDir: string;
916
- /** The raw captured text (never transformed, ANSI preserved). */
917
- readonly text: string;
918
- /** @internal Normaliser applied before comparisons, never to fixtures. */
919
- readonly transform?: ((text: string) => string) | undefined;
920
- constructor(value: string, streamName: string, testDir: string, options?: {
921
- captures?: CaptureScope | undefined;
922
- transform?: ((text: string) => string) | undefined;
923
- });
924
- /** @internal The text as compared by matchers — ANSI stripped, transform applied. */
925
- get comparableText(): string;
829
+ type ElementMatch = {
926
830
  /**
927
- * Keep only the blank-line-separated blocks of the text that contain
928
- * `pattern` (how linter/compiler output is structured), returned as a new
929
- * `TextAccessor` — the same subject type, so the result is chainable and
930
- * snapshot-able. The scalpel for probing large tool outputs; snapshot the
931
- * whole surface by default, reach for `.grep()` for targeted checks.
831
+ * The accessible name, when it differs from the text — an `aria-label` is
832
+ * what a role descriptor MATCHED on, and printing the text alone sends the
833
+ * author to disambiguate a name they cannot see.
932
834
  */
933
- grep(pattern: string): TextAccessor;
934
- toString(): string;
935
- valueOf(): string;
936
- }
937
- //#endregion
938
- //#region src/specification/facets/cli/result.d.ts
835
+ accessibleName?: string | undefined;
836
+ /** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
837
+ context?: string | undefined;
838
+ /** The attribute that disambiguates most — `href` for links, `name` for fields. */
839
+ detail?: string | undefined;
840
+ /** Tag name, lower-cased. */
841
+ tag: string;
842
+ /** Text content, whitespace-collapsed and truncated. */
843
+ text: string;
844
+ };
845
+ /** A `<link>` element captured from the rendered document's head. */
846
+ type BrowserLinkElement = {
847
+ href: string;
848
+ hreflang?: string | undefined;
849
+ rel: string;
850
+ type?: string | undefined;
851
+ };
852
+ /** A `<meta>` element captured from the rendered document's head. */
853
+ type BrowserMetaElement = {
854
+ content: string;
855
+ name?: string | undefined;
856
+ property?: string | undefined;
857
+ };
858
+ /** A console message emitted while the page loaded or the scenario ran. */
859
+ type BrowserConsoleMessage = {
860
+ text: string;
861
+ type: string;
862
+ };
939
863
  /**
940
- * Result from a command action (`.exec()`).
941
- *
942
- * When the runner was configured with a `docker` option, the result also
943
- * exposes container accessors and participates in `Symbol.asyncDispose`
944
- * cleanup. The Docker shell-outs are **lazy**: a test that never calls
945
- * `.container(...)` never queries the Docker daemon, so command-only tests
946
- * pay zero Docker cost even when the runner is Docker-aware.
947
- *
948
- * Dispose always runs one final label-filtered `docker ps` to catch
949
- * containers that were spawned during `.exec` but never asserted on —
950
- * tests still get leak-free cleanup even if they forget to reach into
951
- * a container.
864
+ * The visitor — the interaction vocabulary handed to a visit scenario.
865
+ * Every action auto-waits (playwright actionability); `see()` is the single
866
+ * synchronization primitive: it retries until the element is visible and
867
+ * fails at the timeout. There is no sleep and no conditional helper.
952
868
  */
953
- declare class CliResult extends BaseResult {
954
- private readonly commandOutput;
955
- private containersCache;
956
- private readonly dockerConfig?;
957
- private readonly testRunId?;
958
- private readonly transform?;
959
- constructor(options: {
960
- commandOutput: CliOutput;
961
- config: SpecificationConfig;
962
- dockerConfig?: DockerSpecConfig | undefined;
963
- testDir: string;
964
- testRunId?: string | undefined;
965
- transform?: ((text: string) => string) | undefined;
966
- workDir: string;
967
- });
968
- /** The process exit code. */
969
- get exitCode(): number;
970
- /** Accessor for the captured standard output with file-based assertions. */
971
- get stdout(): TextAccessor;
972
- /** Accessor for the captured standard error with file-based assertions. */
973
- get stderr(): TextAccessor;
974
- /** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
975
- get json(): JsonAccessor;
976
- /** Accessor for the temporary working directory the command ran in. */
977
- get filesystem(): FilesystemAccessor;
869
+ type Visitor = {
870
+ /** Check a checkbox or radio. */
871
+ check: (element: ElementRef) => Promise<void>;
872
+ /** Click the element. */
873
+ click: (element: ElementRef) => Promise<void>;
874
+ /** Fill a form field with a value. */
875
+ fill: (element: ElementRef, value: string) => Promise<void>;
978
876
  /**
979
- * Look up a container the command spawned during this run by the value of
980
- * its name-label (as declared in the `docker.nameLabel` of the
981
- * `specification.cli()` options).
982
- * First access triggers a one-shot `docker ps` + `docker inspect`
983
- * query and caches the result for the rest of the result's lifetime.
984
- * Tests that don't call this never touch Docker.
985
- *
986
- * Returns an accessor with `.exists === false` when no container
987
- * carries that name — callers can still assert absence without a try.
877
+ * Wait until the element is GONE — hidden, or never in the document. The
878
+ * absence primitive: `see()` cannot answer "the dialog closed", because an
879
+ * element that never appears and one that disappeared read the same to it.
988
880
  */
989
- container(name: string): ContainerAccessor;
990
- /** All captured container IDs. Triggers the same lazy query as `container()`. */
991
- get containerIds(): string[];
992
- [Symbol.asyncDispose](): Promise<void>;
993
- private ensureDockerAware;
994
- private loadContainers;
995
- }
881
+ gone: (element: ElementRef) => Promise<void>;
882
+ /** Navigate to a path of the site under test. */
883
+ goto: (path: string) => Promise<void>;
884
+ /** Hover the element. */
885
+ hover: (element: ElementRef) => Promise<void>;
886
+ /** Press a key (e.g. `Enter`). */
887
+ press: (key: string) => Promise<void>;
888
+ /** Wait until the element is visible — the only synchronization primitive. */
889
+ see: (element: ElementRef) => Promise<void>;
890
+ /** Select an option in a select field. */
891
+ select: (element: ElementRef, option: string) => Promise<void>;
892
+ };
893
+ /** The behavior of a visit — the When of the spec; assertions stay in the Then. */
894
+ type VisitScenario = (visitor: Visitor) => Promise<void>;
895
+ /** Per-visit options forwarded to the browser context. */
896
+ type BrowserOpenOptions = {
897
+ /**
898
+ * Extra origins the `external: 'block'` policy lets through — the
899
+ * declared stub backend the page legitimately fetches from.
900
+ */
901
+ allowedOrigins?: string[] | undefined;
902
+ /**
903
+ * Base URL of the site under test — the origin `goto()` resolves against
904
+ * and the boundary of the `external` policy.
905
+ */
906
+ baseUrl: string;
907
+ /**
908
+ * Pin the page's own `Date` at this instant before the first byte is
909
+ * parsed — the calendar the site's scripts read. `.clock()` on the chain.
910
+ */
911
+ clock?: string | undefined;
912
+ /**
913
+ * Cross-origin request policy. `'block'` aborts every request leaving
914
+ * the site under test (analytics, CDNs) — the browser-side analog of
915
+ * strict intercepts. `'allow'` lets them through (deployed-site mode).
916
+ */
917
+ external: 'allow' | 'block';
918
+ /** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
919
+ headers?: Record<string, string> | undefined;
920
+ /** The interaction scenario to run after load; the capture reflects the final state. */
921
+ scenario?: undefined | VisitScenario;
922
+ };
923
+ /**
924
+ * The rendered page captured by a browser visit — the FINAL state when a
925
+ * scenario ran. Extraction happens in-page (the browser IS the HTML
926
+ * parser); interpretation of the raw elements belongs to the model.
927
+ */
928
+ type BrowserPage = {
929
+ /** Console messages emitted while loading and interacting, in order. */
930
+ consoleMessages: BrowserConsoleMessage[];
931
+ /** Serialized DOM after rendering (`document.documentElement.outerHTML`). */
932
+ html: string;
933
+ /** Raw text content of every `application/ld+json` script, in DOM order. */
934
+ jsonLdBlocks: string[];
935
+ /** `<link>` elements of the head, in DOM order. */
936
+ links: BrowserLinkElement[];
937
+ /** `<meta>` elements of the head, in DOM order. */
938
+ metas: BrowserMetaElement[];
939
+ /** HTTP status of the main document response (0 when unavailable). */
940
+ status: number;
941
+ /** Rendered `document.body.innerText`. */
942
+ text: string;
943
+ /** The ARIA snapshot of the rendered `<body>` — the outline a golden pins. */
944
+ tree: string;
945
+ /** `document.title` after rendering. */
946
+ title: string;
947
+ /** Final URL after redirects and scenario navigation. */
948
+ url: string;
949
+ };
950
+ /**
951
+ * Abstract browser interface for the website specification runner.
952
+ * One implementation lives in `integrations/playwright/` — a single shared
953
+ * browser instance per runner; each `open()` gets a fresh, isolated context.
954
+ */
955
+ type BrowserPort = {
956
+ /** Close the shared browser instance (idempotent). */
957
+ close: () => Promise<void>;
958
+ /** Load `url` in a fresh context, run the scenario, capture the final page. */
959
+ open: (url: string, options: BrowserOpenOptions) => Promise<BrowserPage>;
960
+ };
996
961
  //#endregion
997
- //#region src/specification/facets/cli/literate.d.ts
962
+ //#region src/model/ports/device.port.d.ts
998
963
  /**
999
- * Running a `<case>.spec.yaml` document: one scenario, one working directory,
1000
- * one set of servers, every run executed and asserted.
964
+ * The element kinds a mobile screen can designate — the structural subset of
965
+ * {@link ElementRef} kinds that map onto the XCUITest accessibility tree.
966
+ * There is ONE element vocabulary across facets: `button('Bookmark')` works
967
+ * in a visit scenario and a mobile scenario alike. Landmarks have no iOS
968
+ * analog — passing one to a mobile verb refuses at runtime.
969
+ */
970
+ type MobileElementKind = 'button' | 'field' | 'testId' | 'text';
971
+ /**
972
+ * A user-facing element descriptor for mobile scenarios — pure data, built by
973
+ * the shared element vocabulary (`button()`, `field()`, `content()`,
974
+ * `testId()`, `within()`) and translated into iOS predicate strings by the
975
+ * device integration. Structurally the same ref type as the browser's, so the
976
+ * vocabulary stays single; kinds outside {@link MobileElementKind} (the ARIA
977
+ * landmarks) are refused at runtime with a message naming the boundary.
1001
978
  *
1002
- * The engine composes pieces that already exist rather than adding a second of
1003
- * anything — {@link copyPlan} for `fixture:`, the runner's own command adapter
1004
- * for each run, {@link ServeAdapter} for `serve:`, the `{{token}}` engine for
1005
- * every comparison, and {@link mergeTextPreservingPlaceholders} for the
1006
- * rewrite. What is new here is only the ORCHESTRATION the chain cannot express:
1007
- * several commands sharing one working directory, each judged the moment it
1008
- * ends — so what a run states about that directory is what IT left there, and
1009
- * the first run to disagree is the one the failure names.
979
+ * A descriptor must designate exactly ONE element when a verb ACTS on it
980
+ * (`tap`, `fill`) — see {@link MobileElementMatch} and CONVENTIONS W3.
981
+ * `see()` acts on nothing, so any visible match satisfies it: XCUITest trees
982
+ * legitimately duplicate a label across container and child.
1010
983
  */
984
+ type MobileElementRef = ElementRef;
1011
985
  /**
1012
- * A server a document may start by name (`serve: mcp`), registered once per app
1013
- * in the `serve` option of `specification.cli()`.
986
+ * One candidate captured when a descriptor matched more than one element —
987
+ * the evidence the ambiguity error enumerates so the author can disambiguate
988
+ * without opening the simulator.
1014
989
  */
1015
- type LiterateServeRegistration = {
1016
- /** Shell command that starts the server, run from the project root. */
1017
- command: string;
1018
- /** The variable the resolved URL is bound to in every run's child env. */
1019
- env: string;
1020
- /**
1021
- * Matched against the server's output; the FIRST capture group is the port
1022
- * it chose — named (`(?<port>\d+)`) or not, it is group 1 either way.
1023
- */
1024
- ready: RegExp;
1025
- /** Builds the URL bound to {@link env} from the announced port. */
1026
- url: (port: number) => string;
1027
- };
1028
- /** Per-call options for {@link runSpecDocument} / `cli.run()`. */
1029
- type LiterateRunFlags = {
1030
- /**
1031
- * Opt this document OUT of the update-mode rewrite. A frozen document is
1032
- * NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
1033
- * That is what makes a DELIBERATELY-WRONG `.spec.yaml` — one whose failure
1034
- * rendering is the subject of a negative test — survive an update run
1035
- * instead of being silently corrected into a passing file. The document's
1036
- * mirror of `toMatch(name, { frozen: true })`.
1037
- */
1038
- frozen?: boolean | undefined;
990
+ type MobileElementMatch = {
991
+ /** The accessibility identifier (`testId()` target), when one is set. */
992
+ identifier?: string;
993
+ /** The accessible label, whitespace-collapsed and truncated. */
994
+ label?: string;
995
+ /** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
996
+ type: string;
997
+ /** The element value (text-field content, adjustable value), when present. */
998
+ value?: string;
1039
999
  };
1040
- //#endregion
1041
- //#region src/specification/facets/integration/result.d.ts
1042
- /** What `.call()` produced: the value it returned, or the error it threw. */
1043
- type CallOutcome = {
1044
- error: unknown;
1045
- threw: boolean;
1046
- value: unknown;
1000
+ /**
1001
+ * The visitor — the interaction vocabulary handed to a mobile scenario.
1002
+ * Every verb auto-waits by polling until a visible match exists; acting
1003
+ * verbs then enforce exactly-one (W3), while `see()` — the single
1004
+ * synchronization primitive — is satisfied by any visible match. There is
1005
+ * no sleep and no conditional helper.
1006
+ */
1007
+ type MobileVisitor = {
1008
+ /** Fill a text field with a value. */
1009
+ fill: (element: MobileElementRef, value: string) => Promise<void>;
1010
+ /** Wait until the element is visible — the only synchronization primitive. */
1011
+ see: (element: MobileElementRef) => Promise<void>;
1012
+ /** Tap the element. */
1013
+ tap: (element: MobileElementRef) => Promise<void>;
1047
1014
  };
1048
- type CallResultOptions = BaseResultOptions & {
1049
- outcome: CallOutcome;
1015
+ /** The behavior of an open — the When of the spec; assertions stay in the Then. */
1016
+ type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
1017
+ /**
1018
+ * One node of the projected accessibility tree — the XCUITest page source
1019
+ * with its noise collapsed: unlabeled, identifier-less, valueless wrapper
1020
+ * nodes are dropped and their children hoisted, so the projection stays
1021
+ * stable and golden-friendly. Type names lose the `XCUIElementType` prefix.
1022
+ */
1023
+ type ScreenNode = {
1024
+ children?: ScreenNode[];
1025
+ /** The accessibility identifier, only when it differs from the label. */
1026
+ identifier?: string;
1027
+ label?: string;
1028
+ /** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
1029
+ type: string;
1030
+ value?: string;
1050
1031
  };
1051
1032
  /**
1052
- * What `result.value` is, decided by what the call returned: a string reads as
1053
- * a stream, anything else as JSON. `.call<T>()` already knows T, so the test
1054
- * reads one field (`result.value.value.ok`) without narrowing a union first.
1033
+ * The screen captured by a device open — the FINAL state when a scenario
1034
+ * ran. The tree is the projected page source; `texts` are the visible
1035
+ * labels/values in document order, consecutive duplicates collapsed.
1055
1036
  */
1056
- type CallValue<Returned> = Returned extends string ? TextAccessor : JsonAccessor<Returned>;
1037
+ type DeviceScreen = {
1038
+ texts: string[];
1039
+ tree: ScreenNode;
1040
+ };
1041
+ /** Per-open options forwarded to the device session. */
1042
+ type DeviceOpenOptions = {
1043
+ /** The app under test — terminated and relaunched for a fresh, deterministic state. */
1044
+ bundleId: string;
1045
+ /** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
1046
+ deepLink?: string | undefined;
1047
+ /** The interaction scenario to run after launch; the capture reflects the final state. */
1048
+ scenario?: MobileScenario | undefined;
1049
+ };
1057
1050
  /**
1058
- * The result of an in-process call — the integration facet's whole surface.
1059
- *
1060
- * Two readings, never both: `value` is what the call RETURNED, `error` is
1061
- * what it THREW. A module that refuses is specified the same way a module
1062
- * that answers is, and neither needs a `try`/`catch` in the test — which is
1063
- * the shape that used to make a refusal spec three times the size of the
1064
- * happy one, and the shape that quietly passes when nothing throws at all.
1065
- *
1066
- * Both are the package's ordinary accessors, so the golden mechanism reaches
1067
- * them, and `Returned` is what `.call<T>()` returned — `result.value.value` is
1068
- * that type, not `unknown`, so a one-field reading needs no golden file: `toMatch('<name>.json'|'<name>.txt')`, the `{{token}}` grammar,
1069
- * `{ frozen }` and `TEST_UPDATE=1` all work here exactly as on an HTTP
1070
- * response or a command's stdout.
1051
+ * How long a device session waits, in milliseconds. Every field is optional
1052
+ * and falls back to the framework's own default, so a runner states only the
1053
+ * wait it needs to move — the defaults suit a release build, and a project
1054
+ * that drives a DEV build pays for its bundler's cold boot.
1071
1055
  */
1072
- declare class CallResult<Returned = unknown> extends BaseResult {
1073
- private readonly outcome;
1074
- constructor(options: CallResultOptions);
1056
+ type DeviceTimeouts = {
1075
1057
  /**
1076
- * What the call THREW, as text — the message of an `Error`, the value
1077
- * itself when something else was thrown, and empty when it returned.
1078
- *
1079
- * Empty rather than absent on purpose: `await expect(result.error).toBeEmpty()`
1080
- * is how a spec says "and it did not refuse" — awaited, because the
1081
- * matcher answers a promise on every subject it takes, and a dropped one
1082
- * passes the test while the failure surfaces as an unhandled rejection
1083
- * (D2 refuses the bare form).
1058
+ * How long every verb polls for a visible match before refusing.
1059
+ * Default: `30_000` — enough for a cold app boot on a release bundle.
1084
1060
  */
1085
- get error(): TextAccessor;
1061
+ action?: number | undefined;
1086
1062
  /**
1087
- * What the call RETURNED. A string reads as a stream (`<name>.txt`),
1088
- * anything else as JSON (`<name>.json`) — the same two subjects every
1089
- * other facet's results are built from.
1090
- *
1091
- * A call that threw returns the empty value: what it produced is
1092
- * {@link CallResult.error}.
1063
+ * How long the FIRST session waits for WebDriverAgent to build and
1064
+ * launch on the simulator. Default: `240_000`.
1093
1065
  */
1094
- get value(): CallValue<Returned>;
1095
- }
1066
+ launch?: number | undefined;
1067
+ };
1068
+ /**
1069
+ * Abstract device interface for the mobile specification runner.
1070
+ * One implementation lives in `integrations/appium/` — a single driver
1071
+ * session per runner, created on the first `open()` and reused; each open
1072
+ * terminates and relaunches the app for a deterministic fresh state.
1073
+ */
1074
+ type DevicePort = {
1075
+ /** End the driver session (idempotent). */
1076
+ close: () => Promise<void>;
1077
+ /** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
1078
+ open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
1079
+ };
1096
1080
  //#endregion
1097
- //#region src/specification/facets/mobile/result.d.ts
1081
+ //#region src/facets/mobile/mobile.result.d.ts
1098
1082
  /** Result from a `.open()` action — the screen as the device saw it, final state. */
1099
1083
  declare class ScreenResult extends BaseResult {
1100
1084
  private readonly capturedScreen;
@@ -1115,7 +1099,7 @@ declare class ScreenResult extends BaseResult {
1115
1099
  get screen(): JsonAccessor;
1116
1100
  }
1117
1101
  //#endregion
1118
- //#region src/specification/facets/website/result.d.ts
1102
+ //#region src/facets/website/website.result.d.ts
1119
1103
  /** A raw HTTP exchange captured by `.fetch()` — redirects are NOT followed. */
1120
1104
  type FetchExchange = {
1121
1105
  body: string;
@@ -1174,32 +1158,113 @@ declare class PageResult extends BaseResult {
1174
1158
  /** The rendered DOM serialization as a text accessor. */
1175
1159
  get html(): TextAccessor;
1176
1160
  /**
1177
- * Every `application/ld+json` block of the page, parsed, as one JSON
1178
- * array — `expect(result.jsonLd).toMatch('article.jsonld.json')`.
1161
+ * Every `application/ld+json` block of the page, parsed, as one JSON
1162
+ * array — `expect(result.jsonLd).toMatch('article.jsonld.json')`.
1163
+ */
1164
+ get jsonLd(): JsonAccessor;
1165
+ /** `<link>` elements of the head, in DOM order. */
1166
+ get links(): BrowserLinkElement[];
1167
+ /** The content of a named meta — `meta('description')`, `meta('og:image')`. */
1168
+ meta(name: string): string | undefined;
1169
+ /** HTTP status of the main document response. */
1170
+ get status(): number;
1171
+ /** Rendered body text as a text accessor. */
1172
+ get content(): TextAccessor;
1173
+ /**
1174
+ * The ARIA snapshot of the rendered `<body>` — the page's outline, as the
1175
+ * accessibility tree draws it, and the one golden a rendered surface pins.
1176
+ * Deterministic where a screenshot is not, and the SAME dialect a component
1177
+ * spec asserts, so a page and the components inside it read alike.
1178
+ */
1179
+ get tree(): TextAccessor;
1180
+ /** The document title as a text accessor. */
1181
+ get title(): TextAccessor;
1182
+ /** Final URL after redirects. */
1183
+ get url(): string;
1184
+ }
1185
+ //#endregion
1186
+ //#region src/model/ports/isolation.port.d.ts
1187
+ /**
1188
+ * Strategy for isolating service state across parallel test workers.
1189
+ *
1190
+ * Each service handle provides an isolation strategy. The framework
1191
+ * calls `acquire()` once per vitest worker, `reset()` before each
1192
+ * `spec.run()`, and `release()` when the worker shuts down.
1193
+ *
1194
+ * Implement this interface to support new service types (e.g. MongoDB,
1195
+ * Elasticsearch, S3).
1196
+ */
1197
+ type IsolationStrategy = {
1198
+ /**
1199
+ * Create an isolated namespace for this worker.
1200
+ * Called once when the worker starts — e.g. clone a template database,
1201
+ * set a Redis key prefix.
1202
+ *
1203
+ * @param workerId - Unique identifier for this vitest worker.
1204
+ */
1205
+ acquire: (workerId: string) => Promise<void>;
1206
+ /**
1207
+ * Fast reset within the namespace between `spec.run()` calls.
1208
+ * E.g. truncate tables (without dropping the database).
1209
+ */
1210
+ reset: () => Promise<void>;
1211
+ /**
1212
+ * Tear down the isolated namespace.
1213
+ * Called once when the worker shuts down — e.g. drop the cloned database.
1214
+ */
1215
+ release: () => Promise<void>;
1216
+ };
1217
+ //#endregion
1218
+ //#region src/model/ports/service.port.d.ts
1219
+ /**
1220
+ * A service handle — returned by factory functions like postgres(), redis().
1221
+ * Mutable: connectionString is populated after the orchestrator starts containers.
1222
+ */
1223
+ type ServiceHandle = {
1224
+ /** Service type identifier. */
1225
+ readonly type: string;
1226
+ /**
1227
+ * The name this handle is known by. Left `null` until the orchestrator
1228
+ * assigns it at start time: it is the kebab-case form of the RECORD KEY,
1229
+ * so `{ analyticsDb: postgres() }` reports as `analytics-db` and reads its
1230
+ * init script from `docker/analytics-db/`. The key is the only name a
1231
+ * specification writes; a handle carries no second one of its own.
1179
1232
  */
1180
- get jsonLd(): JsonAccessor;
1181
- /** `<link>` elements of the head, in DOM order. */
1182
- get links(): BrowserLinkElement[];
1183
- /** The content of a named meta — `meta('description')`, `meta('og:image')`. */
1184
- meta(name: string): string | undefined;
1185
- /** HTTP status of the main document response. */
1186
- get status(): number;
1187
- /** Rendered body text as a text accessor. */
1188
- get content(): TextAccessor;
1233
+ serviceName: null | string;
1234
+ /** Default container port for this service type. */
1235
+ readonly defaultPort: number;
1236
+ /** Default Docker image for this service type. */
1237
+ readonly defaultImage: string;
1238
+ /** Environment variables to pass to the container. */
1239
+ readonly environment: Record<string, string>;
1240
+ /** Connection string — populated after start. */
1241
+ connectionString: string;
1242
+ /** Whether this service has been started. */
1243
+ started: boolean;
1244
+ /** Build the connection string from host and port. */
1245
+ buildConnectionString: (host: string, port: number) => string;
1246
+ /** Create a DatabasePort adapter (if this is a database). Returns null otherwise. */
1247
+ createDatabaseAdapter: () => DatabasePort | null;
1248
+ /** Verify the service is ready and accepting connections. Throws with context if not. */
1249
+ healthcheck: () => Promise<void>;
1189
1250
  /**
1190
- * The ARIA snapshot of the rendered `<body>` — the page's outline, as the
1191
- * accessibility tree draws it, and the one golden a rendered surface pins.
1192
- * Deterministic where a screenshot is not, and the SAME dialect a component
1193
- * spec asserts, so a page and the components inside it read alike.
1251
+ * Run initialization scripts (e.g., init.sql). Throws with SQL error
1252
+ * context if it fails.
1253
+ *
1254
+ * `dockerDir` is `<root>/docker` — where a service reads its `init.sql`
1255
+ * from, under its own name. `root` is the project root the specification resolved
1256
+ * (A9), handed down so a service that CACHES something writes it under the
1257
+ * project's own `.artifacts/`, never in a machine-global directory two
1258
+ * checkouts would share.
1194
1259
  */
1195
- get tree(): TextAccessor;
1196
- /** The document title as a text accessor. */
1197
- get title(): TextAccessor;
1198
- /** Final URL after redirects. */
1199
- get url(): string;
1200
- }
1260
+ initialize: (dockerDir: string, root: string) => Promise<void>;
1261
+ /** Reset state between tests (truncate tables, flush cache, etc.) */
1262
+ reset: () => Promise<void>;
1263
+ /** Get the isolation strategy for parallel test execution. */
1264
+ isolation: () => IsolationStrategy;
1265
+ };
1201
1266
  //#endregion
1202
- //#region src/specification/facets/_common/stub-backend.d.ts
1267
+ //#region src/model/chain/stub-backend.d.ts
1203
1268
  /** Options for the stub backend server. */
1204
1269
  type StubBackendOptions = {
1205
1270
  /**
@@ -1241,7 +1306,7 @@ declare class StubBackend {
1241
1306
  private record;
1242
1307
  }
1243
1308
  //#endregion
1244
- //#region src/specification/facets/_common/builder.d.ts
1309
+ //#region src/model/chain/builder.d.ts
1245
1310
  /** A named job that can be triggered via jobs.trigger(). */
1246
1311
  type JobHandle = {
1247
1312
  name: string;
@@ -1321,17 +1386,6 @@ type SpecificationConfig = {
1321
1386
  * (`env: frozen`). Declared once per app in `specification.cli()`.
1322
1387
  */
1323
1388
  envSets?: Record<string, CliEnv> | undefined;
1324
- /**
1325
- * When set, `.clock()` is unavailable on this runner and throws this
1326
- * reason immediately (compose mode — the calendar it would pin is this
1327
- * process's, and the app runs in a container).
1328
- */
1329
- clockDisabledReason?: string | undefined;
1330
- /**
1331
- * When set, `.intercept()` is unavailable on this runner and throws this
1332
- * reason immediately (compose mode — MSW is in-process, CONVENTIONS I3).
1333
- */
1334
- interceptDisabledReason?: string | undefined;
1335
1389
  jobs?: JobHandle[] | undefined;
1336
1390
  /** The project root — the working directory a document's `serve:` command runs from. */
1337
1391
  root?: string | undefined;
@@ -1391,8 +1445,18 @@ type ApiSpecification<DatabaseKey extends string = string> = {
1391
1445
  };
1392
1446
  /**
1393
1447
  * The `jobs` facet — job chain entry handed out by `specification.jobs()`.
1394
- * Jobs run in-process by definition (CONVENTIONS A5/A8).
1448
+ * Jobs run in-process by definition (CONVENTIONS A8).
1449
+ */
1450
+ /**
1451
+ * What a triggered job HANDS BACK.
1452
+ *
1453
+ * A job writes to a database, a cache, a queue — never to a working directory
1454
+ * the spec then walks, because a job has none: it runs in this process, where
1455
+ * the only filesystem is the repository's. So the two accessors that read one
1456
+ * (`file()`, `directory()`) are not on this result. A pipeline that really
1457
+ * produces files is a binary, and `specification.cli()` is its facet.
1395
1458
  */
1459
+ type JobsResult = Omit<BaseResult, 'directory' | 'file'>;
1396
1460
  type JobsSpecification<DatabaseKey extends string = string> = {
1397
1461
  /** Pin the job's `Date` at `iso` for this chain — the calendar the job reads. */
1398
1462
  clock: (iso: string) => JobsSpecification<DatabaseKey>;
@@ -1403,7 +1467,7 @@ type JobsSpecification<DatabaseKey extends string = string> = {
1403
1467
  database?: DatabaseKey;
1404
1468
  }) => JobsSpecification<DatabaseKey>;
1405
1469
  /** Execute the named job registered via the `jobs` option and resolve with the result. */
1406
- trigger: (name: string) => Promise<BaseResult>;
1470
+ trigger: (name: string) => Promise<JobsResult>;
1407
1471
  };
1408
1472
  /**
1409
1473
  * The `integration` facet — the in-process chain handed out by
@@ -1515,92 +1579,66 @@ type MobileSpecification = {
1515
1579
  open: (deepLink?: string, scenario?: MobileScenario) => Promise<ScreenResult>;
1516
1580
  };
1517
1581
  //#endregion
1518
- //#region src/specification/facets/_common/result/directory.d.ts
1519
- /**
1520
- * Read-only accessor for a directory produced by a spec action.
1521
- *
1522
- * Assertions go through `expect()` (async — they walk the disk):
1523
- * `await expect(result.directory('out')).toMatch('scaffold/out')`
1524
- * compares the tree against the fixture directory `_expected/<name>/`.
1525
- */
1526
- declare class DirectoryAccessor {
1527
- /** @internal Ref-capture scope shared by the current spec execution. */
1528
- readonly captures: CaptureScope;
1529
- /** @internal Absolute path of the directory under assertion. */
1530
- readonly root: string;
1531
- /** @internal Test-file directory — fixture resolution root for matchers. */
1532
- readonly testDir: string;
1533
- constructor(absPath: string, testDir: string, captures?: CaptureScope);
1534
- /** List all files (recursively) under the directory, sorted. */
1535
- files(options?: {
1536
- ignore?: string[];
1537
- }): Promise<string[]>;
1538
- }
1539
- //#endregion
1540
- //#region src/specification/facets/_common/result/table.d.ts
1582
+ //#region src/facets/cli/cli.result.d.ts
1541
1583
  /**
1542
- * Read-only accessor for a database table after a specification run.
1543
- *
1544
- * Assertions go through `expect()` (async — they query the database):
1584
+ * Result from a command action (`.exec()`).
1545
1585
  *
1546
- * await expect(result.table('users', { database: 'db' })).toMatchRows({
1547
- * columns: ['name'],
1548
- * rows: [['Alice']],
1549
- * });
1550
- * await expect(result.table('users', { database: 'db' })).toBeEmpty();
1551
- */
1552
- declare class TableAccessor {
1553
- /** @internal Ref-capture scope shared by the current spec execution. */
1554
- readonly captures: CaptureScope;
1555
- /** @internal */
1556
- private readonly db;
1557
- /** The table name this accessor reads. */
1558
- readonly name: string;
1559
- constructor(name: string, db: DatabasePort, captures?: CaptureScope);
1560
- /** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
1561
- query(columns: string[]): Promise<unknown[][]>;
1562
- }
1563
- //#endregion
1564
- //#region src/specification/facets/_common/result/result.d.ts
1565
- /** Read-only handle to a single file produced by a spec action. */
1566
- type FileAccessor = {
1567
- /** The UTF-8 text content. Throws if the file does not exist. */
1568
- readonly content: string;
1569
- readonly exists: boolean;
1570
- /** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
1571
- grep: (pattern: string) => TextAccessor;
1572
- };
1573
- type BaseResultOptions = {
1574
- config: SpecificationConfig;
1575
- testDir: string;
1576
- workDir?: string | undefined;
1577
- };
1578
- /**
1579
- * Base result - common accessors available after any action type.
1580
- * Extended by HttpResult, CliResult, and used directly by job results.
1586
+ * When the runner was configured with a `docker` option, the result also
1587
+ * exposes container accessors and participates in `Symbol.asyncDispose`
1588
+ * cleanup. The Docker shell-outs are **lazy**: a test that never calls
1589
+ * `.container(...)` never queries the Docker daemon, so command-only tests
1590
+ * pay zero Docker cost even when the runner is Docker-aware.
1581
1591
  *
1582
- * Every result carries a fresh {@link CaptureScope}: `match.ref()` captures
1583
- * and `{{type#ref}}` placeholders are scoped to one spec execution.
1592
+ * Dispose always runs one final label-filtered `docker ps` to catch
1593
+ * containers that were spawned during `.exec` but never asserted on —
1594
+ * tests still get leak-free cleanup even if they forget to reach into
1595
+ * a container.
1584
1596
  */
1585
- declare class BaseResult {
1586
- /** @internal Ref-capture scope shared by every assertion on this result. */
1587
- readonly captures: CaptureScope;
1588
- protected config: SpecificationConfig;
1589
- protected testDir: string;
1590
- protected workDir?: string | undefined;
1591
- constructor(options: BaseResultOptions);
1592
- /** Access a directory (relative to the working directory) for snapshot assertions. */
1593
- directory(path?: string): DirectoryAccessor;
1594
- /** Access a single file (relative to the working directory) for content assertions. */
1595
- file(path: string): FileAccessor;
1596
- /** Access a database table for row-level assertions via expect() matchers. */
1597
- table(tableName: string, options?: {
1598
- database?: string;
1599
- }): TableAccessor;
1600
- private resolveDatabase;
1597
+ declare class CliResult extends BaseResult {
1598
+ private readonly commandOutput;
1599
+ private containersCache;
1600
+ private readonly dockerConfig?;
1601
+ private readonly testRunId?;
1602
+ private readonly transform?;
1603
+ constructor(options: {
1604
+ commandOutput: CliOutput;
1605
+ config: SpecificationConfig;
1606
+ dockerConfig?: DockerSpecConfig | undefined;
1607
+ testDir: string;
1608
+ testRunId?: string | undefined;
1609
+ transform?: ((text: string) => string) | undefined;
1610
+ workDir: string;
1611
+ });
1612
+ /** The process exit code. */
1613
+ get exitCode(): number;
1614
+ /** Accessor for the captured standard output with file-based assertions. */
1615
+ get stdout(): TextAccessor;
1616
+ /** Accessor for the captured standard error with file-based assertions. */
1617
+ get stderr(): TextAccessor;
1618
+ /** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
1619
+ get json(): JsonAccessor;
1620
+ /** Accessor for the temporary working directory the command ran in. */
1621
+ get filesystem(): FilesystemAccessor;
1622
+ /**
1623
+ * Look up a container the command spawned during this run by the value of
1624
+ * its name-label (as declared in the `docker.nameLabel` of the
1625
+ * `specification.cli()` options).
1626
+ * First access triggers a one-shot `docker ps` + `docker inspect`
1627
+ * query and caches the result for the rest of the result's lifetime.
1628
+ * Tests that don't call this never touch Docker.
1629
+ *
1630
+ * Returns an accessor with `.exists === false` when no container
1631
+ * carries that name — callers can still assert absence without a try.
1632
+ */
1633
+ container(name: string): ContainerAccessor;
1634
+ /** All captured container IDs. Triggers the same lazy query as `container()`. */
1635
+ get containerIds(): string[];
1636
+ [Symbol.asyncDispose](): Promise<void>;
1637
+ private ensureDockerAware;
1638
+ private loadContainers;
1601
1639
  }
1602
1640
  //#endregion
1603
- //#region src/integrations/docker/container-accessor.d.ts
1641
+ //#region src/seams/docker/container-accessor.d.ts
1604
1642
  /**
1605
1643
  * Assertion accessor for a single Docker container captured by the docker()
1606
1644
  * spec mode. Mirrors the shape of {@link CliResult} so tests use the same
@@ -1653,31 +1691,7 @@ declare class ContainerAccessor {
1653
1691
  private containerExec;
1654
1692
  }
1655
1693
  //#endregion
1656
- //#region src/specification/facets/_common/result/match-options.d.ts
1657
- /**
1658
- * The per-call option both `toMatch` implementations honour.
1659
- *
1660
- * A golden is read from disk under node and through a server command from
1661
- * inside a page, and BOTH builds must answer `{ frozen: true }` the same way —
1662
- * so the option is declared here, where the node matchers and the page's
1663
- * matchers can each reach it, rather than in the runner's own folder, which a
1664
- * seam may not import (rule I1).
1665
- */
1666
- /**
1667
- * Per-call options for the fixture-file `toMatch` subjects. `frozen` opts a
1668
- * single fixture OUT of update-mode rewriting: a frozen fixture is NEVER
1669
- * written under `TEST_UPDATE=1` (or vitest `-u`) — in update mode a frozen
1670
- * mismatch still throws its diff, and a frozen missing fixture still throws its
1671
- * "does not exist" error. This is what makes a DELIBERATELY-WRONG fixture (the
1672
- * subject of a negative test that asserts the mismatch/error rendering)
1673
- * survivable across update runs instead of being silently overwritten with the
1674
- * actual output.
1675
- */
1676
- type MatchFixtureOptions = {
1677
- frozen?: boolean;
1678
- };
1679
- //#endregion
1680
- //#region src/integrations/vitest-browser/ui.d.ts
1694
+ //#region src/seams/vitest-browser/ui.d.ts
1681
1695
  /**
1682
1696
  * A vanilla DOM subject: a function handed the container it must fill. Its
1683
1697
  * optional return is the teardown, the way a DOM library states one.
@@ -1701,7 +1715,7 @@ declare module 'vitest/browser' {
1701
1715
  }
1702
1716
  }
1703
1717
  //#endregion
1704
- //#region src/integrations/vitest-browser/vitest-browser.adapter.d.ts
1718
+ //#region src/seams/vitest-browser/vitest-browser.adapter.d.ts
1705
1719
  /** What a mounted subject offers the chain, React or vanilla DOM alike. */
1706
1720
  type MountedSurface = {
1707
1721
  /** The element the subject was mounted into. */
@@ -1725,7 +1739,7 @@ declare function componentVerbs(surface: MountedSurface): {
1725
1739
  unmount: () => Promise<void>;
1726
1740
  };
1727
1741
  //#endregion
1728
- //#region src/specification/facets/component/rendered-text.d.ts
1742
+ //#region src/facets/component/rendered-text.d.ts
1729
1743
  /**
1730
1744
  * A stream captured INSIDE the page.
1731
1745
  *
@@ -1740,7 +1754,7 @@ declare class RenderedText extends TextAccessor {
1740
1754
  readonly capturedInPage: true;
1741
1755
  }
1742
1756
  //#endregion
1743
- //#region src/specification/facets/component/component.result.d.ts
1757
+ //#region src/facets/component/component.result.d.ts
1744
1758
  /**
1745
1759
  * What a render resolves to — the component as the page saw it, after the
1746
1760
  * scenario ran.
@@ -1770,7 +1784,7 @@ declare class RenderResult {
1770
1784
  });
1771
1785
  }
1772
1786
  //#endregion
1773
- //#region src/specification/facets/component/component.types.d.ts
1787
+ //#region src/facets/component/component.types.d.ts
1774
1788
  /**
1775
1789
  * The component facet's shape, stated where BOTH runtimes can read it.
1776
1790
  *
@@ -1824,26 +1838,29 @@ type ComponentChain = {
1824
1838
  wrap: (wrapper: (ui: ComponentUi) => ComponentUi) => ComponentChain;
1825
1839
  };
1826
1840
  //#endregion
1827
- //#region src/specification/facets/website/elements.d.ts
1841
+ //#region src/model/elements/elements.d.ts
1828
1842
  /**
1829
1843
  * The element vocabulary — user-facing descriptors for visit scenarios.
1830
1844
  * Reads like English (`click(link('Articles'))`), translates to
1831
1845
  * accessibility-first locators in the browser integration. CSS/XPath is
1832
1846
  * deliberately not expressible; `testId()` is the single escape hatch.
1833
1847
  *
1834
- * A descriptor must designate exactly ONE element (CONVENTIONS W3). Two knobs
1835
- * narrow it, in this order of preference:
1836
- *
1837
- * 1. `within(scope, target)` — search inside a landmark, the way a person
1838
- * would say "the Articles link *in the nav*";
1839
- * 2. `{ exact: true }` — match the accessible name whole rather than as a
1840
- * substring, when two names genuinely overlap.
1848
+ * A descriptor must designate exactly ONE element (CONVENTIONS W3). A name
1849
+ * matches the accessible name WHOLE, and `within(scope, target)` is how an
1850
+ * overlap is narrowed — the way a person would say "the Articles link *in the
1851
+ * nav*".
1841
1852
  */
1842
1853
  /** Options accepted by every named descriptor. */
1843
1854
  type ElementOptions = {
1844
1855
  /**
1845
- * Match the accessible name as a whole string. Default is substring —
1846
- * `link('Articles')` also matches "Read Articles".
1856
+ * Match the accessible name as a whole string. `true` by default since
1857
+ * 16.0.
1858
+ *
1859
+ * A substring match is what a person does NOT mean: `link('Articles')`
1860
+ * designated "Read Articles" as readily as "Articles", so a test could pass
1861
+ * for years against the element beside the one it named. `{ exact: false }`
1862
+ * is the opt-out, for a name that genuinely carries a variable part the
1863
+ * test does not control.
1847
1864
  */
1848
1865
  exact?: boolean;
1849
1866
  };
@@ -1958,7 +1975,7 @@ declare const selected: (element: ElementRef) => ElementRef;
1958
1975
  */
1959
1976
  declare const valued: (element: ElementRef, value: string) => ElementRef;
1960
1977
  //#endregion
1961
- //#region src/specification/contracts/filters.d.ts
1978
+ //#region src/model/contracts/filters.d.ts
1962
1979
  /**
1963
1980
  * A text filter on a provider request builder (`openai.chat({ user })`,
1964
1981
  * `anthropic.messages({ system })`, …).
@@ -1976,7 +1993,7 @@ declare const valued: (element: ElementRef, value: string) => ElementRef;
1976
1993
  */
1977
1994
  type TextFilter = Matcher | RegExp | string;
1978
1995
  //#endregion
1979
- //#region src/integrations/anthropic/anthropic.d.ts
1996
+ //#region src/seams/anthropic/anthropic.d.ts
1980
1997
  type AnthropicMessagesFilter = {
1981
1998
  model?: TextFilter;
1982
1999
  system?: TextFilter;
@@ -2008,7 +2025,7 @@ declare const anthropic: {
2008
2025
  timeout(): ContractResponse;
2009
2026
  };
2010
2027
  //#endregion
2011
- //#region src/specification/contracts/http.d.ts
2028
+ //#region src/model/contracts/http.d.ts
2012
2029
  /**
2013
2030
  * Request filters for the generic HTTP provider. Every field is a subset
2014
2031
  * constraint — a request matches when all provided fields match.
@@ -2111,7 +2128,7 @@ declare const http: {
2111
2128
  text(body: string, init?: HttpResponseInit): ContractResponse;
2112
2129
  };
2113
2130
  //#endregion
2114
- //#region src/integrations/openai/openai.d.ts
2131
+ //#region src/seams/openai/openai.d.ts
2115
2132
  type OpenAIChatFilter = {
2116
2133
  model?: TextFilter;
2117
2134
  system?: TextFilter;
@@ -2169,7 +2186,7 @@ declare const openai: {
2169
2186
  timeout(): ContractResponse;
2170
2187
  };
2171
2188
  //#endregion
2172
- //#region src/vitest/mock-of.d.ts
2189
+ //#region src/model/doubles/mock-of.d.ts
2173
2190
  /** How much of the port the double answers for. */
2174
2191
  type MockOfOptions = {
2175
2192
  /**
@@ -2183,7 +2200,7 @@ type MockOfOptions = {
2183
2200
  };
2184
2201
  /**
2185
2202
  * A typed double for an injected port — the fourth rung of the doubles ladder
2186
- * (docs/12-conventions.md), and the only one a module test may build itself.
2203
+ * (docs/18-conventions.md), and the only one a module test may build itself.
2187
2204
  *
2188
2205
  * The type argument is the port: `T` is constrained to `object` and has no
2189
2206
  * default, so a double is always asked for something. A call with no argument
@@ -2200,21 +2217,7 @@ declare function mockOf<T extends object>(options?: MockOfOptions): DeepMockProx
2200
2217
  /** The factory's own shape — kept as a name so a consumer can annotate with it. */
2201
2218
  type MockPort = typeof mockOf;
2202
2219
  //#endregion
2203
- //#region src/vitest/mock-of-date.d.ts
2204
- /** Interface for freezing and resetting the global Date in tests. */
2205
- type MockDatePort = {
2206
- /** Restore the real Date object. */
2207
- reset: () => void;
2208
- /** Freeze `Date.now()` and `new Date()` to the given value. */
2209
- set: (date: Date | number | string) => void;
2210
- };
2211
- /**
2212
- * Freeze or reset the global Date for deterministic time-dependent tests.
2213
- * Wraps the `mockdate` package.
2214
- */
2215
- declare const mockOfDate: MockDatePort;
2216
- //#endregion
2217
- //#region src/vitest/clock.d.ts
2220
+ //#region src/model/clock/clock.d.ts
2218
2221
  /**
2219
2222
  * The one time primitive.
2220
2223
  *
@@ -2269,7 +2272,7 @@ declare const clock: {
2269
2272
  run(iso?: string): PinnedClock;
2270
2273
  };
2271
2274
  //#endregion
2272
- //#region src/specification/assertions/required.d.ts
2275
+ //#region src/model/assertions/required.d.ts
2273
2276
  /**
2274
2277
  * The value, or a failure that says what was missing and why it mattered.
2275
2278
  *
@@ -2286,7 +2289,7 @@ declare const clock: {
2286
2289
  */
2287
2290
  declare function required<T>(value: null | T | undefined, why: string): T;
2288
2291
  //#endregion
2289
- //#region src/specification/assertions/wait-until.d.ts
2292
+ //#region src/model/assertions/wait-until.d.ts
2290
2293
  /** How long to wait, and how often to look. */
2291
2294
  type WaitUntilOptions = {
2292
2295
  /** Milliseconds between two readings. Default 50. */
@@ -2325,7 +2328,7 @@ declare function waitUntil(predicate: () => boolean | Promise<boolean>, options?
2325
2328
  //#endregion
2326
2329
  //#region src/surface.d.ts
2327
2330
  declare module 'vitest' {
2328
- interface Assertion<T = any> {
2331
+ interface Assertion<R extends Promise<void> | void = void, T = unknown> {
2329
2332
  /**
2330
2333
  * Assert the subject is empty — zero rows for a table (async), an
2331
2334
  * empty stream for a text accessor (console, errors, stdout).
@@ -2349,9 +2352,9 @@ declare module 'vitest' {
2349
2352
  * Use it for a deliberately-wrong fixture whose diff/error rendering is
2350
2353
  * the behaviour under test.
2351
2354
  */
2352
- toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends {
2355
+ toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> & R : T extends {
2353
2356
  readonly capturedInPage: true;
2354
- } ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => void : (expected: RegExp | string, options?: MatchFixtureOptions) => void;
2357
+ } ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> & R : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => R : (expected: RegExp | string, options?: MatchFixtureOptions) => R;
2355
2358
  /**
2356
2359
  * Assert the table contains exactly the given rows for the given
2357
2360
  * columns. Cells accept `match.*` dynamic-value matchers. Async —
@@ -2364,17 +2367,39 @@ declare module 'vitest' {
2364
2367
  }
2365
2368
  }
2366
2369
  //#endregion
2367
- //#region src/specification/facets/_common/orchestrator.d.ts
2370
+ //#region src/model/chain/reporter.d.ts
2371
+ /**
2372
+ * What the subject IS, for the line the startup report closes on.
2373
+ *
2374
+ * Four shapes, because there are four: an app built and served inside this
2375
+ * process (api), a child process the runner started and polls (a site's dev
2376
+ * server), a deployment already running somewhere (`url`), and a subject that
2377
+ * is not an app at all — a binary, a pipeline, a module, a screen — which gets
2378
+ * no line rather than a wrong one.
2379
+ */
2380
+ type AppInfo = {
2381
+ /** The command, for a child process. */
2382
+ command?: string | undefined;
2383
+ type: 'http' | 'in-process' | 'none' | 'process';
2384
+ url?: string | undefined;
2385
+ };
2386
+ //#endregion
2387
+ //#region src/model/chain/orchestrator.d.ts
2368
2388
  type OrchestratorOptions = {
2369
2389
  /**
2370
- * Named infrastructure record. Keys become the database vocabulary of the
2371
- * spec (`.seed()` / `.table()` `database` option) and drive the compose
2372
- * binding: a handle with no explicit `composeService` links to the compose
2373
- * service named exactly like its key, else the kebab-case conversion of the
2374
- * key (`analyticsDb` → `analytics-db`) — see {@link resolveComposeBinding}.
2390
+ * What the subject IS — the line the startup report closes on. Defaults to
2391
+ * the api shape, which is the only facet that serves an app in this
2392
+ * process; a facet whose subject is a binary, a pipeline or a screen says
2393
+ * `{ type: 'none' }` and gets no line rather than a wrong one.
2394
+ */
2395
+ subject?: AppInfo | undefined;
2396
+ /**
2397
+ * Named infrastructure record. Keys are the database vocabulary of the spec
2398
+ * (`.seed()` / `.table()` `database` option) and the name each handle is
2399
+ * reported and initialised under: the kebab-case form of the key is the
2400
+ * directory its init script lives in (`analyticsDb` → `docker/analytics-db/`).
2375
2401
  */
2376
2402
  services: Record<string, ServiceHandle>;
2377
- mode: 'e2e' | 'integration';
2378
2403
  /**
2379
2404
  * The project root, already resolved. REQUIRED: the constructors derive it
2380
2405
  * by walking up from the calling specification file (CONVENTIONS A9), and
@@ -2384,68 +2409,40 @@ type OrchestratorOptions = {
2384
2409
  * launched, which in a workspace is rarely the same directory.
2385
2410
  */
2386
2411
  root: string;
2387
- /** Compose project name — used for per-worker stack isolation. */
2388
- projectName?: string;
2389
2412
  };
2390
2413
  /**
2391
- * Orchestrator for test infrastructure.
2392
- * Integration: starts services via testcontainers.
2393
- * E2E: runs full docker compose up.
2414
+ * The infrastructure a specification declares, started and stopped as a unit.
2415
+ *
2416
+ * Every declared handle becomes a real thing: a container through
2417
+ * testcontainers, or an embedded service (SQLite) that needs no container. The
2418
+ * record KEY is the only name in play — it is the spec's database vocabulary,
2419
+ * the name the startup report prints, and, kebab-cased, the directory the
2420
+ * service reads its init script from.
2421
+ *
2422
+ * It is internal wiring: the constructors drive it, and no consumer names it.
2394
2423
  */
2395
2424
  declare class Orchestrator {
2396
2425
  private readonly services;
2397
- private readonly mode;
2398
2426
  private readonly root;
2399
- private readonly projectName;
2427
+ private readonly subject;
2400
2428
  private running;
2401
- private composeStack;
2402
- private composeHandles;
2403
2429
  private started;
2404
2430
  constructor(options: OrchestratorOptions);
2405
2431
  /**
2406
- * Bind each declared handle to a compose service (CONVENTIONS A6): a record
2407
- * key resolves to the service named exactly like it, else the kebab-case
2408
- * conversion of the key; an explicit `composeService` wins. Throws on an
2409
- * ambiguous binding (both names present). Runs once compose config is known
2410
- * so the two-step resolution can consult the real service list.
2411
- */
2412
- private resolveBindings;
2413
- /**
2414
- * Start declared services via testcontainers (integration mode).
2432
+ * Start every declared service.
2415
2433
  * Phase 1: start all containers in parallel (the slow part).
2416
2434
  * Phase 2: wire connections, healthcheck, and init sequentially (fast).
2417
2435
  */
2418
2436
  start(): Promise<void>;
2419
- /**
2420
- * Stop testcontainers (integration mode).
2421
- */
2437
+ /** Stop every container this stack started. */
2422
2438
  stop(): Promise<void>;
2423
- /**
2424
- * Start full docker compose stack (e2e mode).
2425
- * Auto-detects infra services and creates handles for them.
2426
- */
2427
- startCompose(): Promise<void>;
2428
- /**
2429
- * Stop docker compose stack (e2e mode).
2430
- */
2431
- stopCompose(): Promise<void>;
2432
- /**
2433
- * Get the default database — the first declared handle that is one.
2434
- */
2439
+ /** The default database — the first declared handle that is one. */
2435
2440
  getDatabase(): DatabasePort | null;
2436
- /**
2437
- * Get all database services keyed by their record key (declared services)
2438
- * or compose service name (stack-detected services).
2439
- */
2441
+ /** Every database service, keyed by its record key. */
2440
2442
  getDatabases(): Map<string, DatabasePort>;
2441
- private allHandles;
2442
- /**
2443
- * Get app URL from compose (e2e mode).
2444
- */
2445
- getAppUrl(): null | string;
2446
2443
  }
2447
2444
  //#endregion
2448
- //#region src/specification/facets/_common/services.d.ts
2445
+ //#region src/model/chain/services.d.ts
2449
2446
  /**
2450
2447
  * Infrastructure services declared as a named record. Keys become the typed
2451
2448
  * vocabulary of the whole spec: the server factory receives the same record,
@@ -2455,41 +2452,29 @@ type ServiceRecord = Record<string, ServiceHandle>;
2455
2452
  /** Keys of a services record whose handles are databases. */
2456
2453
  type DatabaseKeys<Services extends ServiceRecord> = { [K in keyof Services]: Services[K] extends DatabasePort ? K & string : never; }[keyof Services];
2457
2454
  //#endregion
2458
- //#region src/specification/facets/api/start-api.d.ts
2455
+ //#region src/facets/api/api.specification.d.ts
2459
2456
  /** Any object with a request method compatible with Hono's app.request(). */
2460
2457
  type HonoApp = {
2461
2458
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
2462
2459
  };
2463
- /** Execution mode — exists ONLY on `specification.api()` (CONVENTIONS A5). */
2464
- type SpecificationMode = 'compose' | 'node';
2465
2460
  /** Options for {@link startApi | specification.api}. */
2466
2461
  type ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
2467
2462
  /**
2468
- * Execution mode override. Resolution: `options.mode` >
2469
- * `process.env.TEST_MODE` > `'node'`. Never hardcode this in a
2470
- * specification file when `server` is defined — set it per vitest
2471
- * project via `env: { TEST_MODE: 'compose' }` (CONVENTIONS A5).
2472
- */
2473
- mode?: SpecificationMode;
2474
- /**
2475
- * Project root override for compose detection and init scripts. When
2476
- * absent, the root is auto-discovered by walking up from the calling
2477
- * specification file to the first directory containing
2478
- * `docker/compose.test.yaml`, else the first containing `package.json`
2479
- * (CONVENTIONS A9).
2463
+ * Project root override for init scripts and artefact paths. When absent,
2464
+ * the root is auto-discovered by walking up from the calling specification
2465
+ * file to the first directory containing `package.json` (CONVENTIONS A9).
2480
2466
  */
2481
2467
  root?: string;
2482
2468
  /**
2483
2469
  * The app factory — receives the started services record (fully typed)
2484
- * and returns the Hono app. Required in node mode, ignored in compose
2485
- * mode (the app runs as a compose service there).
2470
+ * and returns the Hono app.
2486
2471
  */
2487
- server?: (services: Services) => HonoApp;
2472
+ server: (services: Services) => HonoApp;
2488
2473
  /**
2489
2474
  * Named infrastructure record. Keys become the `database` vocabulary of
2490
- * `.seed()` / `.table()` and drive the compose binding: a handle with no
2491
- * `composeService` option links to the compose service named exactly like
2492
- * its key, else the kebab-case conversion of the key (CONVENTIONS A6).
2475
+ * `.seed()` / `.table()`, and, kebab-cased, the folder each service reads
2476
+ * its init script from (`{ analyticsDb: postgres() }` →
2477
+ * `docker/analytics-db/init.sql`).
2493
2478
  */
2494
2479
  services?: Services;
2495
2480
  };
@@ -2508,12 +2493,10 @@ type ApiHandle<DatabaseKey extends string = string> = {
2508
2493
  * usable with `await expect(...).toBeRunning()` and read accessors.
2509
2494
  */
2510
2495
  docker: (containerId: string) => ContainerAccessor;
2511
- /** The orchestrator managing the test infrastructure lifecycle. */
2512
- orchestrator: Orchestrator;
2513
2496
  };
2514
2497
  declare function startApi<Services extends ServiceRecord>(options: ApiSpecificationOptions<Services>): Promise<ApiHandle<DatabaseKeys<Services>>>;
2515
2498
  //#endregion
2516
- //#region src/specification/facets/cli/start-cli.d.ts
2499
+ //#region src/facets/cli/cli.specification.d.ts
2517
2500
  /** Options for {@link startCli | specification.cli}. */
2518
2501
  type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
2519
2502
  /**
@@ -2549,9 +2532,9 @@ type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
2549
2532
  serve?: Record<string, LiterateServeRegistration>;
2550
2533
  /**
2551
2534
  * Project-root override (CONVENTIONS A9) — the single meaning of `root`:
2552
- * it anchors compose detection and local-bin resolution for the tested
2553
- * binary. It is NOT a fixtures root; `.fixture()` resolves feature-local
2554
- * or `$FIXTURES/` paths on its own.
2535
+ * it anchors the local-bin resolution of the tested binary, in place of
2536
+ * the walk to the nearest `package.json`. It is NOT a fixtures root;
2537
+ * `.fixture()` resolves feature-local or `$FIXTURES/` paths on its own.
2555
2538
  */
2556
2539
  root?: string;
2557
2540
  /**
@@ -2589,7 +2572,7 @@ type CliHandle<DatabaseKey extends string = string> = {
2589
2572
  };
2590
2573
  declare function startCli<Services extends ServiceRecord>(bin: string, options?: CliSpecificationOptions<Services>): Promise<CliHandle<DatabaseKeys<Services>>>;
2591
2574
  //#endregion
2592
- //#region src/specification/facets/integration/start-integration.d.ts
2575
+ //#region src/facets/integration/integration.specification.d.ts
2593
2576
  /** Options for {@link startIntegration | specification.integration}. */
2594
2577
  type IntegrationSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
2595
2578
  /** Project-root override — see the `root` option of `specification.api()`. */
@@ -2619,19 +2602,20 @@ type IntegrationHandle<Services extends ServiceRecord = ServiceRecord, DatabaseK
2619
2602
  *
2620
2603
  * The fork is the SUBJECT. A module alone is a module test, beside its code,
2621
2604
  * with no runner at all. A module that needs a real database, or whose oracle
2622
- * is a golden file, is this — and it used to be neither: a `specs/` folder
2623
- * with plain vitest, a hand-built sqlite template, a `beforeAll` that started
2624
- * a container, and a `toMatchSnapshot` where the package has a golden engine.
2605
+ * is a golden file, is this one — which is what spares it the shape it would
2606
+ * otherwise take: plain vitest under a `specs/` folder, a hand-built sqlite
2607
+ * template, a `beforeAll` starting a container, and a `toMatchSnapshot` beside
2608
+ * a framework that owns a golden engine.
2625
2609
  */
2626
2610
  declare function startIntegration<Services extends ServiceRecord>(options?: IntegrationSpecificationOptions<Services>): Promise<IntegrationHandle<Services, DatabaseKeys<Services>>>;
2627
2611
  //#endregion
2628
- //#region src/specification/facets/jobs/start-jobs.d.ts
2612
+ //#region src/facets/jobs/jobs.specification.d.ts
2629
2613
  /** Options for {@link startJobs | specification.jobs}. */
2630
2614
  type JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
2631
2615
  /**
2632
2616
  * Named jobs triggerable via `jobs.trigger(name)` — a factory receiving
2633
2617
  * the started services record, or a static array. Jobs run in-process by
2634
- * definition (CONVENTIONS A5/A8) — there is no mode.
2618
+ * definition (CONVENTIONS A8).
2635
2619
  */
2636
2620
  jobs: ((services: Services) => JobHandle[]) | JobHandle[];
2637
2621
  /** Project root override — see the `root` option of `specification.api()`. */
@@ -2654,7 +2638,7 @@ type JobsHandle<DatabaseKey extends string = string> = {
2654
2638
  };
2655
2639
  declare function startJobs<Services extends ServiceRecord>(options: JobsSpecificationOptions<Services>): Promise<JobsHandle<DatabaseKeys<Services>>>;
2656
2640
  //#endregion
2657
- //#region src/specification/facets/mobile/start-mobile.d.ts
2641
+ //#region src/facets/mobile/mobile.specification.d.ts
2658
2642
  /**
2659
2643
  * The declared stub backend behind the app under test. The framework owns
2660
2644
  * the simulator and appium but NOT the JS bundler (Metro belongs to the
@@ -2735,11 +2719,11 @@ type MobileHandle = {
2735
2719
  };
2736
2720
  declare function startMobile<Services extends ServiceRecord>(options: MobileSpecificationOptions<Services>): Promise<MobileHandle>;
2737
2721
  //#endregion
2738
- //#region src/specification/facets/website/serve.adapter.d.ts
2722
+ //#region src/seams/process/serve.adapter.d.ts
2739
2723
  /**
2740
2724
  * The ONE shape an external process takes — the site under test, a backend a
2741
2725
  * page talks to, a bundler a simulator loads from. Built by `process()`
2742
- * (chapter 11) and accepted wherever a facet starts something outside this
2726
+ * (chapter 17) and accepted wherever a facet starts something outside this
2743
2727
  * process: `website({ server })`, `services: { api: process(…) }`, and the
2744
2728
  * literate `serve:` registry.
2745
2729
  */
@@ -2779,26 +2763,16 @@ type ProcessOptions = {
2779
2763
  /** Readiness budget in milliseconds. Default 30 000. */
2780
2764
  timeout?: number;
2781
2765
  };
2782
- /**
2783
- * What `ProcessOptions` was called in 15.2, when the only process the
2784
- * framework owned was a website's server.
2785
- *
2786
- * @deprecated Renamed to {@link ProcessOptions} in 15.3 — the shape is the
2787
- * one external process every facet starts, not a website's alone. The alias
2788
- * is removed in 16.0.
2789
- */
2790
- type ServeOptions = ProcessOptions;
2791
2766
  //#endregion
2792
- //#region src/specification/facets/_common/process.d.ts
2767
+ //#region src/model/chain/process.d.ts
2793
2768
  /**
2794
2769
  * An external process, declared like any other service.
2795
2770
  *
2796
- * Before this there were three shapes for the same thing: `website({ server })`
2797
- * took a `ServeOptions`, a literate document took a `serve:` entry, and a
2798
- * project that needed a second process — an API behind the site, Metro behind
2799
- * the simulator — started one by hand in a `beforeAll` and killed it in an
2800
- * `afterAll` it sometimes forgot. `process()` is the one shape, and the
2801
- * framework owns the lifetime.
2771
+ * One shape for every one of them: a site's dev server, an API behind the
2772
+ * site, the bundler a simulator loads from. Without it each is declared
2773
+ * somewhere else — a facet option, a document's `serve:` entry, a `beforeAll`
2774
+ * spawning a child and an `afterAll` that sometimes forgets to kill it — and
2775
+ * the lifetime belongs to whoever remembered. Here it is the framework's.
2802
2776
  *
2803
2777
  * It sits in a `services` record beside `postgres()`, `redis()` and
2804
2778
  * `sqlite()`, so it starts after them and can be handed their connection
@@ -2810,7 +2784,7 @@ declare class ProcessHandle implements ServiceHandle {
2810
2784
  readonly defaultPort = 0;
2811
2785
  readonly environment: Record<string, string>;
2812
2786
  readonly type = "process";
2813
- composeName: null | string;
2787
+ serviceName: null | string;
2814
2788
  connectionString: string;
2815
2789
  started: boolean;
2816
2790
  private adapter;
@@ -2866,7 +2840,7 @@ declare class ProcessHandle implements ServiceHandle {
2866
2840
  */
2867
2841
  declare function processService(options: ProcessOptions): ProcessHandle;
2868
2842
  //#endregion
2869
- //#region src/specification/facets/website/start-website.d.ts
2843
+ //#region src/facets/website/website.specification.d.ts
2870
2844
  /** What `server` may be handed as, and what it resolves to. */
2871
2845
  type ServerSpec<Services extends ServiceRecord> = ((services: Services) => ProcessHandle | ProcessOptions) | ProcessHandle | ProcessOptions;
2872
2846
  /**
@@ -2944,7 +2918,7 @@ type WebsiteHandle = {
2944
2918
  };
2945
2919
  declare function startWebsite<Services extends ServiceRecord>(options: WebsiteSpecificationOptions<Services>): Promise<WebsiteHandle>;
2946
2920
  //#endregion
2947
- //#region src/specification/facets/_common/specification.d.ts
2921
+ //#region src/facets/specification.d.ts
2948
2922
  /**
2949
2923
  * The six specification constructors (CONVENTIONS A2) — created in a
2950
2924
  * `*.specification.ts` file under `specs/`, destructured with canonical
@@ -2980,11 +2954,11 @@ type Specification = {
2980
2954
  };
2981
2955
  declare const specification: Specification;
2982
2956
  //#endregion
2983
- //#region src/specification/facets/component/component.node.d.ts
2957
+ //#region src/facets/component/component.specification.d.ts
2984
2958
  /** Present, typed, and refusing — every setup and the terminal action alike. */
2985
2959
  declare const component: ComponentChain;
2986
2960
  //#endregion
2987
- //#region src/specification/literate/spec-document.d.ts
2961
+ //#region src/model/literate/spec-document.d.ts
2988
2962
  /**
2989
2963
  * The facets a document may describe. `cli` is the only one implemented; the
2990
2964
  * key exists so a second facet can arrive without a second grammar.
@@ -3064,15 +3038,7 @@ type SpecDocument = {
3064
3038
  serve: SpecServeEntry[];
3065
3039
  };
3066
3040
  //#endregion
3067
- //#region src/integrations/docker/docker-lookup.d.ts
3068
- /** Return all container IDs (running or stopped) that carry `key=value`. */
3069
- declare function findContainersByLabel(key: string, value: string): string[];
3070
- /** Return the raw `docker inspect` payload (object, not array) for a container. */
3071
- declare function inspectContainer(id: string): unknown;
3072
- /** Force-remove the given container IDs in a single call. Errors are swallowed. */
3073
- declare function removeContainers(ids: string[]): void;
3074
- //#endregion
3075
- //#region src/specification/facets/_common/result/text-subject.d.ts
3041
+ //#region src/model/result/text-subject.d.ts
3076
3042
  /**
3077
3043
  * Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
3078
3044
  * test's directory — the same caller-detection the builders use.
@@ -3098,7 +3064,7 @@ declare function removeContainers(ids: string[]): void;
3098
3064
  */
3099
3065
  declare function text(value: string): TextAccessor;
3100
3066
  //#endregion
3101
- //#region src/specification/ports/container.port.d.ts
3067
+ //#region src/model/ports/container.port.d.ts
3102
3068
  /**
3103
3069
  * Abstract container interface.
3104
3070
  * Represents a running service (database, cache, etc.)
@@ -3118,13 +3084,8 @@ type ContainerPort = {
3118
3084
  getLogs: () => Promise<string>;
3119
3085
  };
3120
3086
  //#endregion
3121
- //#region src/integrations/postgres/postgres.d.ts
3087
+ //#region src/seams/postgres/postgres.d.ts
3122
3088
  type PostgresOptions = {
3123
- /**
3124
- * Map to a service in docker/compose.test.yaml. Defaults to the handle's
3125
- * key in the declared services record.
3126
- */
3127
- composeService?: string;
3128
3089
  /** Override image. */
3129
3090
  image?: string;
3130
3091
  /** Override environment variables. */
@@ -3132,7 +3093,7 @@ type PostgresOptions = {
3132
3093
  };
3133
3094
  declare class PostgresHandle implements DatabasePort, ServiceHandle {
3134
3095
  readonly type = "postgres";
3135
- composeName: null | string;
3096
+ serviceName: null | string;
3136
3097
  readonly defaultPort = 5432;
3137
3098
  readonly defaultImage: string;
3138
3099
  readonly environment: Record<string, string>;
@@ -3145,7 +3106,7 @@ declare class PostgresHandle implements DatabasePort, ServiceHandle {
3145
3106
  buildConnectionString(host: string, port: number): string;
3146
3107
  createDatabaseAdapter(): DatabasePort;
3147
3108
  healthcheck(): Promise<void>;
3148
- initialize(composeDir: string): Promise<void>;
3109
+ initialize(dockerDir: string): Promise<void>;
3149
3110
  private getClient;
3150
3111
  seed(sql: string): Promise<void>;
3151
3112
  reset(): Promise<void>;
@@ -3156,28 +3117,23 @@ declare class PostgresHandle implements DatabasePort, ServiceHandle {
3156
3117
  * Create a PostgreSQL service handle.
3157
3118
  *
3158
3119
  * @example
3159
- * // Key derives the compose service (exact name or kebab-case):
3160
- * // { db: postgres() } → compose service "db"
3161
- * // { analyticsDb: postgres() } → compose service "analytics-db"
3162
- * // Escape hatch for names the key cannot derive:
3163
- * const events = postgres({ composeService: "legacy_event_store" });
3120
+ * // The record key is the name: it is the spec's `database` vocabulary, and
3121
+ * // Kebab-cased it is the folder the init script is read from.
3122
+ * // { db: postgres() } → docker/db/init.sql, then docker/postgres/init.sql
3123
+ * // { analyticsDb: postgres() } → docker/analytics-db/init.sql
3124
+ * const events = postgres();
3164
3125
  * // After start: events.connectionString is populated
3165
3126
  */
3166
3127
  declare function postgres(options?: PostgresOptions): PostgresHandle;
3167
3128
  //#endregion
3168
- //#region src/integrations/redis/redis.d.ts
3129
+ //#region src/seams/redis/redis.d.ts
3169
3130
  type RedisOptions = {
3170
- /**
3171
- * Map to a service in docker/compose.test.yaml. Defaults to the handle's
3172
- * key in the declared services record.
3173
- */
3174
- composeService?: string;
3175
3131
  /** Override image. */
3176
3132
  image?: string;
3177
3133
  };
3178
3134
  declare class RedisHandle implements ServiceHandle {
3179
3135
  readonly type = "redis";
3180
- composeName: null | string;
3136
+ serviceName: null | string;
3181
3137
  readonly defaultPort = 6379;
3182
3138
  readonly defaultImage: string;
3183
3139
  readonly environment: Record<string, string>;
@@ -3196,13 +3152,13 @@ declare class RedisHandle implements ServiceHandle {
3196
3152
  * Create a Redis service handle.
3197
3153
  *
3198
3154
  * @example
3199
- * // The record key derives the compose service: { cache: redis() } → "cache".
3155
+ * // The record key is the name it reports under: { cache: redis() } → "cache".
3200
3156
  * const cache = redis();
3201
3157
  * // After start: cache.connectionString is populated
3202
3158
  */
3203
3159
  declare function redis(options?: RedisOptions): RedisHandle;
3204
3160
  //#endregion
3205
- //#region src/integrations/sqlite/sqlite.d.ts
3161
+ //#region src/seams/sqlite/sqlite.d.ts
3206
3162
  type SqliteOptions = {
3207
3163
  /**
3208
3164
  * Path to a SQL file used to initialize the database schema.
@@ -3219,7 +3175,7 @@ type SqliteOptions = {
3219
3175
  };
3220
3176
  declare class SqliteHandle implements DatabasePort, ServiceHandle {
3221
3177
  readonly type = "sqlite";
3222
- composeName: null | string;
3178
+ serviceName: null | string;
3223
3179
  readonly defaultPort = 0;
3224
3180
  readonly defaultImage = "";
3225
3181
  readonly environment: Record<string, string>;
@@ -3249,7 +3205,7 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
3249
3205
  * rather than proceeding into a second concurrent `prisma db push` on the
3250
3206
  * same file (which is what `database is locked` was).
3251
3207
  */
3252
- initialize(_composeDir?: string, root?: string): Promise<void>;
3208
+ initialize(_dockerDir?: string, root?: string): Promise<void>;
3253
3209
  /**
3254
3210
  * Build the template on a private path, then rename it into place.
3255
3211
  *
@@ -3285,8 +3241,8 @@ declare function sqlite(options?: SqliteOptions): SqliteHandle;
3285
3241
  //#region src/index.d.ts
3286
3242
  /**
3287
3243
  * The module-scope network double, on msw's node server — what a test with no
3288
- * chain to hang contracts on reaches for (chapter 10).
3244
+ * chain to hang contracts on reaches for (chapter 16).
3289
3245
  */
3290
3246
  declare const intercept: Intercept;
3291
3247
  //#endregion
3292
- export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, CallResult, type CaptureScope, type CliEnv, type CliHandle, type CliInput, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, type ComponentChain, type ComponentScenario, type ComponentUi, type ComponentVisitor, ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, type DeviceTimeouts, DirectoryAccessor, type DockerSpecConfig, type DomMount, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, type HttpStreamInit, type IntegrationHandle, type IntegrationSpecification, type IntegrationSpecificationOptions, type Intercept, type InterceptScope, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type LiterateRunFlags, type LiterateServeRegistration, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockOfOptions, type MockPort, Orchestrator, PageResult, type PinnedClock, type PostgresOptions, ProcessHandle, type ProcessOptions, type RedisOptions, type RenderResult, type RenderSubject, type RenderedText, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServerSpec, type ServiceHandle, type ServiceRecord, type SpecDocument, type SpecEnvToken, type SpecFileAssertion, type SpecFixture, type SpecKind, type SpecRun, type SpecServeEntry, type SpecStream, type SpecificationConfig, type SpecificationMode, type SqliteOptions, type SseEvent, type StreamBody, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WaitUntilOptions, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, clock, complementary, component, content, contentinfo, defineContract, defineContracts, dialog, disabled, enabled, field, findContainersByLabel, focused, form, heading, http, inspectContainer, intercept, link, listitem, main, match, mockOf, mockOfDate, navigation, openai, option, postgres, processService as process, redis, region, removeContainers, required, row, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };
3248
+ export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, type BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CallResult, type CaptureScope, type CliEnv, type CliHandle, type CliInput, type CliOutput, type CliPort, type CliResult, type CliSpecification, type CliSpecificationOptions, type ComponentChain, type ComponentScenario, type ComponentUi, type ComponentVisitor, type ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, type DeviceTimeouts, type DirectoryAccessor, type DockerSpecConfig, type DomMount, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, type FetchResult, type FileAccessor, type FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, type HttpResult, type HttpStreamInit, type IntegrationHandle, type IntegrationSpecification, type IntegrationSpecificationOptions, type Intercept, type InterceptOptions, type InterceptScope, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsResult, type JobsSpecification, type JobsSpecificationOptions, type JsonAccessor, type LandmarkKind, type LiterateRunFlags, type LiterateServeRegistration, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockOfOptions, type MockPort, type PageResult, type PinnedClock, type PostgresOptions, ProcessHandle, type ProcessOptions, type RedisOptions, type RenderResult, type RenderSubject, type RenderedText, type ResponseAccessor, type ScreenNode, type ScreenResult, type ServerPort, type ServerResponse, type ServerSpec, type ServiceHandle, type ServiceRecord, type SpecDocument, type SpecEnvToken, type SpecFileAssertion, type SpecFixture, type SpecKind, type SpecRun, type SpecServeEntry, type SpecStream, type SpecificationConfig, type SqliteOptions, type SseEvent, type StreamBody, type TableAccessor, type TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WaitUntilOptions, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, clock, complementary, component, content, contentinfo, defineContract, defineContracts, dialog, disabled, enabled, field, focused, form, heading, http, intercept, link, listitem, main, match, mockOf, navigation, openai, option, postgres, processService as process, redis, region, required, row, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };