@aventara/testing 0.1.0-pilot.1 → 0.1.0-pilot.2

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.
@@ -1,8 +1,3 @@
1
- /**
2
- * Takes the caller's snapshot at the moment the boundary opens and returns the
3
- * single restore action that undoes everything written after it. Returning a
4
- * closure keeps the snapshot value's type local to the hook that owns it.
5
- */
6
1
  function beginRollback(rollback) {
7
2
  if (rollback === undefined) {
8
3
  return undefined;
@@ -12,7 +7,6 @@ function beginRollback(rollback) {
12
7
  rollback.restore(snapshot);
13
8
  };
14
9
  }
15
- /** Reusable provider-free Adapter implementation for framework/conformance tests. */
16
10
  export function createFakeAdapter(options) {
17
11
  const operations = [];
18
12
  const transactions = [];
@@ -54,8 +48,6 @@ export function createFakeAdapter(options) {
54
48
  catch (error) {
55
49
  restore?.();
56
50
  settle(scoped, "rolled-back");
57
- // By identity: the transaction runtime recognises its own abort
58
- // sentinel by reference, never by shape.
59
51
  throw error;
60
52
  }
61
53
  settle(scoped, "committed");
@@ -3,16 +3,14 @@ import { Decimal } from "@aventara/core";
3
3
  /**
4
4
  * The corpus's adapter model:
5
5
  *
6
- * - `authors` — every standard operation (family 2); its `secret` field is
7
- * hidden at client scope (R1's client-hidden field);
6
+ * - `authors` — every standard operation (family 2); its `secret` field is hidden
7
+ * at client scope;
8
8
  * - `books` — references `authors`, and deleting an author cascades into it
9
- * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
10
- * adds a computed `label` to it ({@link CORPUS_CLIENT_CONFIG}, F-828);
11
- * - `secrets` — its one operation restricted away at client scope (R1's
12
- * client-restricted variant; M6's `operations: {}`);
13
- * - `hiddenRes` — hidden at client scope (R1's client-hidden Resource, M7);
14
- * - `samples` — one field of each scalar whose wire form is not its runtime
15
- * form (`bigint`, `datetime`, `decimal`, `bytes`; family 13, P1, C1).
9
+ * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope adds
10
+ * a computed `label` to it;
11
+ * - `secrets` — its one operation restricted away at client scope;
12
+ * - `hiddenRes` — hidden at client scope;
13
+ * - `samples` — one field of each scalar whose wire form is not its runtime form.
16
14
  */
17
15
  export declare const CORPUS_MODEL: {
18
16
  readonly transactions: "interactive";
@@ -290,7 +288,9 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
290
288
  * conflict code; `A3001` — it throws a plain `Error`;
291
289
  * - `A3002` — a plan step carrying it runs, and the Adapter's transaction then
292
290
  * fails to commit;
293
- * - `A3003` — `samples.find.unique` answers a datetime the wire cannot spell.
291
+ * - `A3003` — `samples.find.unique` answers a datetime the wire cannot spell;
292
+ * - `A3004` — the client pipe rewrites the arguments into ones the server
293
+ * contract refuses (a filter on a field `authors` does not have).
294
294
  */
295
295
  export declare const CORPUS_TRIGGER: {
296
296
  readonly unauthenticated: "A4000";
@@ -303,13 +303,19 @@ export declare const CORPUS_TRIGGER: {
303
303
  readonly adapterThrows: "A3001";
304
304
  readonly commitFails: "A3002";
305
305
  readonly unencodable: "A3003";
306
+ readonly pipeOutputInvalid: "A3004";
306
307
  };
307
308
  /** The corpus's one client guard: it decides nothing unless a row names a trigger. */
308
309
  declare function corpusGuard(context: PipelineContext): boolean;
310
+ /**
311
+ * The corpus's one client pipe: it rewrites nothing unless a row names the
312
+ * `A3004` trigger, and then adds a filter on a field the server does not have.
313
+ */
314
+ declare function corpusPipe(args: unknown): unknown;
309
315
  /**
310
316
  * The corpus's client-scope configuration: what the ClientContract hides or
311
317
  * restricts, and `books.label` — a VIRTUAL field added at client scope and
312
- * computed on read from `id` (F-828, plan S12).
318
+ * computed on read from `id`.
313
319
  */
314
320
  export declare const CORPUS_CLIENT_CONFIG: {
315
321
  readonly fields: {
@@ -340,6 +346,7 @@ export declare const CORPUS_CLIENT_CONFIG: {
340
346
  };
341
347
  readonly pipelines: {
342
348
  readonly guards: readonly [typeof corpusGuard];
349
+ readonly pipes: readonly [typeof corpusPipe];
343
350
  };
344
351
  readonly restrictions: {
345
352
  readonly authors: {
@@ -372,18 +379,17 @@ export declare const CORPUS_ENTRYPOINT = "/api";
372
379
  export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
373
380
  /**
374
381
  * The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
375
- * bytes (Q8), `protocol.hash` included (M22). A literal, as a host must
376
- * serve it byte for byte; `corpus-contract.spec.ts` fails, naming this
377
- * constant, when the compiled contract moves.
382
+ * bytes, `protocol.hash` included. A literal, as a host must serve it byte for
383
+ * byte; `corpus-contract.spec.ts` fails, naming this constant, when the compiled
384
+ * contract moves.
378
385
  */
379
386
  export declare const CORPUS_CLIENT_CONTRACT_DOCUMENT = "{\"enums\":{},\"limits\":{\"maxBooleanNodes\":50,\"maxListLimit\":250,\"maxNestingDepth\":12,\"maxRequestBytes\":1048576,\"maxTransactionOperations\":20},\"protocol\":{\"hash\":\"sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0\",\"version\":1},\"resources\":{\"authors\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"name\":{\"capabilities\":{\"create\":[],\"filter\":[\"equals\"],\"select\":true,\"update\":[]},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"count\":true,\"many\":true,\"one\":true},\"delete\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"find\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"update\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"upsert\":{\"unique\":true}}},\"books\":{\"fields\":{\"authorId\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"label\":{\"capabilities\":{\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[\"COMPUTED_ON_READ\",\"VIRTUAL\"],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"find\":{\"many\":true}}},\"samples\":{\"fields\":{\"amount\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"decimal\"}},\"at\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"datetime\"}},\"blob\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bytes\"}},\"count\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bigint\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"one\":true},\"find\":{\"unique\":true}}},\"secrets\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{}}},\"scalars\":{\"bigint\":{\"builtin\":true},\"bytes\":{\"builtin\":true},\"datetime\":{\"builtin\":true},\"decimal\":{\"builtin\":true},\"string\":{\"builtin\":true}},\"transactions\":\"interactive\"}";
380
387
  /**
381
388
  * The hash of the corpus's SECOND ClientContract — the same model and
382
- * configuration with `transactions: "none"` (family 6's `/_transactions`
383
- * absence). A row targets it by sending this hash, exactly as a client
384
- * generated against it would; the runner gives the row the Framework whose
385
- * ClientContract it names (the director, 2026-10-05). A literal, held to the
386
- * compiled value by `corpus-contract.spec.ts`.
389
+ * configuration with `transactions: "none"` (family 6's `/_transactions` absence).
390
+ * A row targets it by sending this hash, exactly as a client generated against it
391
+ * would; the runner gives the row the Framework whose ClientContract it names. A
392
+ * literal, held to the compiled value by `corpus-contract.spec.ts`.
387
393
  */
388
394
  export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
389
395
  /** The one record the corpus Adapter knows. */
@@ -394,8 +400,8 @@ export declare const CORPUS_AUTHOR: {
394
400
  /** The `where.id` that matches nothing. */
395
401
  export declare const CORPUS_NOBODY = "nobody";
396
402
  /**
397
- * The one `samples` record, in runtime forms — what an Adapter hands core,
398
- * and what the HTTP encoders must spell in wire forms (P1).
403
+ * The one `samples` record, in runtime forms — what an Adapter hands core, and
404
+ * what the HTTP encoders must spell in wire forms.
399
405
  */
400
406
  export declare const CORPUS_SAMPLE: {
401
407
  readonly id: "s1";
@@ -1,14 +1,5 @@
1
1
  import { createFramework, Decimal, FrameworkError } from "@aventara/core";
2
2
  import { createFakeAdapter } from "../adapters/fake-adapter.js";
3
- /**
4
- * The Framework every driven corpus row runs against: one model, one
5
- * configuration, and an in-memory Adapter whose answer is a pure function of
6
- * the operation it is handed — so a row's expected bytes are fixed facts, and
7
- * no row needs a provider (plan §13).
8
- *
9
- * Grows with the corpus (plan §6): each slice adds the resources its rows
10
- * need, and moves {@link CORPUS_CLIENT_CONTRACT_HASH} with them.
11
- */
12
3
  const identityString = {
13
4
  kind: "scalar",
14
5
  type: { scalar: "string" },
@@ -26,7 +17,6 @@ const writableString = {
26
17
  update: [],
27
18
  },
28
19
  };
29
- /** A non-JSON-native scalar the corpus reads (family 13): selectable, never filtered. */
30
20
  const wired = (scalar) => ({
31
21
  kind: "scalar",
32
22
  type: { scalar },
@@ -35,22 +25,7 @@ const wired = (scalar) => ({
35
25
  lifecycle: [],
36
26
  capabilities: { select: true, create: [] },
37
27
  });
38
- /** One `find.many`: what the corpus's supporting Resources advertise. */
39
28
  const findMany = { find: { many: true } };
40
- /**
41
- * The corpus's adapter model:
42
- *
43
- * - `authors` — every standard operation (family 2); its `secret` field is
44
- * hidden at client scope (R1's client-hidden field);
45
- * - `books` — references `authors`, and deleting an author cascades into it
46
- * ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
47
- * adds a computed `label` to it ({@link CORPUS_CLIENT_CONFIG}, F-828);
48
- * - `secrets` — its one operation restricted away at client scope (R1's
49
- * client-restricted variant; M6's `operations: {}`);
50
- * - `hiddenRes` — hidden at client scope (R1's client-hidden Resource, M7);
51
- * - `samples` — one field of each scalar whose wire form is not its runtime
52
- * form (`bigint`, `datetime`, `decimal`, `bytes`; family 13, P1, C1).
53
- */
54
29
  export const CORPUS_MODEL = {
55
30
  transactions: "interactive",
56
31
  scalars: {
@@ -105,23 +80,9 @@ export const CORPUS_MODEL = {
105
80
  },
106
81
  },
107
82
  };
108
- /** Deleting an author cascades into its books (an edge under `books`). */
109
83
  export const CORPUS_REFERENTIAL_ACTIONS = {
110
84
  books: [{ field: "author", target: "authors", onDelete: "cascade" }],
111
85
  };
112
- /**
113
- * The `where.id` a row sends to make the corpus Framework fail one way
114
- * (families 10 and 12, "a fake adapter returning the code, labelled so"):
115
- *
116
- * - `A4000`, `A4001`, `A4002`, `A3000` — the client guard throws
117
- * `FrameworkError("A4000")`, denies (`false`), throws
118
- * `FrameworkError("A4002")`, or throws a plain `Error`;
119
- * - `A2008`, `A2013`, `A2014` — the Adapter throws `FrameworkError` with that
120
- * conflict code; `A3001` — it throws a plain `Error`;
121
- * - `A3002` — a plan step carrying it runs, and the Adapter's transaction then
122
- * fails to commit;
123
- * - `A3003` — `samples.find.unique` answers a datetime the wire cannot spell.
124
- */
125
86
  export const CORPUS_TRIGGER = {
126
87
  unauthenticated: "A4000",
127
88
  guardDenies: "A4001",
@@ -133,8 +94,8 @@ export const CORPUS_TRIGGER = {
133
94
  adapterThrows: "A3001",
134
95
  commitFails: "A3002",
135
96
  unencodable: "A3003",
97
+ pipeOutputInvalid: "A3004",
136
98
  };
137
- /** The `where.id` an operation's arguments name, when it names one as a string. */
138
99
  function whereIdOf(args) {
139
100
  const where = typeof args === "object" && args !== null
140
101
  ? args.where
@@ -144,7 +105,6 @@ function whereIdOf(args) {
144
105
  : undefined;
145
106
  return typeof id === "string" ? id : undefined;
146
107
  }
147
- /** The corpus's one client guard: it decides nothing unless a row names a trigger. */
148
108
  function corpusGuard(context) {
149
109
  switch (whereIdOf(context.args)) {
150
110
  case CORPUS_TRIGGER.unauthenticated:
@@ -159,11 +119,13 @@ function corpusGuard(context) {
159
119
  return true;
160
120
  }
161
121
  }
162
- /**
163
- * The corpus's client-scope configuration: what the ClientContract hides or
164
- * restricts, and `books.label` — a VIRTUAL field added at client scope and
165
- * computed on read from `id` (F-828, plan S12).
166
- */
122
+ function corpusPipe(args) {
123
+ if (whereIdOf(args) !== CORPUS_TRIGGER.pipeOutputInvalid) {
124
+ return undefined;
125
+ }
126
+ const record = args;
127
+ return { ...record, where: { ...record.where, nope: "x" } };
128
+ }
167
129
  export const CORPUS_CLIENT_CONFIG = {
168
130
  fields: {
169
131
  books: {
@@ -187,46 +149,19 @@ export const CORPUS_CLIENT_CONFIG = {
187
149
  },
188
150
  },
189
151
  },
190
- pipelines: { guards: [corpusGuard] },
152
+ pipelines: { guards: [corpusGuard], pipes: [corpusPipe] },
191
153
  restrictions: {
192
154
  authors: { fields: { secret: { hidden: true } } },
193
155
  secrets: { operations: { find: { many: false } } },
194
156
  hiddenRes: { hidden: true },
195
157
  },
196
158
  };
197
- /** The corpus Framework's mount point; every row's path is relative to it. */
198
159
  export const CORPUS_ENTRYPOINT = "/api";
199
- /**
200
- * The corpus ClientContract's hash: what a generated client for it sends as
201
- * `Aventara-Contract-Hash`. A literal, as a generated client holds it;
202
- * `corpus-contract.spec.ts` fails, naming this constant, when the compiled
203
- * contract moves.
204
- */
205
160
  export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0";
206
- /**
207
- * The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
208
- * bytes (Q8), `protocol.hash` included (M22). A literal, as a host must
209
- * serve it byte for byte; `corpus-contract.spec.ts` fails, naming this
210
- * constant, when the compiled contract moves.
211
- */
212
161
  export const CORPUS_CLIENT_CONTRACT_DOCUMENT = '{"enums":{},"limits":{"maxBooleanNodes":50,"maxListLimit":250,"maxNestingDepth":12,"maxRequestBytes":1048576,"maxTransactionOperations":20},"protocol":{"hash":"sha256:07b1a75a6e8e88e30115ae81bcf7e3a12fa493bddce995a01867e2381bd158d0","version":1},"resources":{"authors":{"fields":{"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"name":{"capabilities":{"create":[],"filter":["equals"],"select":true,"update":[]},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"create":{"count":true,"many":true,"one":true},"delete":{"count":true,"first":true,"many":true,"unique":true},"find":{"count":true,"first":true,"many":true,"unique":true},"update":{"count":true,"first":true,"many":true,"unique":true},"upsert":{"unique":true}}},"books":{"fields":{"authorId":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"label":{"capabilities":{"select":true},"kind":"scalar","lifecycle":["COMPUTED_ON_READ","VIRTUAL"],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"find":{"many":true}}},"samples":{"fields":{"amount":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"decimal"}},"at":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"datetime"}},"blob":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"bytes"}},"count":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"bigint"}},"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"create":{"one":true},"find":{"unique":true}}},"secrets":{"fields":{"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{}}},"scalars":{"bigint":{"builtin":true},"bytes":{"builtin":true},"datetime":{"builtin":true},"decimal":{"builtin":true},"string":{"builtin":true}},"transactions":"interactive"}';
213
- /**
214
- * The hash of the corpus's SECOND ClientContract — the same model and
215
- * configuration with `transactions: "none"` (family 6's `/_transactions`
216
- * absence). A row targets it by sending this hash, exactly as a client
217
- * generated against it would; the runner gives the row the Framework whose
218
- * ClientContract it names (the director, 2026-10-05). A literal, held to the
219
- * compiled value by `corpus-contract.spec.ts`.
220
- */
221
162
  export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:d333e8396b9bf9127e0859ed69b9874f230058a597e2129e6b9e9b380daef618";
222
- /** The one record the corpus Adapter knows. */
223
163
  export const CORPUS_AUTHOR = { id: "a1", name: "Ada" };
224
- /** The `where.id` that matches nothing. */
225
164
  export const CORPUS_NOBODY = "nobody";
226
- /**
227
- * The one `samples` record, in runtime forms — what an Adapter hands core,
228
- * and what the HTTP encoders must spell in wire forms (P1).
229
- */
230
165
  export const CORPUS_SAMPLE = {
231
166
  id: "s1",
232
167
  count: 9007199254740993n,
@@ -234,25 +169,16 @@ export const CORPUS_SAMPLE = {
234
169
  amount: new Decimal("12.50"),
235
170
  blob: new Uint8Array([255, 0, 16]),
236
171
  };
237
- /** The conflict codes the corpus Adapter raises, and the text it raises them with. */
238
172
  const CONFLICTS = {
239
173
  [CORPUS_TRIGGER.conflict]: "A2008",
240
174
  [CORPUS_TRIGGER.concurrentModification]: "A2013",
241
175
  [CORPUS_TRIGGER.staleSelection]: "A2014",
242
176
  };
243
- /** The one record of each supporting Resource. */
244
177
  const SUPPORTING_RECORDS = {
245
178
  books: { id: "b1", authorId: CORPUS_AUTHOR.id },
246
179
  secrets: { id: "s1" },
247
180
  hiddenRes: { id: "h1" },
248
181
  };
249
- /**
250
- * What the corpus Adapter answers an operation. A trigger `where.id` fails it
251
- * ({@link CORPUS_TRIGGER}). Otherwise: for {@link CORPUS_NOBODY}, nothing — `0`
252
- * for a count, `[]` for a many, `null` for a single record; else `2` for a
253
- * count, the Resource's one record in a list for a many, and the one record
254
- * for a single-record operation (`samples`: {@link CORPUS_SAMPLE}).
255
- */
256
182
  function corpusAnswer(operation) {
257
183
  const id = whereIdOf(operation.arguments);
258
184
  const conflict = id === undefined ? undefined : CONFLICTS[id];
@@ -281,11 +207,6 @@ function corpusAnswer(operation) {
281
207
  }
282
208
  return CORPUS_AUTHOR;
283
209
  }
284
- /**
285
- * The fake Adapter's transaction, made to fail its commit when a step of the
286
- * plan named {@link CORPUS_TRIGGER.commitFails} — an infrastructure failure
287
- * after the work ran (`A3002`).
288
- */
289
210
  function failingCommit(transaction) {
290
211
  return async (work) => {
291
212
  let doomed = false;
@@ -303,16 +224,7 @@ function failingCommit(transaction) {
303
224
  return result;
304
225
  };
305
226
  }
306
- /**
307
- * A fresh corpus Framework, and the count of operations its Adapter has been
308
- * handed so far — a row's `adapterCalls` is read from it. `contractHash`
309
- * selects which corpus ClientContract it serves: the `transactions: "none"`
310
- * one when it is {@link CORPUS_NONE_CLIENT_CONTRACT_HASH}, the main one
311
- * otherwise (a stale or absent hash included).
312
- */
313
227
  export async function createCorpusFramework(contractHash) {
314
- // Over the widened model type: which model is discovered is a runtime fact,
315
- // and a literal-typed Framework costs type-checking the corpus never uses.
316
228
  const fake = createFakeAdapter({
317
229
  model: contractHash === CORPUS_NONE_CLIENT_CONTRACT_HASH
318
230
  ? { ...CORPUS_MODEL, transactions: "none" }
@@ -1,11 +1,10 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * Driver 1 (plan S8, S9): the protocol hosted in process, with no HTTP stack —
4
- * a catch-all host (Q12) that hands `/_contract` to `protocol.encodeContract`,
5
- * `/_transactions` to the bound shortcut `protocol.handleTransaction`, every
6
- * other request to `protocol.handleOperation`, and writes back exactly what
7
- * they answer. What
8
- * it proves is the framework's own answer, host-free; driver 2 (S13) must
9
- * answer every row identically through a real server.
3
+ * Driver 1: the protocol hosted in process, with no HTTP stack — a catch-all host
4
+ * that hands `/_contract` to `protocol.encodeContract`, `/_transactions` to the
5
+ * bound shortcut `protocol.handleTransaction`, every other request to
6
+ * `protocol.handleOperation`, and writes back exactly what they answer. What it
7
+ * proves is the framework's own answer, host-free; driver 2 must answer every row
8
+ * identically through a real server.
10
9
  */
11
10
  export declare const inProcessDriver: AvProtocolDriver;
@@ -1,13 +1,4 @@
1
1
  import { AvProtocol } from "@aventara/core/protocol";
2
- /**
3
- * Driver 1 (plan S8, S9): the protocol hosted in process, with no HTTP stack —
4
- * a catch-all host (Q12) that hands `/_contract` to `protocol.encodeContract`,
5
- * `/_transactions` to the bound shortcut `protocol.handleTransaction`, every
6
- * other request to `protocol.handleOperation`, and writes back exactly what
7
- * they answer. What
8
- * it proves is the framework's own answer, host-free; driver 2 (S13) must
9
- * answer every row identically through a real server.
10
- */
11
2
  export const inProcessDriver = async (framework) => {
12
3
  const protocol = AvProtocol.bind(framework);
13
4
  return {
@@ -1,12 +1,11 @@
1
1
  import type { AvProtocolDriver } from "./protocol-driver.js";
2
2
  /**
3
- * The per-route driver (plan S10): the protocol hosted in process as a host
4
- * that mounts ONE route per `protocol.surface()` entry — each operation on
5
- * `handleOperation`, `/_transactions` on `handleTransaction`, `/_contract` on
6
- * `encodeContract`, matched by method and path — and `protocol.answerAbsent`
7
- * as the fallback for everything it did not mount (Q12's first style).
8
- * `undefined` from the fallback is a path outside the protocol, which this
9
- * host answers with its own bodiless 404. Every corpus row must answer here
10
- * exactly as through driver 1's catch-all.
3
+ * The per-route driver: the protocol hosted in process as a host that mounts ONE
4
+ * route per `protocol.surface()` entry — each operation on `handleOperation`,
5
+ * `/_transactions` on `handleTransaction`, `/_contract` on `encodeContract`,
6
+ * matched by method and path — and `protocol.answerAbsent` as the fallback for
7
+ * everything it did not mount. `undefined` from the fallback is a path outside the
8
+ * protocol, which this host answers with its own bodiless 404. Every corpus row
9
+ * must answer here exactly as through driver 1's catch-all.
11
10
  */
12
11
  export declare const perRouteDriver: AvProtocolDriver;
@@ -1,20 +1,9 @@
1
1
  import { AvProtocol } from "@aventara/core/protocol";
2
- /** What this host answers a path the protocol says is the host's: its own bodiless 404. */
3
2
  const HOST_NOT_FOUND = Object.freeze({
4
3
  status: 404,
5
4
  headers: Object.freeze({}),
6
5
  body: "",
7
6
  });
8
- /**
9
- * The per-route driver (plan S10): the protocol hosted in process as a host
10
- * that mounts ONE route per `protocol.surface()` entry — each operation on
11
- * `handleOperation`, `/_transactions` on `handleTransaction`, `/_contract` on
12
- * `encodeContract`, matched by method and path — and `protocol.answerAbsent`
13
- * as the fallback for everything it did not mount (Q12's first style).
14
- * `undefined` from the fallback is a path outside the protocol, which this
15
- * host answers with its own bodiless 404. Every corpus row must answer here
16
- * exactly as through driver 1's catch-all.
17
- */
18
7
  export const perRouteDriver = async (framework) => {
19
8
  const protocol = AvProtocol.bind(framework);
20
9
  const mounted = new Map(protocol.surface().map((route) => [`${route.method} ${route.path}`, route]));
@@ -4,12 +4,11 @@ import type { AvProtocolDriver } from "./protocol-driver.js";
4
4
  *
5
5
  * Every row a host answers is driven — the rows whose `client.outcome` is
6
6
  * `"transport-error"` describe what a client receives instead of a framework
7
- * answer, so they are skipped (the director, 2026-10-05). Each driven row runs
8
- * against a fresh corpus Framework, through a fresh driver session, with one
9
- * request — the corpus Framework whose ClientContract hash the row's
10
- * `Aventara-Contract-Hash` names, as a generated client names it (the main
11
- * corpus contract for any other hash; the director, 2026-10-05) — and its
12
- * answer is held to the row's `server` block:
7
+ * answer, so they are skipped. Each driven row runs against a fresh corpus
8
+ * Framework, through a fresh driver session, with one request — the corpus
9
+ * Framework whose ClientContract hash the row's `Aventara-Contract-Hash` names, as
10
+ * a generated client names it — and its answer is held to the row's `server`
11
+ * block:
13
12
  *
14
13
  * - `status`, exactly;
15
14
  * - `code`: the body is a framework envelope (`AvProtocol.isOperationResponse`,
@@ -17,14 +16,14 @@ import type { AvProtocolDriver } from "./protocol-driver.js";
17
16
  * - `causeKeys`: the exact sorted key set of `cause`;
18
17
  * - `issues`: each issue's `code` and `path`, in order;
19
18
  * - `adapterCalls`: the operations the corpus Adapter was handed, exactly;
20
- * - `responseHeaders`: each named header present (names matched without
21
- * regard to case) with exactly that value — a host may add its own;
19
+ * - `responseHeaders`: each named header present (names matched without regard to
20
+ * case) with exactly that value — a host may add its own;
22
21
  * - `bodyBytes`: the body text, exactly.
23
22
  *
24
- * Resolves one result per driven row, in corpus order, each with the
25
- * mismatches found — none when the host conforms. A driver that throws is a
26
- * mismatch of its row, not a rejection of the run. Test-runner free: a
27
- * caller asserts on the results with whatever runner it uses.
23
+ * Resolves one result per driven row, in corpus order, each with the mismatches
24
+ * found — none when the host conforms. A driver that throws is a mismatch of its
25
+ * row, not a rejection of the run. Test-runner free: a caller asserts on the
26
+ * results with whatever runner it uses.
28
27
  */
29
28
  export declare function runAvProtocolConformance(driver: AvProtocolDriver): Promise<readonly {
30
29
  readonly label: string;
@@ -1,33 +1,6 @@
1
1
  import { AvProtocol } from "@aventara/core/protocol";
2
2
  import { createCorpusFramework } from "./corpus-contract.fixture.js";
3
3
  import { AvProtocolFixtures } from "./protocol-fixtures.js";
4
- /**
5
- * `runAvProtocolConformance(driver)` — the corpus, driven through one host.
6
- *
7
- * Every row a host answers is driven — the rows whose `client.outcome` is
8
- * `"transport-error"` describe what a client receives instead of a framework
9
- * answer, so they are skipped (the director, 2026-10-05). Each driven row runs
10
- * against a fresh corpus Framework, through a fresh driver session, with one
11
- * request — the corpus Framework whose ClientContract hash the row's
12
- * `Aventara-Contract-Hash` names, as a generated client names it (the main
13
- * corpus contract for any other hash; the director, 2026-10-05) — and its
14
- * answer is held to the row's `server` block:
15
- *
16
- * - `status`, exactly;
17
- * - `code`: the body is a framework envelope (`AvProtocol.isOperationResponse`,
18
- * the one envelope test) carrying that code — or, for `null`, is none;
19
- * - `causeKeys`: the exact sorted key set of `cause`;
20
- * - `issues`: each issue's `code` and `path`, in order;
21
- * - `adapterCalls`: the operations the corpus Adapter was handed, exactly;
22
- * - `responseHeaders`: each named header present (names matched without
23
- * regard to case) with exactly that value — a host may add its own;
24
- * - `bodyBytes`: the body text, exactly.
25
- *
26
- * Resolves one result per driven row, in corpus order, each with the
27
- * mismatches found — none when the host conforms. A driver that throws is a
28
- * mismatch of its row, not a rejection of the run. Test-runner free: a
29
- * caller asserts on the results with whatever runner it uses.
30
- */
31
4
  export async function runAvProtocolConformance(driver) {
32
5
  const results = [];
33
6
  for (const row of AvProtocolFixtures) {
@@ -59,7 +32,6 @@ export async function runAvProtocolConformance(driver) {
59
32
  }
60
33
  return results;
61
34
  }
62
- /** Every way `response` departs from what the row's server block states. */
63
35
  function mismatchesOf(server, response, adapterCalls) {
64
36
  const mismatches = [];
65
37
  const expect = (what, actual, expected) => {
@@ -96,12 +68,10 @@ function mismatchesOf(server, response, adapterCalls) {
96
68
  }
97
69
  return mismatches;
98
70
  }
99
- /** The row's `Aventara-Contract-Hash`, its name matched without regard to case. */
100
71
  function contractHashOf(request) {
101
72
  const value = Object.entries(request.headers).find(([name]) => name.toLowerCase() === "aventara-contract-hash")?.[1];
102
73
  return typeof value === "string" ? value : undefined;
103
74
  }
104
- /** The parsed body, or `undefined` — which no envelope is — for text that is not JSON. */
105
75
  function parsedBody(text) {
106
76
  try {
107
77
  return JSON.parse(text);
@@ -2,15 +2,13 @@ import type { Framework } from "@aventara/core";
2
2
  import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/protocol";
3
3
  /**
4
4
  * One host under test, as the protocol conformance run drives it
5
- * (`runAvProtocolConformance`): given the corpus's Framework, mount the
6
- * protocol the way that host does, then answer each corpus request as that
7
- * host would answer it over HTTP — status, headers and body text, as an
8
- * `AvProtocolResponse`.
5
+ * (`runAvProtocolConformance`): given the corpus's Framework, mount the protocol
6
+ * the way that host does, then answer each corpus request as that host would
7
+ * answer it over HTTP — status, headers and body text, as an `AvProtocolResponse`.
9
8
  *
10
- * Driver 1 (`in-process.driver.ts`) calls the bound protocol's shortcuts
11
- * directly; a real host (a `node:http` server, Phase 11's Nest module) sends
12
- * the request over its own stack and reads back what arrived. The run calls
13
- * `close` once per row, after its one request.
9
+ * Driver 1 (`in-process.driver.ts`) calls the bound protocol's shortcuts directly;
10
+ * a real host sends the request over its own stack and reads back what arrived.
11
+ * The run calls `close` once per row, after its one request.
14
12
  */
15
13
  export type AvProtocolDriver = (framework: Framework) => Promise<{
16
14
  readonly send: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
@@ -1,24 +1,20 @@
1
1
  import type { OperationCode, ValidationCode } from "@aventara/core";
2
2
  import type { AvProtocolRequest } from "@aventara/core/protocol";
3
3
  /**
4
- * One row of the Phase 10 protocol fixture corpus: the HTTP request a host
5
- * receives (`request`), what the host must answer for it (`server`), and what
6
- * a generated client must do with that answer (`client`). Types only; the
7
- * rows live in `protocol-fixtures.ts`.
4
+ * Types only; the rows live in `protocol-fixtures.ts`.
8
5
  *
9
6
  * `request` is core's own request value, `AvProtocolRequest` from
10
- * `@aventara/core/protocol` (Q9), so a row is exactly what a host hands the
11
- * bound protocol: no second request shape exists for the corpus to drift from.
7
+ * `@aventara/core/protocol`, so a row is exactly what a host hands the bound
8
+ * protocol: no second request shape exists for the corpus to drift from.
12
9
  */
13
10
  export type AvProtocolFixture = {
14
11
  /**
15
- * Stable across slices; the coverage gate reads it. A row that covers one
16
- * of the specification's success rows begins with that row's operation
17
- * name (`find.many …`, `create.one …`) or with `transaction` for the
12
+ * A row that covers one of the specification's success rows begins with that row's
13
+ * operation name (`find.many …`, `create.one …`) or with `transaction` for the
18
14
  * committed-plan row.
19
15
  */
20
16
  readonly label: string;
21
- /** Method, entrypoint-relative path, headers, and a raw or parsed body (Q11). */
17
+ /** Method, entrypoint-relative path, headers, and a raw or parsed body. */
22
18
  readonly request: AvProtocolRequest;
23
19
  readonly server: {
24
20
  /**
@@ -1,14 +1,4 @@
1
1
  import { CORPUS_AUTHOR, CORPUS_CLIENT_CONTRACT_DOCUMENT, CORPUS_CLIENT_CONTRACT_HASH, CORPUS_NOBODY, CORPUS_NONE_CLIENT_CONTRACT_HASH, CORPUS_TRIGGER, } from "./corpus-contract.fixture.js";
2
- /**
3
- * The Phase 10 protocol fixture corpus: passive data, read by the coverage
4
- * gate (`protocol-coverage.spec.ts`) and, from later slices on, driven
5
- * through each host driver. Each slice adds the rows it settles and removes
6
- * what they cover from the gate's awaiting ledger.
7
- */
8
- /**
9
- * A well-formed generated-client request. A family-11 row's answer does not
10
- * depend on it: the answer is what arrived instead of a framework envelope.
11
- */
12
2
  const OPERATION_REQUEST = {
13
3
  method: "POST",
14
4
  path: "/_resources/authors/find/many",
@@ -19,21 +9,6 @@ const OPERATION_REQUEST = {
19
9
  },
20
10
  body: { kind: "raw", content: "{}" },
21
11
  };
22
- /**
23
- * Family 11 — transport errors (§13.5; R5). Each row is a response that is NOT
24
- * a framework envelope — produced by a proxy, a broken host or the network,
25
- * never by the framework — so a client raises `TransportError`, never a
26
- * `FrameworkError`. `AvProtocol.isOperationResponse` refuses every row's body
27
- * (`envelope-guard.spec.ts`), and the generated client's emitted check is held
28
- * to agree with it row by row (`packages/client`, the envelope pin).
29
- *
30
- * Every label begins `transport-error:`. These rows describe what a client
31
- * receives, not what a host must answer, so a host driver does not drive them:
32
- * it selects its rows by `client.outcome`, skipping every row whose outcome is
33
- * `"transport-error"` (the director, 2026-10-05) — never by label or by
34
- * `server.code === null`. `adapterCalls` is `0` because the framework produced
35
- * none of these answers.
36
- */
37
12
  const transportError = (label, status, bodyBytes) => ({
38
13
  label: `transport-error: ${label}`,
39
14
  request: OPERATION_REQUEST,
@@ -87,12 +62,6 @@ const TRANSPORT_ERRORS = [
87
62
  transportError("a negative operation index", 409, envelopeText("A2008", null, { message: "x", operation: -1 })),
88
63
  transportError("a fractional operation index", 409, envelopeText("A2008", null, { message: "x", operation: 0.5 })),
89
64
  ];
90
- /**
91
- * A generated client's request to the corpus Framework for one operation of
92
- * `authors` (`corpus-contract.fixture.ts`): its identity headers name the
93
- * corpus ClientContract, and it sends its own `Aventara-Request-Id`, so the
94
- * echo every framework answer carries (Q13) is a fixed fact of the row.
95
- */
96
65
  const corpusRequest = (operation, body, requestId, overrides = {}) => ({
97
66
  method: "POST",
98
67
  path: `/_resources/authors/${operation}`,
@@ -105,20 +74,10 @@ const corpusRequest = (operation, body, requestId, overrides = {}) => ({
105
74
  body: { kind: "raw", content: JSON.stringify(body) },
106
75
  ...overrides,
107
76
  });
108
- /** The framework's own headers on an answer to a request that sent `requestId`. */
109
77
  const answeredHeaders = (requestId) => ({
110
78
  "Content-Type": "application/json",
111
79
  "Aventara-Request-Id": requestId,
112
80
  });
113
- /**
114
- * Family 2 — success statuses (§13.2; plan S8). Every standard operation
115
- * through a found route: its success code at its status — `201` exactly for
116
- * `A1002` and `A1003`, `200` otherwise, `upsert.unique`'s `A1008` included —
117
- * with `cause: null`, the result in wire form, one Adapter call, and no
118
- * `Location` (P2: the framework's two headers are all it writes). The label
119
- * begins with the operation's name, which is how the coverage gate reads a
120
- * success row.
121
- */
122
81
  const success = (operation, body, status, code, data, label = "") => {
123
82
  const requestId = `corpus-${operation.replace(".", "-")}${label === "" ? "" : "-miss"}`;
124
83
  return {
@@ -157,13 +116,6 @@ const SUCCESS_STATUSES = [
157
116
  success("delete.count", WHERE, 200, "A1007", 2),
158
117
  success("upsert.unique", { ...WHERE, create: DATA, update: DATA }, 200, "A1008", CORPUS_AUTHOR),
159
118
  ];
160
- /**
161
- * F-828 (plan S12) — a computed field added under `client.fields` is read
162
- * through the HTTP surface like one added at root: `books.label`, VIRTUAL,
163
- * computed from `id` (`corpus-contract.fixture.ts`). It is in the default read,
164
- * and an explicit `select` of it answers it alone; the Adapter is asked only for
165
- * stored columns (core's `computed-read-runtime.scope-added-field.spec.ts`).
166
- */
167
119
  const scopeAdded = (label, body, requestId, data) => ({
168
120
  label: `scope-added: ${label}`,
169
121
  request: corpusRequest("find/many", body, requestId, {
@@ -179,15 +131,9 @@ const scopeAdded = (label, body, requestId, data) => ({
179
131
  client: { outcome: "resolve" },
180
132
  });
181
133
  const SCOPE_ADDED = [
182
- scopeAdded("a computed field added under client.fields is in the default read (F-828)", {}, "corpus-scope-added-default",
183
- // The Contract orders the record's fields, not the Adapter.
184
- [{ authorId: CORPUS_AUTHOR.id, id: "b1", label: "book:b1" }]),
185
- scopeAdded("an explicit select of it answers it alone (F-828)", { select: ["label"] }, "corpus-scope-added-select", [{ label: "book:b1" }]),
134
+ scopeAdded("a computed field added under client.fields is in the default read", {}, "corpus-scope-added-default", [{ authorId: CORPUS_AUTHOR.id, id: "b1", label: "book:b1" }]),
135
+ scopeAdded("an explicit select of it answers it alone", { select: ["label"] }, "corpus-scope-added-select", [{ label: "book:b1" }]),
186
136
  ];
187
- /**
188
- * A request the protocol answers itself, before any Adapter call: the
189
- * framework's own envelope, exactly, at its code's status.
190
- */
191
137
  const refused = (label, request, status, code, message, headers = {}) => ({
192
138
  label,
193
139
  request,
@@ -205,20 +151,10 @@ const refused = (label, request, status, code, message, headers = {}) => ({
205
151
  client: { outcome: "framework-error", code },
206
152
  });
207
153
  const MALFORMED = "The request is malformed and cannot be interpreted.";
208
- /** `{"pad":"x…"}`, one byte over the default 1 MiB `maxRequestBytes` (§20.1). */
209
154
  const OVERSIZED = `{"pad":"${"x".repeat(1024 * 1024 + 1 - 10)}"}`;
210
155
  const rawBody = (requestId, content) => corpusRequest("find/many", {}, requestId, {
211
156
  body: { kind: "raw", content },
212
157
  });
213
- /**
214
- * Family 7 — body and media (§12.3, §12.6, Q11; plan S7, driven from S8). On
215
- * the raw arm: `{}` is valid (`find.many` above), and every other shape — a
216
- * list, a scalar, `null`, no body, text that is not JSON — is `A2000`; a
217
- * content type other than `application/json` is `A2011`; a decoded body over
218
- * the limit is `A2010`. On the parsed arm only the shape is the decoder's.
219
- * Routing metadata in the body is an argument like any other, refused by
220
- * core's own validation (N16), so it reaches the Framework but no Adapter.
221
- */
222
158
  const BODY_AND_MEDIA = [
223
159
  refused("body: a JSON list → 400 A2000", rawBody("corpus-body-list", "[]"), 400, "A2000", MALFORMED),
224
160
  refused("body: a JSON number → 400 A2000", rawBody("corpus-body-number", "3"), 400, "A2000", MALFORMED),
@@ -255,24 +191,16 @@ const BODY_AND_MEDIA = [
255
191
  client: { outcome: "framework-error", code: "A2004" },
256
192
  },
257
193
  ];
258
- /**
259
- * Family 8 — method (§12.1). A framework route accepts `POST` only; anything
260
- * else is `405 A2012` carrying `Allow: POST` (RFC 9110 §15.5.6; the
261
- * director, 2026-10-05).
262
- */
263
194
  const METHOD = [
264
195
  refused("method: GET on a resource route → 405 A2012 with Allow: POST", corpusRequest("find/many", {}, "corpus-method-get", { method: "GET" }), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST" }),
265
196
  ];
266
- /** A generator's `GET /_contract`, with `headers` beside its own request id. */
267
197
  const contractRequest = (requestId, headers = {}, method = "GET") => ({
268
198
  method,
269
199
  path: "/_contract",
270
200
  headers: { "Aventara-Request-Id": requestId, ...headers },
271
201
  body: { kind: "raw", content: "" },
272
202
  });
273
- /** The corpus document's entity tag: the quoted contract hash (§12.5). */
274
203
  const CORPUS_ETAG = `"${CORPUS_CLIENT_CONTRACT_HASH}"`;
275
- /** `200` with the document: its exact bytes, `ETag`, `Cache-Control: no-cache`. */
276
204
  const contractServed = (label, requestId, headers = {}) => ({
277
205
  label: `contract: ${label}`,
278
206
  request: contractRequest(requestId, headers),
@@ -289,7 +217,6 @@ const contractServed = (label, requestId, headers = {}) => ({
289
217
  },
290
218
  client: { outcome: "resolve" },
291
219
  });
292
- /** `304 Not Modified`: no body, `ETag` and `Cache-Control` kept (RFC 9110 §15.4.5). */
293
220
  const contractNotModified = (label, requestId, ifNoneMatch) => ({
294
221
  label: `contract: ${label}`,
295
222
  request: contractRequest(requestId, { "If-None-Match": ifNoneMatch }),
@@ -306,14 +233,6 @@ const contractNotModified = (label, requestId, ifNoneMatch) => ({
306
233
  },
307
234
  client: { outcome: "resolve" },
308
235
  });
309
- /**
310
- * Family 1 — contract discovery (§12.5, Q8; plan S11). `GET /_contract`
311
- * serves the ClientContract's JCS bytes with `ETag` the quoted hash and
312
- * `Cache-Control: no-cache`, needs no identity header, and answers `304` with
313
- * no body when `If-None-Match` matches — weakly, in a list, or `*`. Any other
314
- * method is `405 A2012` with `Allow: GET`. A generator resolves both `200`
315
- * and `304` ("up to date", Phase 12).
316
- */
317
236
  const CONTRACT_DISCOVERY = [
318
237
  contractServed("GET → 200 with the JCS bytes, ETag and no-cache", "corpus-contract"),
319
238
  contractServed("no identity header is required, nor judged when sent", "corpus-contract-identity", {
@@ -329,24 +248,16 @@ const CONTRACT_DISCOVERY = [
329
248
  }),
330
249
  refused("method: POST on /_contract → 405 A2012 with Allow: GET", contractRequest("corpus-contract-post", {}, "POST"), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "GET" }),
331
250
  ];
332
- /**
333
- * One node of a generated client's plan (§14, M25): the operation, its
334
- * arguments verbatim, and the fingerprint the client computed over them —
335
- * a literal, as the client sends it; `corpus-contract.spec.ts` recomputes
336
- * every one with core's `computeOperationFingerprint`.
337
- */
338
251
  const planNode = (resource, operation, args, fingerprint) => {
339
252
  const [family, variant] = operation.split(".");
340
253
  return { resource, family, variant, args, fingerprint };
341
254
  };
342
255
  const AUTHORS_FIND_MANY = planNode("authors", "find.many", {}, "fp1:1aLMnUbKspahv3fbsV7pKg");
343
- /** `POST /_transactions` with a plan of `nodes`, under the corpus identity. */
344
256
  const planRequest = (requestId, nodes, overrides = {}) => ({
345
257
  ...corpusRequest("", { operations: nodes }, requestId),
346
258
  path: "/_transactions",
347
259
  ...overrides,
348
260
  });
349
- /** A row refused by core's validation layer: its issues and cause keys, no Adapter call. */
350
261
  const invalid = (label, request, status, code, issues, operation) => ({
351
262
  label,
352
263
  request,
@@ -362,20 +273,6 @@ const invalid = (label, request, status, code, issues, operation) => ({
362
273
  },
363
274
  client: { outcome: "framework-error", code },
364
275
  });
365
- /**
366
- * Family 9 — transactions (§14; plan S9), through `/_transactions` under
367
- * ClientContract rules only (R1). A committed plan is `200 A1009` with each
368
- * step's data in plan order, a create included (P3); a rolled-back plan
369
- * answers its failing step's own code at that code's status, cause keys
370
- * `message` and `operation` (Q4, M15); a step that depends on rows an earlier
371
- * step's cascade removed is refused before anything runs (`V1019` → `422`);
372
- * and a plan node cannot carry an origin (N11).
373
- *
374
- * The R1 rows come in pairs, labelled `R1: <what> — single route` and
375
- * `R1: <what> — /_transactions`: the same refusal through both routes, the
376
- * plan's differing only by `cause.operation` (asserted pair by pair in
377
- * `corpus-contract.spec.ts`).
378
- */
379
276
  const TRANSACTIONS = [
380
277
  {
381
278
  label: "transaction — committed, a create included → 200 A1009 in plan order",
@@ -426,38 +323,31 @@ const TRANSACTIONS = [
426
323
  ...["scope", "origin"].map((key) => invalid(`transaction — a plan node carrying "${key}" cannot smuggle an origin → 422 A2004 / V1001`, planRequest(`corpus-tx-smuggle-${key}`, [
427
324
  { ...AUTHORS_FIND_MANY, [key]: "application" },
428
325
  ]), 422, "A2004", [{ code: "V1001", path: [key] }], 0)),
429
- invalid("R1: a client-hidden field — single route", corpusRequest("find/many", { select: ["id", "secret"] }, "corpus-r1-field"), 422, "A2004", [{ code: "V1005", path: ["arguments", "select", 1] }]),
430
- invalid("R1: a client-hidden field — /_transactions", planRequest("corpus-r1-field-tx", [
326
+ invalid("a client-hidden field — single route", corpusRequest("find/many", { select: ["id", "secret"] }, "corpus-r1-field"), 422, "A2004", [{ code: "V1005", path: ["arguments", "select", 1] }]),
327
+ invalid("a client-hidden field — /_transactions", planRequest("corpus-r1-field-tx", [
431
328
  AUTHORS_FIND_MANY,
432
329
  planNode("authors", "find.many", { select: ["id", "secret"] }, "fp1:1t8vSfPuohv1N5MIX8ccNw"),
433
330
  ]), 422, "A2004", [{ code: "V1005", path: ["arguments", "select", 1] }], 1),
434
- invalid("R1: a client-restricted variant — single route", {
331
+ invalid("a client-restricted variant — single route", {
435
332
  ...corpusRequest("", {}, "corpus-r1-variant"),
436
333
  path: "/_resources/secrets/find/many",
437
334
  }, 404, "A2002", [{ code: "V1006", path: ["operation"] }]),
438
- invalid("R1: a client-restricted variant — /_transactions", planRequest("corpus-r1-variant-tx", [
335
+ invalid("a client-restricted variant — /_transactions", planRequest("corpus-r1-variant-tx", [
439
336
  AUTHORS_FIND_MANY,
440
337
  planNode("secrets", "find.many", {}, "fp1:KegI5OcquDHi8z39VH11EQ"),
441
338
  ]), 404, "A2002", [{ code: "V1006", path: ["operation"] }], 1),
442
- invalid("R1: a client-hidden Resource — single route", {
339
+ invalid("a client-hidden Resource — single route", {
443
340
  ...corpusRequest("", {}, "corpus-r1-resource"),
444
341
  path: "/_resources/hiddenRes/find/many",
445
342
  }, 404, "A2001", [{ code: "V1005", path: ["resource"] }]),
446
- invalid("R1: a client-hidden Resource — /_transactions", planRequest("corpus-r1-resource-tx", [
343
+ invalid("a client-hidden Resource — /_transactions", planRequest("corpus-r1-resource-tx", [
447
344
  AUTHORS_FIND_MANY,
448
345
  planNode("hiddenRes", "find.many", {}, "fp1:UY6pLVLKM2LaIMnfQa0zGw"),
449
346
  ]), 404, "A2001", [{ code: "V1005", path: ["resource"] }], 1),
450
347
  refused("method: GET on /_transactions → 405 A2012 with Allow: POST", planRequest("corpus-tx-get", [AUTHORS_FIND_MANY], { method: "GET" }), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST" }),
451
348
  ];
452
- /** A hash no corpus ClientContract has: a stale generated client's. */
453
349
  const STALE_HASH = `sha256:${"0".repeat(64)}`;
454
- /** The FVL's validation-failure text, which every A2001/A2002 envelope carries. */
455
350
  const VALIDATION_FAILED = "Operation arguments failed framework validation.";
456
- /**
457
- * An absence answer (Q2's O3): core's own envelope for the same request,
458
- * byte for byte (`corpus-contract.spec.ts` recomputes each operation row's
459
- * through `framework.execute`), with zero Adapter calls.
460
- */
461
351
  const absent = (label, request, status, code, cause) => ({
462
352
  label: `absent: ${label}`,
463
353
  request,
@@ -476,7 +366,6 @@ const absent = (label, request, status, code, cause) => ({
476
366
  },
477
367
  client: { outcome: "framework-error", code },
478
368
  });
479
- /** A corpus request to `path`, its identity headers overridden by `headers`. */
480
369
  const absentRequest = (path, requestId, headers = {}, method = "POST") => {
481
370
  const base = corpusRequest("", {}, requestId);
482
371
  return { ...base, method, path, headers: { ...base.headers, ...headers } };
@@ -501,16 +390,6 @@ const unavailableResource = (resource) => ({
501
390
  },
502
391
  ],
503
392
  });
504
- /**
505
- * Family 6 — the absence responder (Q2's O3, Q3; plan S10). What a request
506
- * for a route the ClientContract does not advertise answers, identically
507
- * whether the host mounts one route per `protocol.surface()` entry with
508
- * `protocol.answerAbsent` as its fallback or a catch-all whose decoder answers
509
- * absence (Q12) — both styles drive these rows. Identity first, so a stale
510
- * client hears `A2005` (F-716's server half), then the Resource, then the
511
- * operation; zero Adapter calls. `_transactions` under `"none"` is asked of
512
- * the corpus's second ClientContract, named by its hash.
513
- */
514
393
  const ABSENCE = [
515
394
  absent("an unadvertised operation, current hash → 404 A2002", absentRequest("/_resources/books/delete/many", "corpus-absent-op"), 404, "A2002", unavailableOperation("books", "delete.many")),
516
395
  absent("an unadvertised operation, stale hash → 409 A2005 (regenerate)", absentRequest("/_resources/books/delete/many", "corpus-absent-stale", {
@@ -525,9 +404,9 @@ const ABSENCE = [
525
404
  "Aventara-Protocol-Version": "2",
526
405
  }), 400, "A2006", { message: "Aventara protocol version does not match the server." }),
527
406
  absent("an unknown Resource → 404 A2001", absentRequest("/_resources/nope/find/many", "corpus-absent-resource"), 404, "A2001", unavailableResource("nope")),
528
- absent("a Resource with operations: {} → 404 A2002 (M6)", absentRequest("/_resources/secrets/find/many", "corpus-absent-empty"), 404, "A2002", unavailableOperation("secrets", "find.many")),
529
- absent("a client-hidden Resource → 404 A2001 (M7)", absentRequest("/_resources/hiddenRes/find/many", "corpus-absent-hidden"), 404, "A2001", unavailableResource("hiddenRes")),
530
- absent('/_transactions under transactions "none" → 404 A2002 at ["operations"] (M17)', {
407
+ absent("a Resource with operations: {} → 404 A2002", absentRequest("/_resources/secrets/find/many", "corpus-absent-empty"), 404, "A2002", unavailableOperation("secrets", "find.many")),
408
+ absent("a client-hidden Resource → 404 A2001", absentRequest("/_resources/hiddenRes/find/many", "corpus-absent-hidden"), 404, "A2001", unavailableResource("hiddenRes")),
409
+ absent('/_transactions under transactions "none" → 404 A2002 at ["operations"]', {
531
410
  ...planRequest("corpus-absent-tx-none", [AUTHORS_FIND_MANY]),
532
411
  headers: {
533
412
  ...planRequest("corpus-absent-tx-none", [AUTHORS_FIND_MANY]).headers,
@@ -544,12 +423,6 @@ const ABSENCE = [
544
423
  ],
545
424
  }),
546
425
  ];
547
- /**
548
- * A row whose whole answer is stated: its status, its envelope byte for byte
549
- * (each object's keys in the order core writes them, as measured), its cause
550
- * keys and issues read off that envelope, and its Adapter calls. A success
551
- * code resolves; any other code is a framework error.
552
- */
553
426
  const answered = (label, request, status, adapterCalls, envelope, responseHeaders = answeredHeaders(String(request.headers["Aventara-Request-Id"]))) => {
554
427
  const { cause } = envelope;
555
428
  const issues = Array.isArray(cause?.issues)
@@ -576,16 +449,11 @@ const answered = (label, request, status, adapterCalls, envelope, responseHeader
576
449
  : { outcome: "framework-error", code: envelope.code },
577
450
  };
578
451
  };
579
- /** A corpus request to any Resource's operation route. */
580
452
  const routeRequest = (resource, operation, body, requestId, overrides = {}) => corpusRequest("", body, requestId, {
581
453
  path: `/_resources/${resource}/${operation.replace(".", "/")}`,
582
454
  ...overrides,
583
455
  });
584
456
  const NO_MATCH = "No record matched the unique selector.";
585
- /**
586
- * Family 3 — absence semantics (§13.4): what a request that matches nothing
587
- * answers, operation by operation. `find.first`'s `A1001` is family 2's.
588
- */
589
457
  const ABSENCE_SEMANTICS = [
590
458
  answered("absence: find.unique matching nothing → 404 A2003", routeRequest("authors", "find.unique", { where: { id: CORPUS_NOBODY } }, "corpus-none-find-unique"), 404, 1, { data: null, code: "A2003", cause: { message: NO_MATCH } }),
591
459
  answered("absence: find.many matching nothing → 200 A1000 with []", routeRequest("authors", "find.many", { where: { id: CORPUS_NOBODY } }, "corpus-none-find-many"), 200, 1, { data: [], code: "A1000", cause: null }),
@@ -595,34 +463,19 @@ const ABSENCE_SEMANTICS = [
595
463
  answered("absence: update.unique with no target → 404 A2003", routeRequest("authors", "update.unique", { where: { id: CORPUS_NOBODY }, data: DATA }, "corpus-none-update-unique"), 404, 1, { data: null, code: "A2003", cause: { message: NO_MATCH } }),
596
464
  answered("absence: delete.unique with no target → 404 A2003", routeRequest("authors", "delete.unique", { where: { id: CORPUS_NOBODY } }, "corpus-none-delete-unique"), 404, 1, { data: null, code: "A2003", cause: { message: NO_MATCH } }),
597
465
  ];
598
- /** `{ id: { equals: "a1" } }` wrapped in `depth` single-member `AND`s. */
599
466
  const nested = (depth) => depth === 0
600
467
  ? { id: { equals: CORPUS_AUTHOR.id } }
601
468
  : { AND: [nested(depth - 1)] };
602
- /** 51 filter nodes under one `OR`: one over the default `maxBooleanNodes` (50). */
603
469
  const TOO_MANY_NODES = {
604
470
  OR: Array.from({ length: 51 }, () => ({ id: { equals: CORPUS_AUTHOR.id } })),
605
471
  };
606
- /** A plan reference to `path` of step `operation`, whose fingerprint is `fingerprint`. */
607
472
  const ref = (operation, fingerprint, path) => ({
608
473
  $ref: { operation, fingerprint, path: [path] },
609
474
  });
610
- /** Step 0 of every reference row: create an author, selecting its id. */
611
475
  const CREATE_ID = planNode("authors", "create.one", { data: { name: CORPUS_AUTHOR.name }, select: ["id"] }, "fp1:aMKNYrUwcifOeY8ZYk4pow");
612
476
  const CREATE_ID_PRINT = CREATE_ID.fingerprint;
613
- /** A `find.first` that matches nothing: a source with no value to refer to. */
614
477
  const FIRST_NOBODY = planNode("authors", "find.first", { where: { id: CORPUS_NOBODY } }, "fp1:qNR9mD7_iVygNtmuuXjymw");
615
478
  const ARGUMENTS_FAILED = "Operation arguments failed framework validation.";
616
- /**
617
- * Family 4 — argument validation (§13.3; ADR 0008), core's own answers through
618
- * the HTTP surface: an unknown field (`V1005`), an unadvertised operator
619
- * (`V1006`), an invalid projection (`V1008`) → `A2004`; the three limits
620
- * (`V1013` nesting, `V1014` a plan's operation count, `V1015` filter nodes) →
621
- * `A2009`; a plan reference's index, fingerprint, path, type and value
622
- * (`V1016`, `V1017`, `V1010`, `V1011`, `V1018`) → `A2007`. Rollup precedence
623
- * (ADR 0008 decision 2), behaviourally: two `A2004` issues roll up together,
624
- * and a limit refuses alone, before a field defect is read.
625
- */
626
479
  const ARGUMENT_VALIDATION = [
627
480
  answered("argument: an unknown field in where → 422 A2004 / V1005", corpusRequest("find/many", { where: { nope: { equals: "x" } } }, "corpus-arg-field"), 422, 0, {
628
481
  data: null,
@@ -823,7 +676,6 @@ const ARGUMENT_VALIDATION = [
823
676
  },
824
677
  }),
825
678
  ];
826
- /** The corpus identity headers, with `overrides` applied and `omit` left out. */
827
679
  const identityHeaders = (requestId, overrides = {}, omit) => Object.fromEntries(Object.entries({
828
680
  "Content-Type": "application/json",
829
681
  "Aventara-Protocol-Version": "1",
@@ -832,13 +684,6 @@ const identityHeaders = (requestId, overrides = {}, omit) => Object.fromEntries(
832
684
  ...overrides,
833
685
  }).filter(([name]) => name !== omit));
834
686
  const STALE_CLIENT = "Generated client contract does not match the server. Regenerate the client.";
835
- /**
836
- * Family 5 — identity (§12.4; S7 follow-ups): a request missing either
837
- * identity header is malformed (`A2000`, its message naming the missing
838
- * header(s) — pilot.1 — and never a value), another protocol version is
839
- * `A2006`, a stale or malformed hash is `A2005` with Appendix B.3's text (Q3),
840
- * all on a route that exists; header names are matched without regard to case.
841
- */
842
687
  const IDENTITY = [
843
688
  refused("identity: no Aventara-Protocol-Version → 400 A2000", corpusRequest("find/many", {}, "corpus-id-no-version", {
844
689
  headers: identityHeaders("corpus-id-no-version", {}, "Aventara-Protocol-Version"),
@@ -871,16 +716,7 @@ const IDENTITY = [
871
716
  headers: Object.fromEntries(Object.entries(identityHeaders("corpus-id-case")).map(([name, value]) => [name.toLowerCase(), value])),
872
717
  }), 200, 1, { data: [CORPUS_AUTHOR], code: "A1000", cause: null }, answeredHeaders("corpus-id-case")),
873
718
  ];
874
- /** `authors.find.unique` naming the trigger `id` — what the guard or Adapter fails on. */
875
719
  const triggered = (id, requestId) => corpusRequest("find/unique", { where: { id } }, requestId);
876
- /**
877
- * Family 10 — internal and authorization (§13.2; Q5, Q6), from the corpus's
878
- * guard and Adapter failing on purpose (labelled by trigger,
879
- * `CORPUS_TRIGGER`): `A3000`–`A3003` → 500 with `{ message }` only — no
880
- * sub-code reaches the wire (Q5); `A4000` → 401, `A4001`/`A4002` → 403, with
881
- * no reason member (Q6). Every one is a framework error, never a transport
882
- * error: each body is an envelope.
883
- */
884
720
  const INTERNAL = [
885
721
  answered("internal: a guard that throws → 500 A3000", triggered(CORPUS_TRIGGER.guardThrows, "corpus-internal-a3000"), 500, 0, {
886
722
  data: null,
@@ -900,6 +736,11 @@ const INTERNAL = [
900
736
  code: "A3003",
901
737
  cause: { message: "Result serialization failed." },
902
738
  }),
739
+ answered("internal: a pipe whose output the server contract refuses → 500 A3004", routeRequest("authors", "find.many", { where: { id: CORPUS_TRIGGER.pipeOutputInvalid } }, "corpus-internal-a3004"), 500, 0, {
740
+ data: null,
741
+ code: "A3004",
742
+ cause: { message: "A server pipeline produced invalid arguments." },
743
+ }),
903
744
  answered("internal: a guard that requires authentication → 401 A4000", triggered(CORPUS_TRIGGER.unauthenticated, "corpus-internal-a4000"), 401, 0, {
904
745
  data: null,
905
746
  code: "A4000",
@@ -916,10 +757,6 @@ const INTERNAL = [
916
757
  cause: { message: "Denied by the corpus policy." },
917
758
  }),
918
759
  ];
919
- /**
920
- * Family 12 — conflict codes (§13.2): the corpus Adapter raising `A2008`,
921
- * `A2013` and `A2014` from an `update.unique` → 409, `cause.issues` absent.
922
- */
923
760
  const CONFLICT = [
924
761
  [CORPUS_TRIGGER.conflict, "A2008"],
925
762
  [CORPUS_TRIGGER.concurrentModification, "A2013"],
@@ -929,7 +766,6 @@ const CONFLICT = [
929
766
  code,
930
767
  cause: { message: `The corpus Adapter answered ${code}.` },
931
768
  }));
932
- /** The one `samples` record in wire form (P1): what both encoders must write. */
933
769
  const SAMPLE_WIRE = {
934
770
  id: "s1",
935
771
  count: "9007199254740993",
@@ -937,7 +773,6 @@ const SAMPLE_WIRE = {
937
773
  amount: "12.50",
938
774
  blob: "/wAQ",
939
775
  };
940
- /** A `samples` create whose `blob` is `blob`. */
941
776
  const sampleCreate = (blob, requestId) => routeRequest("samples", "create.one", {
942
777
  data: {
943
778
  count: "1",
@@ -946,13 +781,6 @@ const sampleCreate = (blob, requestId) => routeRequest("samples", "create.one",
946
781
  blob,
947
782
  },
948
783
  }, requestId);
949
- /**
950
- * Family 13 — wire scalars (P1, C1): a result carrying a `bigint`, a `Date`, a
951
- * `Decimal` and a `Uint8Array` is written as their exact wire strings; a
952
- * request `bytes` value whose padding carries non-zero bits is refused at the
953
- * field, and its canonical spelling accepted. (The unencodable result is
954
- * family 10's `A3003`.)
955
- */
956
784
  const WIRE_SCALARS = [
957
785
  answered("wire: a result's bigint, datetime, decimal and bytes in wire form → 200 A1000", routeRequest("samples", "find.unique", { where: { id: "s1" } }, "corpus-wire-result"), 200, 1, { data: SAMPLE_WIRE, code: "A1000", cause: null }),
958
786
  answered('wire: bytes "AB==" (non-zero pad bits) → 422 A2004 / V1003, no Adapter call', sampleCreate("AB==", "corpus-wire-pad-bits"), 422, 0, {
@@ -971,13 +799,6 @@ const WIRE_SCALARS = [
971
799
  }),
972
800
  answered('wire: bytes "AA==" (canonical) → 201 A1002', sampleCreate("AA==", "corpus-wire-canonical"), 201, 1, { data: SAMPLE_WIRE, code: "A1002", cause: null }),
973
801
  ];
974
- /**
975
- * Family 14 — correlation (Q13): `Aventara-Request-Id` is adopted when it is
976
- * 1–128 visible ASCII characters and echoed (every row above sends one and
977
- * states its echo, refusals included); otherwise it is ignored — a fresh id
978
- * is minted, whose value no row can state — and the request is never refused
979
- * over it.
980
- */
981
802
  const CORRELATION = [
982
803
  answered("correlation: a 128-character id is adopted and echoed", corpusRequest("find/many", {}, "x".repeat(128)), 200, 1, { data: [CORPUS_AUTHOR], code: "A1000", cause: null }),
983
804
  ...[
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aventara/testing",
3
- "version": "0.1.0-pilot.1",
3
+ "version": "0.1.0-pilot.2",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "Conformance and extension testing utilities for Aventara.",
6
6
  "type": "module",
@@ -20,13 +20,13 @@
20
20
  "LICENSE-ADDITIONAL-PERMISSION.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@aventara/core": "0.1.0-pilot.1"
23
+ "@aventara/core": "0.1.0-pilot.2"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
28
  "scripts": {
29
- "build": "tsc -p tsconfig.json",
29
+ "build": "tsc -p tsconfig.json --removeComments --declaration false && tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/build/declaration-comments.sanitizer.ts dist",
30
30
  "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
31
31
  "test": "vitest run --typecheck --config ../../vitest.config.mts --root .",
32
32
  "lint": "biome lint src"