# @aweftjs/testing

The checks the rest of the stack is held to: the conformance suite, a model reading of the
format to check real implementations against, the tier rule, and seeded randomness.

This package is an integrator. It sits outside the tier ordering because everything is
allowed to depend on it, and nothing it exports ships to a user of the library. It is where
a claim about the stack becomes a check that fails when the claim stops being true.

## Conformance

A fixture is one case stated as plain JSON: the bytes, what they mean, the document before and the
document after. Nothing about this repo's types is needed to read one, which is the point. An
implementation in another language reads
[`spec/fixtures/`](https://github.com/torrinworx/aweft/tree/9a5bb24770dc7555257f8a307d79917efc58df70/spec/fixtures) and is conformant
when it agrees with every file there.

```ts
import { checkFixture, type Fixture } from '@aweftjs/testing';

const fixture: Fixture = JSON.parse(readFileSync('spec/fixtures/001-object-slots.json', 'utf8'));
checkFixture(fixture);   // throws naming the fixture and the check that failed
```

`checkFixture` runs the case both directions. It decodes the stated bytes and compares the
deltas to the stated JSON, encodes the stated JSON and compares to the stated bytes, applies
the commits and compares the document to the stated ending, and re-runs each commit with its
own deltas shuffled and reversed to check that the order they arrive in changes nothing.

The commits themselves keep their order. Section 4 of the format requires that, and only the
deltas inside one commit are unordered.

The encode direction matters more than it looks. Bytes checked only by decoding them are
checked against the package that produced them, so the fixture asks the implementation
whether it agrees with itself. The stated JSON is authored (the documents by hand, the delta
order by the generator's own rule), so encoding it asks a question the decoder's own output
cannot answer. The bytes in a fixture were produced by the reference encoder when the fixture
was generated; what anchors that encoder to the prose of the format is a pair of commits
spelled out by hand, head by head, in codec's own tests. A second implementation proves itself
by agreeing with those fixtures byte for byte.

`checkInvalidFixture` is the other half. Each case in
[`spec/fixtures/invalid/`](https://github.com/torrinworx/aweft/tree/9a5bb24770dc7555257f8a307d79917efc58df70/spec/fixtures/invalid)
names a `reason` and a `stage`, and an implementation that refuses the input for a different
reason has not agreed on the format, it has agreed on rejecting one string.

```ts
import { checkInvalidFixture, type InvalidFixture } from '@aweftjs/testing';
checkInvalidFixture(JSON.parse(readFileSync('spec/fixtures/invalid/011-truncated.json', 'utf8')));
```

## Checking a real implementation

Both check functions take an `Applier`: one reading of the format, as a function from a
starting document and some commits to the document reached.

```ts
type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;
```

A fixture states its commits as `CommitJson`, which is bytes plus JSON. Your applier is never
handed those: `checkFixture` decodes each one and calls you with `Commit`, the codec type, with
real byte-string ids and reference objects. Read the format from
[`spec/format.md`](/spec/format) and the codec
types, not from the fixture JSON shape.

`modelApplier` is the default, and it is the harness's own reading: plain data, no
reactivity, written from the specification rather than from any package. Passing a second
applier is how a real implementation gets held to the same suite:

```ts
checkFixture(fixture, myApplier);
```

Two independent readings reaching the same document from the same bytes is the evidence.
One implementation agreeing with itself is not.

When your applier throws on a fixture that is valid, the failure names the fixture, the delta
order it was running, and the reason you threw. When it returns the wrong document, the
failure prints both documents. Those are the two ways a second reading goes wrong, and the
suite is built to tell them apart.

## The document model

`DocumentJson` is flat: observables keyed by id in text form, with the root named separately.
Flat rather than nested because an observable can be named from more than one place, and a
nested spelling would have to pick one and quietly lose the others.

```ts
import { applyCommit, canonicalJson } from '@aweftjs/testing';

const after = applyCommit(before, commit);        // a new document, the input untouched
canonicalJson(after) === canonicalJson(expected)  // how two documents are compared
```

`applyCommit` checks every delta before applying any, which is what makes a commit atomic: a
commit that breaks a rule leaves the document exactly as it was. Compare documents through
`canonicalJson` rather than directly, or the order keys happened to be inserted in becomes
part of the answer.

## The recording host

`recordingDocument()` is a light document from `@aweftjs/dom` that writes down every node
operation, so a test asserts what a mount did to the tree and not only what the tree looks
like after.

```ts
import { recordingDocument } from '@aweftjs/testing';

const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
ops;            // ['insert <p> into <body> before end']
ops.length = 0; // clear between the steps of a test
```

One line per insert, remove, replace, text write, attribute write and clear, in order. A node
made elsewhere joins the recording when it is inserted.

## The tier rule

Packages are numbered, and a package may import downward only. `boundaries.json` is the table,
this package holds the check, and
[`packages/testing/scripts/check-boundaries.ts`](https://github.com/torrinworx/aweft/blob/9a5bb24770dc7555257f8a307d79917efc58df70/packages/testing/scripts/check-boundaries.ts)
runs it over the imports that actually exist.

```ts
import { checkGraph } from '@aweftjs/testing';
checkGraph([['core', 'codec']], table);   // [] means the graph is legal
```

Runtime code is what the rule governs. Tests, scripts and examples are outside it, because a
suite importing this harness creates no dependency in anything a user installs, and the
definition of done requires exactly that import. They are still printed on every run, so an
exclusion that starts hiding something is visible rather than silent.

## Testing a module in isolation

`loadModule` instantiates one module the way `@aweftjs/modules` would, with its dependencies
replaced by whatever the test hands over, so a module's own tests do not need a directory, a
document or the modules it depends on.

```ts
import { loadModule } from '@aweftjs/testing';
import * as Create from './modules/posts/Create.ts';

const { instance, stop } = await loadModule({
	exports: Create,
	imports: { 'auth/Session': { userOf: () => 'u_1' }, 'lib/Log': { log: () => {} } },
	config: { maxLength: 10 },     // merged over the module's defaults, as an extension would be
	props: { site: 'test' },       // what the application would pass the loader
});

assert.equal((instance as Post).make('a long title'), 'post:a long ti');
await stop();                    // unloads it, calling the instance's stop if it has one
```

Every name in the module's `deps` needs an entry in `imports`, keyed by the full dependency
name; a missing one is refused by name before anything is instantiated. The module is loaded
through the real loader, so `imports` reaches the factory keyed by the last segment of each
name, exactly as it would in an application.

## Testing a whole backend

`loadModule` above is one module with its dependencies stubbed. `loadServer` is the level above:
a real server, real modules, and the gate the application actually uses, on a listener that opens
nothing. It takes `createServer`'s options without the listener, which is the one part a test
cannot supply.

```ts
import { loadServer } from '@aweftjs/testing';

const server = await loadServer({
	sources: [fromDirectory(modules), auth],
	store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
	gate: 'auth/Gate',
});

const answer = await server.fetch('/api/session', { method: 'POST', body });
const page = await server.open({ headers: { cookie } });
await page.asks.ask('board/Mine');
await server.stop();
```

A throwaway or a server the suite never stops keeps its own resources alive: a cluster is a child
process and a running one holds the test process open. Stop them in an `after`.

`fetch` and `open` are the two seams a listener feeds, so everything a deployment does goes
through the same code a deployment does it with. `open` answers a socket, a link and the call
channel; it **throws** when the gate refuses the handshake, carrying the status the gate answered
with, because a browser handed a refusal gets a failed connection and not a response to read.

**Signing in is yours.** The sequence is a POST to your battery's session route, the `Set-Cookie`
off the answer, and `open` with that cookie. It is five lines and it is not here, because putting
it here would tie this package to one battery's routes and one idea of what a session is.
[`recipes/full-stack/tests/board.test.ts`](/docs/recipes/full-stack/files/tests/board.test.ts)
is those five lines.

## The security suite

`securityChecks({ gate })` is the suite a server passes rather than claims, the way a runner
passes `roomChecks()`. Each case is one named obligation citing the ASVS 5.0 requirements it
proves ([`docs/security.md`](/docs/security) has the table), run against a server you start, so an application
that boots its own modules behind the same server proves the same things about itself.

```ts
import { loadServer, securityChecks } from '@aweftjs/testing';

for (const c of securityChecks({ gate: 'auth/Gate' })) {
	test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
		...given,
		sources: [fromDirectory(modules), auth, ...given.sources],
		store: createStore({ driver: memoryDriver(), declare: { ...paths } }),
	})));
}
```

`start` is yours: it loads the probe modules the suite hands it beside your own, starts under
the gate the suite names (yours, with one rule of its own composed on top), passes the handlers
the suite gives it, and answers `{ fetch, open, server, stop }`, which is what `loadServer`
answers. The suite speaks the session routes by their documented shape, `POST` and `DELETE
/api/session` as `@aweftjs/auth` answers them, and imports no battery; a target with no
session battery fails the session cases at the first sign-up, naming that. Nothing is skipped.

The suite is append-only: a case is added for every hole ever found and none is removed. It
runs over the harness, where `fetch` may be handed a full URL, which is how the suite says a
request arrived over TLS. A target over a real port would adapt `fetch` and `open` to it, and
none ships: the transport's own bounds are pinned by the listener's tests.

`npm run security` ties the suite to the table: every requirement the table gives to the stack
names a case here or a test elsewhere that exists, and every case cites a requirement the table
gives to the stack.

## Two ends of one socket

`socketPair()` is what `loadServer` opens over, and it is exported because a suite that is about
the transport itself wants one without a server.

```ts
const [near, far] = socketPair();       // near is the page's end, far the server's
const link = connect(fromWebSocket(near));
```

What one sends the other hears on a microtask, never synchronously. A send before the socket is
open or after it closed reaches nobody, as a real one refuses and drops. Closing either closes both
and fires `close` at both; a second close does nothing. A listener that throws is recorded on
`thrown` and the listeners after it still run, because one socket here carries both the link and
the call channel and a throwing link would otherwise mean an ask that never settles.

An end also carries `peer` and `fire`. `fire('open', {})` is how a suite driving a client's retry
hands the page the event a browser would have fired. A listener added while an event is dispatching
does not hear that event, which is what a real `EventTarget` does. Pass `0` for a near end that
starts connecting, which is the order a page sees: the server is handed an accepted socket before
the page is told about its own.

This is not `sync`'s `inProcess`. That answers two channels, and a channel is one plane; a socket
carries the link and the call channel together and has the `readyState` and the close event a retry
reads.

## A database that goes away

`throwaway()` on the `/postgres` subpath starts an empty cluster in a temporary directory on a free
port, and stops it when you are done.

```ts
import { throwaway } from '@aweftjs/testing/postgres';

const db = await throwaway();
const store = createStore({ driver: postgresDriver(await db.pool()) });
// ...
await db.stop();
```

Every `pool()` is in a schema of its own, so two checks in one file never see each other's tables,
which is also how two applications share one database. **The throwaway owns every pool it handed
out** and ends them all in `stop`, so do not end one yourself: `pg` throws "Called end on pool more
than once" for the second call, and that throw is its, not this package's.

`embedded-postgres` and `pg` are optional peers: a project that never opens a store installs
neither, and one that asks for a throwaway without them gets `peer-not-installed` naming what to
install. `throwaway` takes how the peers are reached, so that refusal can be reached without
uninstalling anything.

## Reading the page a test drives

`audit` and `walk` on the `/browser` subpath take the page object a browser driver already handed
the test and answer what a screen reader and a keyboard would find there (design 267). Neither
throws on a finding; the test says what a finding means for it.

```ts
import { audit, walk } from '@aweftjs/testing/browser';

const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));

const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));
```

**`audit`** puts axe-core into the page and runs it over the document, or over `options.root`,
with the WCAG 2.x A and AA tags (`options.tags` replaces the list). A violation carries axe's rule
id, its `wcag*` tags as axe writes them (`wcag2aa`, `wcag143`), its help sentence and link, and the
nodes as a selector and the markup. Run it once per colour scheme: the page's colours are what it
measures, and a second run on the same page reuses the script.

**`walk`** presses Tab from the top of the page until the focus comes back round, or `options.limit`
(300) presses have gone by, and answers every stop (tag, id, role, name, and whether it draws a ring
while it matches `:focus-visible`) and the problems: `focus-not-visible` for a stop with no
`outline` and no `box-shadow`, `focus-stuck` when a press left the focus where it was, `focus-loops`
when a press sent the focus back round the page without letting it leave, `unreachable` for a
focusable element the walk never landed on, `never-cycles` when the limit ran out. Whatever the
page had focused loses it first. A ring is the element's own `outline` or `box-shadow`, or one an
ancestor draws for the control inside it through a rule about focus, and nothing else, so a page
that shows its focus by changing a border colour is reported as ringless. A radio group is one
stop, an element with no box (inside a closed `details`, a closed dialog, a hidden parent) is not
expected, and a frame, a shadow host or a media element's own controls is one stop the focus
moves inside without the walk reading where. Run it with no modal dialog open: the page behind
one is inert and the walk reports it unreachable.

The page is structural: `evaluate(source)`, `addScriptTag({ content })` and `keyboard.press(key)`,
which Playwright's `Page` satisfies (the suite passes one), and this package imports no browser
driver.
`axe-core` is an optional peer: a project that never audits installs nothing, and one that asks
without it gets `axe-not-installed`. `options.locate` says where the script is when the installed
one is not the one to use, and is how that refusal is reached without uninstalling anything.

What neither reads is the criteria that need a person: meaning carried by colour alone, the order
of the reading, a heading that describes its section, a time limit, an error message that says
what to do. The build refuses what the source settles (`@aweftjs/build`, The access rules) and the
mount throws on a nameless `Button` and a page with no `lang` or title (`@aweftjs/ui`); these two
read the rendered page for the rest.

## Letting scheduled work run

`settle()` yields to the timer queue ten times, so work that schedules more work gets to run. A
single `await` drains microtasks and nothing else, which is why a suite that awaits once and
asserts sees a tree that is half settled. Pass a whole number of one or more for a different count;
anything else is refused rather than settling for nothing and failing an assertion further on.

## Seeded randomness

A property test is worth having only if a failure can be run again, so a failing assertion
prints its seed and that seed reproduces the run exactly.

```ts
import { randomBelow, randomFrom } from '@aweftjs/testing';

const random = randomFrom(20260901);
const victim = items[randomBelow(random, items.length)];
```

There is one generator here rather than one per suite. Copies of the same shift register
drifted apart in small ways, and a seed that reproduces a failure under one copy reproduces
nothing under another.

## The words check

`npm run words` reads every tracked file for the vocabulary of how the stack was built (who
decided a thing, when, through which review) and prints each line that carries a word from the
list, with what to write instead. The list is in
[`packages/testing/src/words.ts`](https://github.com/torrinworx/aweft/blob/9a5bb24770dc7555257f8a307d79917efc58df70/packages/testing/src/words.ts),
one entry per word with its fix. It reads text, not syntax, so it matches the spellings the
process used and leaves the words the code needs alone.

```ts
import { checkWords } from '@aweftjs/testing';
checkWords([{ path: 'note.md', text }]);   // [] means the text is clean
```

With paths, `node packages/testing/scripts/check-words.ts docs/design` reads those files
instead of the whole tree.

## What a suite has to name

`npm run exercised` reads each package's `surface.txt` and `errors.txt` beside its tests and
fails a covered package whose own suite never names one of its value exports, or never quotes
one of its refusal reasons (design 285). Both files are generated: `surface.txt` by `npm run
surface`, one export per line as `value name: type` with a subpath's lines prefixed by the
subpath, and `errors.txt` by `npm run errors`, one `reason: fix` per line. A test counts when it is not the surface list and not a
white-box `internal.*` file; a name in a comment does not count, and a reason as the whole of a
regular expression does, since a refusal's message opens with its reason. The covered packages
are listed in the script and the list only grows; the rest have their counts printed. `testing`
is exempt, because its surface is the suites of the other packages.

```ts
import { checkExercised } from '@aweftjs/testing';
checkExercised('store', surface, errors, tests);   // [] means the suite names everything
```

## The operator sweep

`npm run sweep -- <package>` copies the repo into a scratch directory, flips one operator at a
time in that package's sources (a comparison, a logical operator, a boolean a function returns),
runs the package's own suite against each flip, and prints every flip the suite let through with
its file, line and operator (design 287). It runs on request and never in the gate: one flip is
one run of the suite, two test files at a time so the machine stays usable, and
`--only=<file>[,<file>]` narrows it to the files you are working on. A survivor is either killed
by a test or written down as equivalent with the reason, wherever the change that ran the sweep
is written up.

```ts
import { flipSite, sweepSites } from '@aweftjs/testing';
const [first] = sweepSites(source);        // where the file can be flipped, in order
const flipped = flipSite(source, first!);  // the same text with that one operator changed
```

## Running the gate

`npm test` at the root is the whole gate: typecheck, the dependency rules, the tier rule over real
imports, every package's suite with its coverage threshold, and every proof program.
[`packages/testing/scripts/run-tests.ts`](https://github.com/torrinworx/aweft/blob/9a5bb24770dc7555257f8a307d79917efc58df70/packages/testing/scripts/run-tests.ts)
is the part that runs the suites, and `generate-fixtures.ts` rewrites
[`spec/fixtures/`](https://github.com/torrinworx/aweft/tree/9a5bb24770dc7555257f8a307d79917efc58df70/spec/fixtures) from the generator
entries.

## API

Every export of `@aweftjs/testing`, its signature as the compiler resolves it, and its block comment.

### `@aweftjs/testing`

#### `AdapterCheck`

```ts
interface AdapterCheck { readonly name: string; run(make: MakeAdapter): Promise<void>; }
```

One named obligation an adapter has to meet.

#### `Applier`

```ts
type Applier = (initial: DocumentJson, commits: readonly Commit[]) => DocumentJson;
```

One reading of the format, as a function.

The suite is the same for every implementation, and there is more than one: a model that
stores plain data, and a real reactive tree. Both must reach the same document from the same
bytes, which is the point of running the fixtures twice.

**Params**

- `initial`: the document to start from
- `commits`: the commits to apply, in order, each whole

**Returns** the document reached. Throws with a stated `reason` when a commit is refused.

#### `CommitJson`

```ts
interface CommitJson { readonly bytes: string; readonly deltas: readonly DeltaJson[]; readonly tag?: string; }
```

A commit as plain JSON: its bytes in hex, the deltas they carry, and an optional tag.

#### `Connected`

```ts
interface Connected { readonly socket: PairedSocket; readonly link: Link; readonly asks: Requests; readonly request: Request; }
```

What `open` answers when the gate let the handshake through.

#### `DeltaJson`

```ts
interface DeltaJson { readonly type: DeltaType; readonly id: string; readonly ref: RefJson; readonly value?: ValueJson; }
```

A delta as plain JSON, with `value` absent exactly when the type is remove.

#### `DocumentJson`

```ts
interface DocumentJson { readonly root: string; readonly observables: Readonly<Record<string, ObservableJson>>; }
```

A whole document as plain JSON.

Flat, keyed by id in text form, with the root named separately. Flat rather than nested
because an observable can be named from more than one place, and a nested spelling would
have to pick one of them and quietly lose the others.

#### `DriverCheck`

```ts
interface DriverCheck { readonly name: string; run(make: MakeDriver): Promise<void>; }
```

One named obligation a driver has to meet.

#### `FileAdapter`

```ts
interface FileAdapter { readonly name: string; put(key: string, stream: ReadableStream<Uint8Array>, options: { readonly type: string; readonly size: number; }): Promise<void>; open(key: string): Promise<ReadableStream<Uint8Array> | undefined>; head(key: string): Promise<{ readonly size: number; } | undefined>; remove(key: string): Promise<void>; }
```

The part of an `uploads` adapter this suite exercises.

Stated structurally rather than imported, so `testing` does not depend on `uploads`: the
package it checks depends on it in turn.

#### `Fixture`

```ts
interface Fixture { readonly name: string; readonly description: string; readonly initial: DocumentJson; readonly commits: readonly CommitJson[]; readonly final: DocumentJson; }
```

One conformance case: bytes, what they mean, and the document they produce.

This is the contract an implementation in another language reads. `initial` is the document
before, `commits` are applied in order, `final` is the document after, and every one of them
is stated as plain JSON so that nothing about this repo's types is needed to consume it.

`initial` and `final` are written by hand rather than generated, which is the point: an
ending document produced by the implementation under test would only say the implementation
agrees with itself.

#### `InvalidFixture`

```ts
interface InvalidFixture { readonly name: string; readonly description: string; readonly stage: 'decode' | 'apply'; readonly reason: string; readonly bytes: string; readonly initial?: DocumentJson; }
```

One case that must be refused, and the reason it must be refused for.

`reason` is the contract, not the message. Two implementations that both reject an input
for different stated reasons have not agreed on the format, they have agreed on rejecting
one string. `stage` says where the refusal is due: `decode` for bytes that are not a commit,
`apply` for a commit that is well formed and cannot be applied, which is the only case that
needs `initial`.

#### `ListenerCheck`

```ts
interface ListenerCheck { readonly name: string; run(make: MakeListener): Promise<void>; }
```

One named obligation a listener has to meet.

#### `LoadedModule`

```ts
interface LoadedModule { readonly instance: unknown; stop(): Promise<void>; }
```

The instance the module produced, and the way to unload it.

#### `LoadedServer`

```ts
interface LoadedServer { readonly server: Server; fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: OpenOptions): Promise<Connected>; stop(): Promise<void>; }
```

A running server with no port, and the two seams a listener would feed it.

#### `MadeListener`

```ts
interface MadeListener { readonly listener: Listener; url(): string; }
```

No block comment on this export.

#### `MakeAdapter`

```ts
type MakeAdapter = () => Promise<FileAdapter> | FileAdapter;
```

What a check needs: an adapter nobody else is using.

#### `MakeDriver`

```ts
type MakeDriver = () => Promise<StoreDriver> | StoreDriver;
```

What a check needs: a driver nobody else is using, and a way to be rid of it.

#### `MakeListener`

```ts
type MakeListener = () => Promise<MadeListener> | MadeListener;
```

What a check needs: a listener nobody else is using, and the URL to reach it at once it has started.

#### `MakeRunner`

```ts
type MakeRunner = () => Runner;
```

What a check needs: a fresh runner nobody else is using.

#### `Manifest`

```ts
interface Manifest { readonly name: string; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly devDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependenciesMeta?: Readonly<Record<string, { readonly optional?: boolean; }>> | undefined; }
```

The dependency-bearing part of one package.json.

#### `ModuleUnderTest`

```ts
interface ModuleUnderTest { readonly exports: ModuleExports; readonly imports?: Readonly<Record<string, unknown>>; readonly config?: Readonly<Record<string, unknown>>; readonly props?: Readonly<Record<string, unknown>>; }
```

No block comment on this export.

#### `ObservableJson`

```ts
interface ObservableJson { readonly kind: ObservableKind; readonly slots: Readonly<Record<string, ValueJson>>; }
```

One observable as plain JSON: its kind, and its slots by key.

#### `OpenOptions`

```ts
interface OpenOptions { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; readonly peer?: Peer | undefined; }
```

What `open` may be told about the handshake it is about to make.

#### `Opened`

```ts
interface Opened { readonly socket: { readonly readyState: number; send(data: string | Uint8Array): void; close(): void; }; readonly link: Link; readonly asks: Requests; }
```

A connection the target opened: the socket, the link and the call channel over it.

#### `PackageInfo`

```ts
interface PackageInfo { readonly tier: number | 'integrator'; readonly plane: Plane; readonly imports: readonly string[] | '*'; }
```

A package's place in the table: how high it sits, which side it runs on, what it reaches.

#### `PairedSocket`

```ts
interface PairedSocket extends SocketLike { peer: PairedSocket | undefined; readonly sent: (Uint8Array | string)[]; readonly thrown: unknown[]; fire(type: string, event: { data?: unknown; }): void; }
```

One end of a pair: a socket, plus what it was asked to send.

#### `Plane`

```ts
type Plane = 'client' | 'server' | 'isomorphic';
```

Which side a package runs on. `isomorphic` may be imported from either.

#### `PublishManifest`

```ts
interface PublishManifest { readonly name: string; readonly version?: string | undefined; readonly private?: boolean | undefined; readonly engines?: Readonly<{ node?: string | undefined; }> | undefined; readonly files?: readonly string[] | undefined; readonly exports?: Readonly<Record<string, string | Readonly<Record<string, string>>>> | undefined; readonly dependencies?: Readonly<Record<string, string>> | undefined; readonly peerDependencies?: Readonly<Record<string, string>> | undefined; readonly publishConfig?: Readonly<{ access?: string | undefined; }> | undefined; readonly scripts?: Readonly<Record<string, string>> | undefined; }
```

The publishing-relevant part of one package.json.

#### `Recording`

```ts
interface Recording { readonly document: DocumentLike & { readonly body: ElementLike; readonly head: ElementLike; }; readonly ops: string[]; }
```

No block comment on this export.

#### `RefJson`

```ts
interface RefJson { readonly kind: ObservableKind; readonly key: string; }
```

A ref as plain JSON, with the key always a string so a fixture stays readable.

An object key is itself, an array position is hex, and a map key is the id in its textual
form: sixteen base64url characters, as `idToText` writes it, not hex.

#### `RoomCheck`

```ts
interface RoomCheck { readonly name: string; run(make: MakeRunner): Promise<void>; }
```

One named obligation the window has to meet behind a runner.

#### `SecurityCase`

```ts
interface SecurityCase { readonly name: string; readonly requirements: readonly string[]; }
```

One case of the suite, as much of it as the table needs.

#### `SecurityCheck`

```ts
interface SecurityCheck { readonly name: string; readonly requirements: readonly string[]; run(start: StartTarget): Promise<void>; }
```

One named obligation a server has to meet, and the requirements it proves.

#### `SecurityRow`

```ts
interface SecurityRow { readonly id: string; readonly level: string; readonly owner: string; readonly check: string; }
```

One line of the table.

#### `SecurityTarget`

```ts
interface SecurityTarget { fetch(path: string, init?: RequestInit): Promise<Response>; open(options?: { readonly headers?: Record<string, string> | undefined; readonly url?: string | undefined; }): Promise<Opened>; readonly server: Server; stop(): Promise<void>; }
```

A started application the suite runs against. `loadServer` answers this shape as it is.

#### `Site`

```ts
interface Site { readonly line: number; readonly column: number; readonly from: string; readonly to: string; }
```

One place a source file can be flipped.

#### `StartOptions`

```ts
interface StartOptions { readonly sources: readonly Source[]; readonly gate: Gate | string; readonly handlers: { failed(name: string, error: unknown): void; }; }
```

What the suite hands `start`: its own probe modules, the gate the target must run under, and
where the server reports a module that broke.

#### `StartTarget`

```ts
type StartTarget = (options: StartOptions) => Promise<SecurityTarget>;
```

The caller's boot. It loads the sources given beside its own (a store declaring the auth
paths, and a session battery answering the auth routes), starts under the gate given, and
hands the server the handlers given, so a module the suite breaks on purpose is reported to
the suite rather than raised where nothing catches it.

#### `StoreDriver`

```ts
interface StoreDriver { write(write: { doc: string; root: string; rootKind: string; rows: readonly { id: string; kind: string; edge?: { parent: string; slot: string; } | null; set: Record<string, unknown>; unset: readonly string[]; }[]; dropped: readonly string[]; body: Uint8Array; project?: Record<string, unknown>; }): Promise<number>; declare(declaration: Readonly<Record<string, readonly string[]>>): Promise<void>; find(lookup: { where: { field: string; op: string; value: unknown; }; sort?: { field: string; direction: 'asc' | 'desc'; }; limit?: number; after?: string; }): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; scan(limit: number, after?: string): Promise<{ doc: string; fields: Record<string, unknown>; cursor: string; }[]>; create(doc: string, root: string, rootKind: string): Promise<boolean>; read(doc: string): Promise<{ root: string; rootKind: string; rows: unknown[]; } | null>; since(doc: string, seq: number): Promise<{ seq: number; body: Uint8Array; }[]>; head(doc: string): Promise<number>; truncate(doc: string, seq: number): Promise<void>; forget(doc: string, ids: readonly string[]): Promise<void>; remove(doc: string): Promise<void>; close(): Promise<void>; }
```

The part of a `store` driver this suite exercises.

Stated structurally rather than imported, so `testing` does not depend on `store`: an
integrator may know everything, but a suite that forced a dependency edge would make the
package it checks unable to depend on it in turn.

#### `TestExists`

```ts
type TestExists = (file: string, text: string, kind: 'test' | 'doc') => boolean;
```

Whether a file holds what a row names: a test of that title, or a document section under that heading.

#### `TestSource`

```ts
interface TestSource { readonly path: string; readonly text: string; }
```

One test file of the package.

#### `ThemeSource`

```ts
interface ThemeSource { readonly path: string; readonly text: string; }
```

One file to read.

#### `ThemeViolation`

```ts
interface ThemeViolation { readonly path: string; readonly line: number; readonly where: string; readonly property: string; readonly literal: string; readonly fix: string; }
```

One value written where it stands rather than named.

#### `ThrownRefusal`

```ts
interface ThrownRefusal { readonly reason: string; readonly fix: string; }
```

One refusal a package can throw: the token, and the remedy offered with it.

Named for what it is rather than `Refusal`, which `core` and `schema` already use for the
different thing an application's own rule returns.

#### `ValueJson`

```ts
type ValueJson = null | boolean | number | string | { readonly bytes: string; } | { readonly ref: string; readonly kind: ObservableKind; readonly edge: EdgeKind; };
```

A value as plain JSON: a primitive, `{ bytes }` for a byte string, or a reference.

#### `Violation`

```ts
interface Violation { readonly from: string; readonly to: string; readonly rule: 'upward-tier' | 'cross-plane' | 'unknown-package' | 'not-allowed'; readonly detail: string; }
```

One illegal import edge, and which rule it broke.

#### `WordSource`

```ts
interface WordSource { readonly path: string; readonly text: string; }
```

One file to read.

#### `WordViolation`

```ts
interface WordViolation { readonly path: string; readonly line: number; readonly word: string; readonly fix: string; readonly text: string; }
```

One line carrying a word from the list.

#### `adapterChecks`

```ts
adapterChecks: () => AdapterCheck[]
```

The obligations of an adapter, as named checks.

**Returns** the checks. Run each with a fresh adapter.

**Example**

```ts
for (const c of adapterChecks()) test(c.name, () => c.run(() => directory(tmp)));
```

#### `applyCommit`

```ts
applyCommit: (doc: DocumentJson, commit: Commit) => DocumentJson
```

Apply one commit to a document.

**Params**

- `doc`: the document before
- `commit`: the commit to apply, its deltas in any order

**Returns** a new document. The input is not modified.

**Throws** a CodecError naming the rule broken. Every delta is checked before any is applied, which is what makes a commit atomic: a commit that would break a rule leaves the document exactly as it was, so no observer ever sees the halfway state.

#### `aweftPackageOf`

```ts
aweftPackageOf: (specifier: string) => string | undefined
```

The package a specifier reaches inside this stack, or undefined for anything else.

**Params**

- `specifier`: a module specifier as written

**Returns** the package name for `@aweftjs/<name>` and any subpath under it. A subpath import is still an edge to that package; whether reaching a subpath is legal is the exports map's question, not this one's.

**Example**

```ts
aweftPackageOf('@aweftjs/dom/router'); // 'dom'
```

#### `canonicalJson`

```ts
canonicalJson: (value: unknown) => string
```

A document as one string, with every key in a stated order.

Two documents are the same document when these strings match. Comparing the objects
directly would make the order keys happen to be inserted in part of the answer, and
applying the same commit in two orders inserts them in two orders.

#### `checkEdge`

```ts
checkEdge: (from: string, to: string, table: Readonly<Record<string, PackageInfo>>) => Violation | null
```

Check one import edge against the boundary rules.

**Params**

- `table`: the tier and plane map. `boundaries.json` at the repo root is the one copy

**Returns** a Violation, or null when the edge is allowed.

#### `checkExercised`

```ts
checkExercised: (name: string, surface: string, errors: string, tests: readonly TestSource[]) => string[]
```

Every export and every reason the suite does not name.

**Params**

- `name`: the package, for the report
- `surface`: the text of its `surface.txt`
- `errors`: the text of its `errors.txt`, empty when it refuses for no reason
- `tests`: its test files; the ones that count are chosen here, so hand in the directory

**Returns** one line per miss, naming the package and the export or reason, empty when the suite names them all. An export counts as named when it appears as a whole word outside a comment; a reason when it appears quoted, or as the whole of a regular expression, since a refusal's message opens with its reason and `assert.throws(fn, /reason/)` is the shortest way to ask.

**Example**

```ts
checkExercised('store', surface, errors, tests);
// ['store: projectionOf is exported and no test names it', "store: 'not-open' is a refusal and no test quotes it"]
```

#### `checkFixture`

```ts
checkFixture: (f: Fixture, applier?: Applier) => void
```

Run one fixture.

**Params**

- `f`: the fixture, already parsed

**Throws** an Error naming the fixture and the check that failed, including when the applier itself throws on a case that is valid. Returns nothing on success.

#### `checkGraph`

```ts
checkGraph: (edges: readonly (readonly [string, string])[], table: Readonly<Record<string, PackageInfo>>) => Violation[]
```

Check a whole import graph.

**Params**

- `edges`: every package-to-package import in the repo
- `table`: the tier and plane map

**Returns** every violation found, in input order. Empty means the graph is legal.

#### `checkInvalidFixture`

```ts
checkInvalidFixture: (f: InvalidFixture, applier?: Applier) => void
```

Run one rejection fixture.

**Params**

- `f`: the fixture. `stage` says whether the bytes must be refused when decoded, or decode cleanly and be refused when applied to `initial`.

**Throws** an Error if the input was accepted, or refused for a different stated reason than the fixture names. Rejecting for the wrong reason counts as a failure: a format whose implementations disagree about why something is invalid has not been specified.

#### `checkManifests`

```ts
checkManifests: (manifests: readonly Manifest[], allowed: readonly string[], perPackage?: Readonly<Record<string, readonly string[]>>) => string[]
```

Every dependency declaration the allowlist does not cover.

**Params**

- `manifests`: the packages' manifests
- `allowed`: dependency names permitted anywhere; `@aweftjs/` packages are always permitted. An entry ending `/*` covers a family, so `@iconify-json/*` is one entry
- `perPackage`: extra names permitted for specific packages, by package name, in the same spelling

**Returns** one line per violation, naming the package and the dependency, empty when clean. A third-party peer that is not marked optional is a violation even when the allowlist names it.

**Example**

```ts
const violations = checkManifests(manifests, ['typescript'], {});
```

#### `checkPublishing`

```ts
checkPublishing: (manifests: readonly PublishManifest[]) => string[]
```

Every way a set of manifests would publish something other than what design 256 describes.

**Params**

- `manifests`: the packages' manifests, as `package.json` holds them

**Returns** one line per violation, naming the package and what is wrong, empty when clean. The version check compares the manifests against each other rather than against a constant, because the packages version in lockstep and no file is the record of which version that is.

**Example**

```ts
const violations = checkPublishing([JSON.parse(readFileSync('packages/core/package.json', 'utf8'))]);
```

#### `checkSecurityTable`

```ts
checkSecurityTable: (rows: readonly SecurityRow[], cases: readonly SecurityCase[], testExists: TestExists) => string[]
```

Every way the table and the suite could disagree.

**Params**

- `rows`: the table
- `cases`: the suite's cases, by name and the requirements each cites
- `testExists`: answers whether `test: <file>#<title>` names a test that is there

**Returns** one line per problem, empty when the table holds: an id that is not `Vn.n.n` or appears twice; a level that is not 1 or 2; an owner that is not one of the four words; an empty check; a `stack` row whose check is not `suite: <name>` naming a case, `test: <file>#<title>` naming a test that exists, or `doc: <file>#<heading>` naming a section that exists; a case citing an id the table lacks or does not mark `stack`.

**Example**

```ts
const problems = checkSecurityTable(rows, securityChecks({ gate: open }), exists);
```

#### `checkTheme`

```ts
checkTheme: (files: readonly ThemeSource[]) => ThemeViolation[]
```

Every value written where it stands rather than named.

**Params**

- `files`: the source files to read, each with the path to report it under

**Returns** one entry per literal, in file then position order. Empty means every value in a CSS position came through a `$name`.

**Example**

```ts
const found = checkTheme([{ path: 'a.tsx', text: readFileSync('a.tsx', 'utf8') }]);
```

#### `checkWords`

```ts
checkWords: (sources: readonly WordSource[]) => WordViolation[]
```

Reads every line of every source against the list and reports each line that carries a word.

**Params**

- `sources`: the files, each with the path it will be reported under

**Returns** one violation per matching line and rule, in file order then line order. A line carrying two words from the list is reported twice. An empty array means the text is clean.

**Example**

```ts
checkWords([{ path: 'a.md', text: 'Decided by: the maintainer.' }]);
// [{ path: 'a.md', line: 1, word: 'a decided-by header', fix: 'drop the line', text: '...' }]
```

#### `commitToJson`

```ts
commitToJson: (commit: Commit, bytes: Uint8Array<ArrayBufferLike>) => CommitJson
```

A commit and its bytes in the shape a fixture states them, for comparing against one.

#### `deltaFromJson`

```ts
deltaFromJson: (d: DeltaJson) => Delta
```

The delta a JSON one names.

This is the direction that makes a fixture's bytes checkable against something beside the
decoder: the stated JSON is ordered independently of the encoder's sort, so encoding it
and comparing to the stated bytes asks a question the decoder's own output cannot answer.

#### `deltaToJson`

```ts
deltaToJson: (d: Delta) => DeltaJson
```

A delta in its JSON form, as a fixture states it.

#### `driverChecks`

```ts
driverChecks: () => DriverCheck[]
```

Every obligation a `store` driver has.

**Returns** the checks, each of which takes a factory for a driver nobody else is using and throws on failure.

**Example**

```ts
for (const check of driverChecks()) test(check.name, () => check.run(() => memoryDriver()));
```

#### `errorLines`

```ts
errorLines: (refusals: readonly ThrownRefusal[]) => string[]
```

The refusals as the lines that go in `errors.txt`.

**Params**

- `refusals`: what `errorsOf` returned

**Returns** one line per refusal, reason then colon then the fix.

**Example**

```ts
const text = errorLines(refusals).join(String.fromCharCode(10));
```

#### `errorsOf`

```ts
errorsOf: (files: readonly string[], program: Program) => ThrownRefusal[]
```

Every refusal thrown in these files.

**Params**

- `files`: absolute paths to the source files of one package
- `program`: a program those files belong to

**Returns** one entry per distinct reason and fix, sorted by reason then fix. A reason thrown from several places with several remedies appears once per remedy, because that difference is exactly what a reader needs to see in a diff.

**Throws** `not-in-program` when a path is not part of the program handed in, `reason-not-literal` when a token is built at runtime, and `fix-missing` or `fix-not-literal` when the remedy is absent or built, which would make the index a description of code rather than of what a reader is told.

**Example**

```ts
const refusals = errorsOf(files, program);
```

#### `flipSite`

```ts
flipSite: (text: string, site: Site) => string
```

The source with one site flipped.

**Params**

- `text`: the source the site was found in
- `site`: one of its sites

**Returns** the same text with that one operator replaced, every other byte unchanged.

**Example**

```ts
flipSite('if (a < b) go();', { line: 1, column: 5, from: ' < ', to: ' <= ' });
// 'if (a <= b) go();'
```

#### `idFromText`

```ts
idFromText: (text: string) => Id (from @aweftjs/codec)
```

The id behind its text form.

**Params**

- `text`: sixteen base64url characters, as idToText writes them

**Returns** the 12 bytes, as an id.

**Throws** when the length or the alphabet is wrong. Round tripping through text is lossless, so anything that does not round trip was never one of these ids.

**Example**

```ts
const id = idFromText(slotKey);
```

#### `idToText`

```ts
idToText: (id: Id) => string (from @aweftjs/codec)
```

The textual form of an id: sixteen base64url characters.

**Params**

- `id`: an id. Raw bytes reach one through assertId

**Returns** the text form, safe in a URL, a log line, or a JSON object key.

**Throws** a CodecError with reason `invalid-id` when the bytes are not ID_BYTES long.

#### `listenerChecks`

```ts
listenerChecks: () => ListenerCheck[]
```

Every obligation a `server` listener has.

**Returns** the checks, each named, each taking a function that makes a fresh listener and says where to reach it.

**Throws** each check's `run` throws an assertion when the listener misses that obligation, and `no-status-line` when a raw upgrade request is closed with nothing written back.

**Example**

```ts
for (const c of listenerChecks()) test(c.name, () => c.run(() => {
  const listener = node({ port: 0 });
  return { listener, url: () => `http://127.0.0.1:${listener.port}` };
}));
```

#### `loadFixtures`

```ts
loadFixtures: (dir: string | URL) => Fixture[]
```

Every fixture in a directory, sorted by filename.

**Params**

- `dir`: the fixtures directory, as a URL or an absolute path ending in a slash

**Returns** the parsed fixtures, in filename order.

**Example**

```ts
for (const f of loadFixtures(new URL('../../../spec/fixtures/', import.meta.url))) {
  checkFixture(f);
}
```

#### `loadInvalidFixtures`

```ts
loadInvalidFixtures: (dir: string | URL) => InvalidFixture[]
```

Every rejection fixture in a directory, sorted by filename.

**Params**

- `dir`: the invalid-fixtures directory, as a URL or an absolute path ending in a slash

**Returns** the parsed rejection fixtures, in filename order.

**Example**

```ts
for (const f of loadInvalidFixtures(new URL('../../../spec/fixtures/invalid/', import.meta.url))) {
  checkInvalidFixture(f);
}
```

#### `loadModule`

```ts
loadModule: (test: ModuleUnderTest) => Promise<LoadedModule>
```

Instantiate one module the way the loader would, with its dependencies replaced by the
instances the test supplies.

**Params**

- `test.exports`: the module
- `test.imports`: one instance per dependency name in `exports.deps`
- `test.config`: merged over `exports.defaults`
- `test.props`: spread into the factory's props

**Returns** the instance and a `stop` that unloads it.

**Throws** `dependency-not-stubbed`, naming the dependency, when one has no entry in `imports`: a test that forgot one should not get an undefined import.

**Example**

```ts
const { instance, stop } = await loadModule({
  exports: await import('./modules/posts/Create.ts'),
  imports: { 'auth/Session': { userOf: () => 'u_1' } },
  config: { maxLength: 10 },
});
```

#### `loadServer`

```ts
loadServer: (options: Omit<ServerOptions, "listener">) => Promise<LoadedServer>
```

Boot a server with real modules on a listener with no port.

#### `modelApplier`

```ts
modelApplier: Applier
```

The harness's own reading of the specification: plain data, no reactivity.

#### `moduleSpecifiers`

```ts
moduleSpecifiers: (source: string, fileName?: string) => string[]
```

Every module specifier a source file names with a literal.

**Params**

- `source`: the file's text
- `fileName`: used only for parser diagnostics

**Returns** the specifiers of static imports and re-exports, dynamic `import()` calls whose argument is a string or substitution-free template, and direct `require()` calls, in order of appearance. Comments and ordinary strings are not imports and are not here.

**Example**

```ts
const packages = moduleSpecifiers(text).map(aweftPackageOf).filter((p) => p !== undefined);
```

#### `parseSecurityTable`

```ts
parseSecurityTable: (text: string) => SecurityRow[]
```

The table, from the CSV text: a header line `id,level,owner,check`, then one row per line.
The check column may carry commas; the first three commas split the row.

**Params**

- `text`: the file's text

**Returns** the rows, in file order. A blank line is skipped.

**Example**

```ts
parseSecurityTable(readFileSync('docs/security/asvs.csv', 'utf8'));
```

#### `randomBelow`

```ts
randomBelow: (random: () => number, bound: number) => number
```

A whole number in [0, bound).

**Params**

- `random`: a stream from randomFrom
- `bound`: how many values, at least 1

**Returns** an index into something of that length.

**Throws** `invalid-bound` when the bound is under 1.

**Example**

```ts
const victim = items[randomBelow(random, items.length)];
```

#### `randomFrom`

```ts
randomFrom: (seed: number) => () => number
```

A seeded stream of numbers in [0, 1).

**Params**

- `seed`: any integer. It is folded to 32 bits, so seeds equal modulo 2^32 give one stream, and zero is mapped to 1, since a shift register cannot leave zero

**Returns** a function giving the next number. Two generators made with one seed produce the same stream, so a failure prints its seed and the run can be repeated exactly.

**Example**

```ts
const random = randomFrom(20260901);
assert.ok(check(list), `failed at seed 20260901`);
```

#### `recordingDocument`

```ts
recordingDocument: () => Recording
```

A light document that records what is done to it.

**Returns** the document and its `ops`, one line per operation: `insert <li> into <ul> before end`, `remove <li> from <ul>`, `text "a" -> "b"`, `attr class="x" on <div>`, `unattr class on <div>`, `clear <ul>`. Nodes made through the document's factories are recorded, and so is anything cloned from one; nodes from elsewhere are not.

**Example**

```ts
const { document, ops } = recordingDocument();
mount(document.body, h('p', {}, 'hi'));
assert.deepEqual(ops, ['insert "hi" into <p> before end', 'insert <p> into <body> before end']);
```

#### `refFromJson`

```ts
refFromJson: (r: RefJson) => Ref
```

The ref a JSON one names. The inverse of refToJson, and the fixtures rely on it round tripping.

#### `refToJson`

```ts
refToJson: (ref: Ref) => RefJson
```

A ref in its JSON form, and back. The pair round trips, which the fixtures depend on.

#### `roomChecks`

```ts
roomChecks: () => RoomCheck[]
```

The window half of the escape suite: what a hostile module cannot do from inside a room,
whatever runner made the room.

**Returns** the checks, each named, each taking a function that makes a fresh runner.

**Throws** each check's `run` throws `room-check-failed` when the runner misses that obligation, and the detail says what was expected.

**Example**

```ts
for (const c of roomChecks()) test(c.name, () => c.run(() => inProcess()));
```

#### `securityChecks`

```ts
securityChecks: (options: { readonly gate: string | Gate<unknown>; }) => SecurityCheck[]
```

Every security obligation a server has, each citing the ASVS 5.0 requirements it proves.

**Params**

- `options.gate`: the gate the target runs under, a `Gate` or the name of a module that is one; the suite composes one rule of its own on top and starts the target under that

**Returns** the checks, each named, each taking `start`: the caller's boot, which loads the sources given beside its own store and session battery, starts under the gate given with the handlers given, and answers `{ fetch, open, server, stop }`. `loadServer` answers that shape, so over the harness a case is one line.

**Throws** each check's `run` throws an assertion naming what the target did instead. A target with no session battery fails the session cases at the first sign-up, naming that.

**Example**

```ts
for (const c of securityChecks({ gate: 'auth/Gate' })) {
  test(`${c.requirements.join(' ')}: ${c.name}`, () => c.run((given) => loadServer({
    ...given, sources: [auth, ...given.sources], store: newStore(),
  })));
}
```

#### `seedFrom`

```ts
seedFrom: (text: string) => number
```

A seed derived from the fixture name, so a failing shuffle is the same one next run.

#### `settle`

```ts
settle: (rounds?: number) => Promise<void>
```

Yield to the timer queue, once per round, so work that schedules more work gets to run.

A single `await` drains microtasks and nothing else, which is why a suite that awaits once
and asserts sees a tree that is half settled. Ten rounds is what the suites that wrote this
by hand all chose.

#### `shuffle`

```ts
shuffle: <T>(items: readonly T[], seed: number) => T[]
```

Reorder deterministically, from a seed.

**Params**

- `items`: what to reorder
- `seed`: the same seed always gives the same order

**Returns** a new array. Used to check that the order deltas arrive in changes nothing, so it has to be repeatable: a failure that cannot be run again is a rumour. A shuffle can legitimately come back in the order it went in, so it is never the only reordering a check tries.

#### `slotKeyOf`

```ts
slotKeyOf: (ref: Ref) => string (from @aweftjs/codec)
```

The name a slot has in a path, and the key a document files it under.

**Params**

- `ref`: the slot a delta names

**Returns** an object key as itself, an array position in hex, a map identity in text form.

**Throws** a CodecError with reason `invalid-id` when a map slot's key is not an id. This is one mapping with one implementation, because a second copy is a second chance to disagree about what a document's own keys are.

**Example**

```ts
slotKeyOf({ kind: 'object', key: 'title' });   // 'title'
```

#### `socketPair`

```ts
socketPair: (readyState?: number) => [PairedSocket, PairedSocket]
```

Two ends of one socket with no port: what one sends, the other hears on a microtask, and
closing either closes both.

#### `surfaceOf`

```ts
surfaceOf: (indexPath: string, program?: Program) => string[]
```

The public surface of one package, as text.

**Params**

- `indexPath`: absolute path to the package's `src/index.ts`
- `program`: a program that already contains that file. One is built for it when omitted

**Returns** one line per export, sorted by name. A value reads `value name: type`, an interface or type alias reads `type name: declaration`, and an export re-exported from another package in the stack ends with ` (from

#### `surfaceProgram`

```ts
surfaceProgram: (indexPaths: readonly string[]) => Program
```

One program over several entry files, so a whole repo's surfaces share one checker.

**Params**

- `indexPaths`: absolute paths to the `src/index.ts` of each package

**Returns** a program those paths can be read from. Building one per package costs a fresh parse of every file they share.

**Example**

```ts
const program = surfaceProgram(['/repo/packages/core/src/index.ts']);
```

#### `sweepSites`

```ts
sweepSites: (text: string) => Site[]
```

Every site in a source text, in file order.

**Params**

- `text`: the source

**Returns** the sites, one per operator occurrence per line, in line then column order. A line that is or carries a comment has none, and an occurrence inside a string is left out. The operators are matched with their spaces, so ` <= ` is one site and never also a ` < ` one.

**Example**

```ts
sweepSites('if (a < b && c) return true;');
// [{ line: 1, column: 5, from: ' < ', to: ' <= ' }, { line: 1, column: 9, from: ' && ', to: ' || ' }, ...]
```

#### `themeTokens`

```ts
themeTokens: (files: readonly ThemeSource[]) => string[]
```

Every `$name` these files define.

**Params**

- `files`: the source files to read

**Returns** the names, sorted, without their `$`, each once. Every scale step, every role, every size, every duration and every theme function the package ships.

**Example**

```ts
const names = themeTokens([{ path: 'roles.ts', text }]);
```

#### `valueFromJson`

```ts
valueFromJson: (v: ValueJson) => Value
```

The value a JSON one names. The inverse of valueToJson.

#### `valueToJson`

```ts
valueToJson: (v: Value) => ValueJson
```

A value in its JSON form, and back. Both directions are used, so both are exercised.

#### `wordRules`

```ts
wordRules: () => readonly { readonly word: string; readonly fix: string; }[]
```

The names in the list, so a report can say what is checked without repeating the patterns.

### `@aweftjs/testing/browser`

#### `AuditNode`

```ts
interface AuditNode { readonly target: string; readonly html: string; }
```

One node axe found a violation on.

#### `AuditOptions`

```ts
interface AuditOptions { readonly root?: string; readonly tags?: readonly string[]; readonly locate?: () => string; }
```

What `audit` takes beside the page. All three are optional.

#### `AuditResult`

```ts
interface AuditResult { readonly violations: readonly AuditViolation[]; readonly passes: number; }
```

What `audit` answers: the rules the page broke, and how many it kept.

#### `AuditViolation`

```ts
interface AuditViolation { readonly rule: string; readonly impact: string | null; readonly wcag: readonly string[]; readonly help: string; readonly helpUrl: string; readonly nodes: readonly AuditNode[]; }
```

One rule the page broke.

#### `PageLike`

```ts
interface PageLike { evaluate(source: string): Promise<unknown>; addScriptTag(options: { readonly content: string; }): Promise<unknown>; readonly keyboard: { press(key: string): Promise<void>; }; }
```

The page object a browser driver hands a test, as much of it as these checks read.

Playwright's `Page` has these three, and the suite passes one: `evaluate` takes an expression
as source text and answers what it evaluates to, `addScriptTag` puts a script into the page by
its content, and `keyboard.press` presses one key.

#### `Stop`

```ts
interface Stop { readonly tag: string; readonly id: string | null; readonly role: string | null; readonly name: string; readonly ring: boolean; }
```

Where the focus landed after one press.

#### `WalkOptions`

```ts
interface WalkOptions { readonly limit?: number; }
```

What `walk` takes beside the page.

#### `WalkProblem`

```ts
interface WalkProblem { readonly reason: 'focus-not-visible' | 'focus-stuck' | 'focus-loops' | 'unreachable' | 'never-cycles'; readonly fix: string; readonly target: string; }
```

One thing the walk found wrong, with the element it is about.

#### `WalkResult`

```ts
interface WalkResult { readonly stops: readonly Stop[]; readonly problems: readonly WalkProblem[]; }
```

What `walk` answers: where the focus went, in order, and what was wrong on the way.

#### `audit`

```ts
audit: (page: PageLike, options?: AuditOptions) => Promise<AuditResult>
```

Run axe-core over the page and answer what it found.

**Params**

- `page`: the page a browser driver opened
- `options`: `root`, a selector for the part to audit; `tags`, the axe tags to run; `locate`, where the axe-core script is, when the installed one is not the one to use

**Returns** the violations, each with axe's rule id, its WCAG tags, its help and the nodes, and the count of rules that passed. Nothing is thrown for a violation; the test decides.

**Throws** `axe-not-installed` when `locate`, or the resolver in its place, finds nothing.

**Example**

```ts
const { violations } = await audit(view);
assert.deepEqual(violations, [], violations.map((v) => `${v.rule}: ${v.help}`).join('\n'));
```

#### `walk`

```ts
walk: (page: PageLike, options?: WalkOptions) => Promise<WalkResult>
```

Press Tab through the page, from the top, and answer where the focus went and what was wrong
on the way. Whatever had the focus loses it first, so the walk starts before the first control.

**Params**

- `page`: the page a browser driver opened
- `options`: `limit`, presses before the walk gives up

**Returns** every stop in order, and the problems: a stop with no visible ring, a press that moved nothing, a press that sent the focus back round the page without leaving it, a focusable element the walk never reached, or a walk that never came back round. Nothing is thrown for a problem; the test decides.

**Example**

```ts
const { stops, problems } = await walk(view);
assert.deepEqual(problems, [], problems.map((p) => `${p.reason} at ${p.target}: ${p.fix}`).join('\n'));
```

### `@aweftjs/testing/postgres`

#### `Load`

```ts
type Load = (name: 'embedded-postgres' | 'pg') => Promise<unknown>;
```

How the optional peers are reached, so a test can reach the refusal without uninstalling one.

#### `PoolLike`

```ts
interface PoolLike { query(text: string, values?: readonly unknown[]): Promise<{ rows: Record<string, unknown>[]; }>; connect(): Promise<{ query(text: string, values?: readonly unknown[]): Promise<{ rows: unknown[]; rowCount: number | null; }>; release(): void; }>; end(): Promise<void>; }
```

The minimum of `pg.Pool` this module hands back, typed here so `pg` stays an optional peer.

It carries `connect` as well as `query` because the pool is handed straight to
`postgresDriver`, which takes connections rather than queries; a pool that could only answer
`query` would make the one thing this module is for fail to compile.

#### `Throwaway`

```ts
interface Throwaway { readonly port: number; pool(): Promise<PoolLike>; stop(): Promise<void>; }
```

A Postgres that exists for one run: the cluster's port, pools in schemas of their own, and the
teardown that ends all of them.

#### `throwaway`

```ts
throwaway: (load?: Load) => Promise<Throwaway>
```

Start a Postgres for this run: an empty cluster in a temporary directory on a free port.

Every pool it answers is in a schema of its own, which is how two checks in one file stay
apart and how two applications share one database.

## Refusals

Every error this package raises carries a `reason` to switch on and a `fix` that says what to do. These are its reasons, from `errors.txt`.

| reason | fix |
|---|---|
| `axe-not-installed` | Install axe-core as a devDependency, or say where the script is with options.locate. |
| `dependency-not-stubbed` | Add an entry under imports for every name the module lists in deps. |
| `fix-missing` | Give the refusal a third argument saying what to do about it. |
| `fix-not-literal` | Write the fix as a plain string; move anything variable into the detail. |
| `fixture-accepted` | Refuse this input, with the reason the fixture names. |
| `fixture-mismatch` | Make the implementation agree with the fixture, or regenerate with npm run fixtures. |
| `fixture-refused` | Accept this case: the fixture is part of the format, so refusing it is the bug. |
| `handshake-refused` | Open with the headers the gate needs, or assert the refusal with assert.rejects. |
| `invalid-bound` | Check the collection is not empty before asking for an index into it. |
| `kind-conflict` | State one kind for this id in every delta of the commit. |
| `missing-initial` | Give the fixture an initial document, or set its stage to decode. |
| `multiple-attach` | Remove the other attach edge in the same commit, or point at it with a reference. |
| `no-status-line` | Answer every upgrade request with an HTTP status line, refusals included. |
| `not-in-program` | Add the file to the program before reading it. |
| `not-in-program` | Pass this path to surfaceProgram too, or let surfaceOf build its own program. |
| `peer-not-installed` | Install embedded-postgres and pg as devDependencies. |
| `reason-not-literal` | Write the reason as a plain string; a token built at runtime cannot be branched on. |
| `room-check-failed` | Fix the runner so it meets this obligation, then run the checks again. |
| `rounds-not-positive` | Pass a whole number of rounds, one or more, or nothing for the default. |
| `server-stopped` | Call loadServer again; a stopped server is not restarted. |
| `slot-exists` | Send a replace delta to overwrite the slot, or add under a free key. |
| `slot-missing` | Send an add delta first, so the slot exists before it is replaced or removed. |
| `unreachable` | Attach it under the root in the same commit that writes into it. |
| `wrong-reason` | Throw the reason the fixture names, so every implementation agrees on why. |

## Recipes

The programs in the stack's gate that use this package, each a job someone would have.

- [`recipes/accessible-page`](/docs/recipes/accessible-page): A page everyone can use, driven by keyboard and audited in both modes, and each guardrail catching one page written wrong: the build refusing an element no one can read, the mount throwing on a nameless button and a page with no language, `audit` and `walk` reading what only the rendered page shows
