@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 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 | What is asserted |
107
- |---|---|
108
- | Dormant rule | Without `TERMWRIGHT_ENDPOINT` it opens no channel and writes no marker; with `baseline`, byte-for-byte identical startup output |
109
- | 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}` |
110
- | Handshake | `hello` first and once, correct protocol id, non-empty adapter identity, capabilities from the closed set |
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 | Cutting the socket leaves the application rendering and alive, and the adapter does not reconnect |
114
- | 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 |
115
- | Deltas (when announced) | With `subscribe: 'diffs'`, the deltas an adapter emits compose through the protocol's own `applyTreeDelta` to the same tree it reports when asked with `get-tree` |
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 | Purpose | Dependencies |
127
- |---|---|---|
128
- | `generic()` | Uninstrumented app: menu, colours, mouse/paste/focus modes, Unicode, alternate screen, scrollback (§20.1) | none |
129
- | `prompt()` | Shell-shaped app emitting OSC 133 marks; `--marks=off` suppresses them, `--work=<ms>` sets the command duration | none |
130
- | `adversarialPeer()` | Raw wire peer; takes a scenario name as `argv[2]` (§20.3) | none |
131
- | `inkProbe()` | Ordinary `ink.render` app launched through the zero-config Ink probe | `ink`, `react`, `@termwright/probe-ink` |
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
- area spec result tests time
160
- generic fallback §20.1 pass 10/10 2.0s
161
- hostile peer §20.3 pass 25/25 14.5s
162
- interaction §20.4 pass 12/12 2.6s
163
- readiness + env §5.3 pass 10/10 2.2s
164
- adapter contract (py/go) §7 pass, 8 skip 6/14 0.6s
165
- hostile peer @ 128 MB heap §10 pass 25/25 14.4s
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: the
173
- language adapters skip their whole registration when the toolchain is absent,
174
- and a matrix that hid it would claim coverage the machine never produced.
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, TreeDelta, SemanticSnapshot } from '@termwright/protocol';
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 published bounds are viewport-absolute (an `absolute-bounds` claim). */
177
- readonly expectAbsoluteBounds?: boolean;
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
- * its interpreter, or a build step that produces the binary `spawn` runs.
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 spawning a trivial child
315
- * through the PTY backend throws. Probed once and cached.
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. */