@termwright/conformance 0.2.0 → 0.3.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/README.md +39 -30
- package/dist/index.d.ts +9 -31
- package/dist/index.js +648 -282
- package/dist/index.js.map +1 -1
- package/package.json +15 -13
- package/src/fixtures/adversarial-peer.mjs +76 -114
- package/src/fixtures/generic-app.mjs +5 -2
- package/src/fixtures/ink-probe-app.mjs +7 -7
package/README.md
CHANGED
|
@@ -35,7 +35,6 @@ await runAdapterConformance({
|
|
|
35
35
|
quit: { input: '', exitCode: 0 },
|
|
36
36
|
columns: 80,
|
|
37
37
|
rows: 24,
|
|
38
|
-
expectAbsoluteBounds: true,
|
|
39
38
|
});
|
|
40
39
|
```
|
|
41
40
|
|
|
@@ -103,17 +102,16 @@ stderr rather than failing the run.
|
|
|
103
102
|
|
|
104
103
|
It checks the five obligations an adapter has:
|
|
105
104
|
|
|
106
|
-
| Obligation
|
|
107
|
-
|
|
108
|
-
| Dormant rule
|
|
109
|
-
| Tree before input | Once the handshake completes and
|
|
110
|
-
| Handshake
|
|
111
|
-
| Snapshot validity | Every snapshot passes `validateSnapshot`, carries this session's id, has resolvable parents and monotonic revisions
|
|
112
|
-
| Revision ordering | For each revision: snapshot → `revision-commit` → a marker that verifies against the session token, markers strictly increasing
|
|
113
|
-
| Channel loss
|
|
114
|
-
| Logs
|
|
115
|
-
|
|
|
116
|
-
| Conventions | The machine-checkable half of the protocol README's "Adapter semantics conventions": containers are not named from their content (rule 2), an annotated test id reaches the wire (rule 3), an empty textbox publishes `value: ''` and no value is derived outside `{textbox, progressbar}` or from a boolean (rule 5) |
|
|
105
|
+
| Obligation | What is asserted |
|
|
106
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| Dormant rule | Without `TERMWRIGHT_ENDPOINT` it opens no channel and writes no marker; with `baseline`, byte-for-byte identical startup output |
|
|
108
|
+
| Tree before input | Once the handshake completes and _before any input_, the tree is non-empty and has at least one node a locator could address. Opt out with `treeBeforeInput: {required: false, reason}` |
|
|
109
|
+
| Handshake | `hello` first and once, correct protocol id, non-empty adapter identity, capabilities from the closed set |
|
|
110
|
+
| Snapshot validity | Every publication is a complete v2 snapshot that passes `validateSnapshot`, carries this session's id, has resolvable parents and monotonic revisions |
|
|
111
|
+
| Revision ordering | For each revision: snapshot → `revision-commit` → a marker that verifies against the session token, markers strictly increasing |
|
|
112
|
+
| Channel loss | Cutting the socket leaves the application rendering and alive, and the adapter does not reconnect |
|
|
113
|
+
| Logs | An adapter that did not announce `logs` sends none. One that declares them in the registration must deliver a record whose `seq` is unique and increasing and whose message never appears on the terminal |
|
|
114
|
+
| Conventions | The machine-checkable half of the protocol README's "Adapter semantics conventions": containers are not named from their content (rule 2), an annotated test id reaches the wire (rule 3), an empty textbox publishes `value: ''` and no value is derived outside `{textbox, progressbar}` or from a boolean (rule 5) |
|
|
117
115
|
|
|
118
116
|
`await` it at the top level: `vitest` is imported dynamically so the package can
|
|
119
117
|
also be used from a plain script.
|
|
@@ -123,12 +121,12 @@ also be used from a plain script.
|
|
|
123
121
|
`CONFORMANCE_FIXTURES` returns absolute paths to programs you can launch with
|
|
124
122
|
`node <path>`:
|
|
125
123
|
|
|
126
|
-
| Fixture
|
|
127
|
-
|
|
128
|
-
| `generic()`
|
|
129
|
-
| `prompt()`
|
|
130
|
-
| `adversarialPeer()` | Raw wire peer; takes a scenario name as `argv[2]` (§20.3)
|
|
131
|
-
| `inkProbe()`
|
|
124
|
+
| Fixture | Purpose | Dependencies |
|
|
125
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
126
|
+
| `generic()` | Uninstrumented app: menu, colours, mouse/paste/focus modes, Unicode, alternate screen, scrollback (§20.1) | none |
|
|
127
|
+
| `prompt()` | Shell-shaped app emitting OSC 133 marks; `--marks=off` suppresses them, `--work=<ms>` sets the command duration | none |
|
|
128
|
+
| `adversarialPeer()` | Raw wire peer; takes a scenario name as `argv[2]` (§20.3) | none |
|
|
129
|
+
| `inkProbe()` | Ordinary `ink.render` app launched through the zero-config Ink probe | `ink`, `react`, `@termwright/probe-ink` |
|
|
132
130
|
|
|
133
131
|
The first three import nothing at all — the adversarial peer re-derives the
|
|
134
132
|
framing and the marker MAC from the specification rather than importing
|
|
@@ -151,27 +149,38 @@ keep running, and only a pid probed afterwards tells the two apart.
|
|
|
151
149
|
|
|
152
150
|
```sh
|
|
153
151
|
pnpm --filter @termwright/conformance conformance # every suite, one matrix
|
|
152
|
+
pnpm --filter @termwright/conformance conformance --require-declared-skips
|
|
153
|
+
pnpm --filter @termwright/conformance conformance --require-no-skipped-areas
|
|
154
154
|
pnpm --filter @termwright/conformance test # plain vitest
|
|
155
155
|
pnpm --filter @termwright/conformance test:hostile # adversarial suite, 128 MB heap cap
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
The conformance orchestrator builds the probed and plain tview fixtures
|
|
159
|
+
asynchronously before the native test host is opened. Each run gets a private
|
|
160
|
+
temporary directory and a platform/architecture contract containing both
|
|
161
|
+
binary digests. Collection only verifies that contract and never launches a
|
|
162
|
+
compiler; the orchestrator removes the directory after the host closes.
|
|
163
|
+
|
|
164
|
+
The table reports each area as pass, fail, or pass with an explicit skip count;
|
|
165
|
+
the exact test counts intentionally are not documentation because suites grow.
|
|
166
|
+
Test identities use `file::fullName`, so declarations cannot accidentally
|
|
167
|
+
match a same-named test elsewhere.
|
|
167
168
|
|
|
168
169
|
Certifying the py/go rows needs their toolchains on the runner
|
|
169
170
|
(`pip install -e clients/python[dev]` and a Go toolchain); without them those
|
|
170
171
|
rows skip honestly, with the probe's failure on stderr.
|
|
171
172
|
|
|
172
|
-
A partly-skipped area is reported as such rather than as a clean pass
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
A partly-skipped area is reported as such rather than as a clean pass. Platform
|
|
174
|
+
deviations live in the reviewed registry and must match the exact test identity.
|
|
175
|
+
`--require-declared-skips` requires the observed skip identities to equal the
|
|
176
|
+
reviewed applicability and platform-deviation registries exactly.
|
|
177
|
+
`--require-no-skipped-areas` is stricter: it permits only the fixed
|
|
178
|
+
applicability skips and rejects every registered platform deviation. Vitest
|
|
179
|
+
implements applicability with `skipIf`/`runIf`, so those cases do appear in the
|
|
180
|
+
matrix as expected skips even though they are not missing platform coverage.
|
|
181
|
+
Local runs may honestly skip a missing optional toolchain, while certification
|
|
182
|
+
installs its prerequisites and requires exact declared skips so missing
|
|
183
|
+
coverage cannot look green.
|
|
175
184
|
|
|
176
185
|
Sessions launch with the driver's secret-safe `envMode: 'replace'` default, so
|
|
177
186
|
a fixture only sees the documented allowlist plus what a suite declares.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdapterToDriverMessage, LogRecord
|
|
1
|
+
import { AdapterToDriverMessage, LogRecord } from '@termwright/protocol';
|
|
2
2
|
import { LaunchOptions, TerminalHarness } from '@termwright/driver';
|
|
3
3
|
|
|
4
4
|
/** How a fixture is started. Everything else about it is opaque to the probe. */
|
|
@@ -31,12 +31,6 @@ interface ProbeOptions {
|
|
|
31
31
|
readonly rows?: number;
|
|
32
32
|
/** Set `false` to withhold the instrumentation env — the dormant-run case. */
|
|
33
33
|
readonly instrument?: boolean;
|
|
34
|
-
/**
|
|
35
|
-
* What the probe asks the adapter to push. `'diffs'` is a preference, not a
|
|
36
|
-
* prohibition: an adapter may still send a full tree when a delta would not
|
|
37
|
-
* pay for itself, and the first publication always is one.
|
|
38
|
-
*/
|
|
39
|
-
readonly subscribe?: 'snapshots' | 'diffs';
|
|
40
34
|
}
|
|
41
35
|
/** Everything the probe observed, readable while the child is still running. */
|
|
42
36
|
interface ProbeObservation {
|
|
@@ -51,17 +45,6 @@ interface ProbeObservation {
|
|
|
51
45
|
readonly screen: string;
|
|
52
46
|
/** Application log records the adapter sent, in arrival order. */
|
|
53
47
|
readonly logs: readonly LogRecord[];
|
|
54
|
-
/** Deltas the adapter sent, in arrival order. */
|
|
55
|
-
readonly deltas: readonly TreeDelta[];
|
|
56
|
-
/**
|
|
57
|
-
* The tree obtained by composing every snapshot and delta received, in order,
|
|
58
|
-
* with the protocol's own `applyTreeDelta`. This is the oracle an adapter's
|
|
59
|
-
* deltas are checked against: a producer that also composed would only prove
|
|
60
|
-
* it agrees with itself.
|
|
61
|
-
*/
|
|
62
|
-
readonly composed: SemanticSnapshot | null;
|
|
63
|
-
/** Why composition stopped, when it did. A conforming adapter produces none. */
|
|
64
|
-
readonly compositionError: string | null;
|
|
65
48
|
}
|
|
66
49
|
/**
|
|
67
50
|
* Runs one fixture under a pseudo-terminal with a protocol endpoint attached.
|
|
@@ -122,13 +105,6 @@ declare class AdapterProbe {
|
|
|
122
105
|
cutChannel(): void;
|
|
123
106
|
/** Stops the child and releases the endpoint. Idempotent. */
|
|
124
107
|
stop(): Promise<void>;
|
|
125
|
-
/**
|
|
126
|
-
* Asks the adapter for a full tree and resolves with it.
|
|
127
|
-
*
|
|
128
|
-
* This is what turns composition into a check rather than a belief: the
|
|
129
|
-
* locally composed tree is compared against one the adapter built itself.
|
|
130
|
-
*/
|
|
131
|
-
requestTree(timeoutMs?: number): Promise<SemanticSnapshot | null>;
|
|
132
108
|
}
|
|
133
109
|
/** The marker prefix, re-exported so suites can assert on dormant output. */
|
|
134
110
|
declare const MARKER_TEXT_PREFIX = "\u001B]8487;twm;";
|
|
@@ -173,8 +149,8 @@ interface AdapterConformanceOptions {
|
|
|
173
149
|
};
|
|
174
150
|
readonly columns?: number;
|
|
175
151
|
readonly rows?: number;
|
|
176
|
-
/** Assert that
|
|
177
|
-
readonly
|
|
152
|
+
/** Assert that intended geometry is authoritative in viewport cells. */
|
|
153
|
+
readonly expectIntendedGeometry?: boolean;
|
|
178
154
|
/**
|
|
179
155
|
* Opt out of the "publishes a tree before any input" obligation.
|
|
180
156
|
*
|
|
@@ -240,8 +216,9 @@ interface AdapterConformanceOptions {
|
|
|
240
216
|
/** How long the handshake may take. Default 10 s. */
|
|
241
217
|
readonly timeoutMs?: number;
|
|
242
218
|
/**
|
|
243
|
-
* A command that must succeed before this adapter can be certified here
|
|
244
|
-
*
|
|
219
|
+
* A cheap command that must succeed before this adapter can be certified here.
|
|
220
|
+
* Compilers and other descendant-producing preparation must run before the
|
|
221
|
+
* native host opens; this probe is collection-time capability validation only.
|
|
245
222
|
* When it fails the whole suite skips and the reason is in the block's name,
|
|
246
223
|
* exactly as a missing pseudo-terminal does.
|
|
247
224
|
*/
|
|
@@ -250,6 +227,7 @@ interface AdapterConformanceOptions {
|
|
|
250
227
|
readonly label: string;
|
|
251
228
|
readonly cwd?: string;
|
|
252
229
|
readonly timeoutMs?: number;
|
|
230
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
253
231
|
};
|
|
254
232
|
}
|
|
255
233
|
/**
|
|
@@ -311,8 +289,8 @@ declare const CONFORMANCE_FIXTURES: Readonly<{
|
|
|
311
289
|
/**
|
|
312
290
|
* Whether this machine can open a pseudo-terminal at all.
|
|
313
291
|
*
|
|
314
|
-
* @returns `false` when `TERMWRIGHT_SKIP_PTY=1` or
|
|
315
|
-
*
|
|
292
|
+
* @returns `false` when `TERMWRIGHT_SKIP_PTY=1` or the native binding cannot
|
|
293
|
+
* be loaded and validated. Real child creation remains inside a test attempt.
|
|
316
294
|
*/
|
|
317
295
|
declare function ptyAvailable(): boolean;
|
|
318
296
|
/** `process.env` with the `undefined` values dropped, as PTY spawning requires. */
|