@volter/twin-standard 1.0.158 → 1.0.160

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.
Files changed (100) hide show
  1. package/README.md +3 -2
  2. package/client-sdks/package.json +1 -0
  3. package/client-sdks/pnpm-lock.yaml +210 -0
  4. package/dist/client-sdks/package.json +1 -0
  5. package/dist/client-sdks/pnpm-lock.yaml +210 -0
  6. package/dist/src/assessment.d.ts +13 -3
  7. package/dist/src/assessment.js +10 -6
  8. package/dist/src/browser-assessment.js +16 -13
  9. package/dist/src/check-sources.js +2 -2
  10. package/dist/src/clients/harness.d.ts +25 -4
  11. package/dist/src/clients/harness.js +184 -22
  12. package/dist/src/clients/jira.js +11 -2
  13. package/dist/src/clients/resend.first-use.json +2 -2
  14. package/dist/src/clients/run.js +19 -14
  15. package/dist/src/conformance.d.ts +3 -4
  16. package/dist/src/conformance.js +396 -203
  17. package/dist/src/consumer.d.ts +22 -1
  18. package/dist/src/consumer.js +91 -26
  19. package/dist/src/create-pack.d.ts +1 -1
  20. package/dist/src/create-pack.js +13 -12
  21. package/dist/src/credential-issuer-import.d.ts +3 -0
  22. package/dist/src/credential-issuer-import.js +42 -0
  23. package/dist/src/derive.js +6 -3
  24. package/dist/src/gate.js +4 -0
  25. package/dist/src/index.d.ts +1 -1
  26. package/dist/src/index.js +1 -1
  27. package/dist/src/lanes.d.ts +2 -0
  28. package/dist/src/lanes.js +7 -0
  29. package/dist/src/pack-facts.d.ts +3 -0
  30. package/dist/src/pack-facts.js +36 -1
  31. package/dist/src/protocol-3.js +13 -8
  32. package/dist/src/spec-documents.d.ts +4 -0
  33. package/dist/src/spec-documents.js +110 -3
  34. package/dist/src/spec-ir-client.js +1 -1
  35. package/dist/src/spec-ir-commands.js +1 -1
  36. package/dist/src/spec-ir-lines.js +1 -1
  37. package/dist/src/spec-ir-proto.d.ts +1 -1
  38. package/dist/src/spec-patches.js +2 -2
  39. package/dist/src/tools/judge-brief.js +1 -2
  40. package/dist/src/tools/life-coverage-walk.js +38 -26
  41. package/dist/src/tools/life-coverage.js +2 -2
  42. package/dist/src/tools/packs-root.js +3 -3
  43. package/dist/src/tools/score-pack.js +7 -7
  44. package/dist/src/tools/vendor-examples.js +95 -85
  45. package/dist/src/verdict.js +1 -1
  46. package/dist/src/walk/behavior-journey.d.ts +22 -14
  47. package/dist/src/walk/behavior-journey.js +199 -133
  48. package/dist/src/walk/journey-kit.d.ts +5 -4
  49. package/dist/src/walk/journey-kit.js +6 -2
  50. package/dist/src/walk/replay.d.ts +23 -0
  51. package/dist/src/walk/replay.js +118 -0
  52. package/dist/src/walk/serve-pack.d.ts +2 -2
  53. package/dist/src/walk/serve-pack.js +44 -2
  54. package/dist/src/walk/shape.d.ts +26 -5
  55. package/dist/src/walk/shape.js +167 -57
  56. package/dist/src/walk/vendor-fetch.d.ts +7 -2
  57. package/dist/src/walk/vendor-fetch.js +13 -2
  58. package/dist/src/walk/walk-database.d.ts +9 -1
  59. package/dist/src/walk/walk-database.js +35 -13
  60. package/dist/src/walk/xml-view.d.ts +3 -2
  61. package/dist/src/walk/xml-view.js +5 -4
  62. package/package.json +6 -4
  63. package/src/assessment.ts +12 -8
  64. package/src/browser-assessment.ts +12 -5
  65. package/src/check-sources.ts +2 -2
  66. package/src/clients/harness.ts +142 -19
  67. package/src/clients/jira.ts +4 -2
  68. package/src/clients/resend.first-use.json +2 -2
  69. package/src/clients/run.ts +17 -15
  70. package/src/conformance.ts +317 -147
  71. package/src/consumer.ts +83 -21
  72. package/src/create-pack.ts +13 -12
  73. package/src/credential-issuer-import.ts +38 -0
  74. package/src/derive.ts +6 -4
  75. package/src/gate.ts +3 -0
  76. package/src/index.ts +1 -1
  77. package/src/lanes.ts +7 -0
  78. package/src/pack-facts.ts +25 -2
  79. package/src/protocol-3.ts +12 -8
  80. package/src/spec-documents.ts +92 -3
  81. package/src/spec-ir-client.ts +1 -1
  82. package/src/spec-ir-commands.ts +1 -1
  83. package/src/spec-ir-lines.ts +1 -1
  84. package/src/spec-ir-proto.ts +1 -1
  85. package/src/spec-patches.ts +2 -2
  86. package/src/tools/judge-brief.ts +1 -2
  87. package/src/tools/life-coverage-walk.ts +32 -22
  88. package/src/tools/life-coverage.ts +2 -2
  89. package/src/tools/packs-root.ts +3 -3
  90. package/src/tools/score-pack.ts +8 -8
  91. package/src/tools/vendor-examples.ts +92 -80
  92. package/src/verdict.ts +1 -1
  93. package/src/walk/behavior-journey.ts +158 -109
  94. package/src/walk/journey-kit.ts +9 -7
  95. package/src/walk/replay.ts +103 -0
  96. package/src/walk/serve-pack.ts +30 -5
  97. package/src/walk/shape.ts +157 -55
  98. package/src/walk/vendor-fetch.ts +16 -4
  99. package/src/walk/walk-database.ts +41 -15
  100. package/src/walk/xml-view.ts +5 -4
@@ -35,8 +35,7 @@
35
35
  // read for. `--sweep` prints, without changing done, every actor field (creator, user, *_by, ...) no `equals` pins and every
36
36
  // list no expect closes: advice for fixing a ruling as a class (its hits include lists a pinned text already fixes).
37
37
  //
38
- // Usage: twin-standard examples <vendor> [--sweep] (from the pack repository's root; twin-world's
39
- // bun scripts/vendor-examples.ts <vendor> runs the same)
38
+ // Usage: twin-standard examples <vendor> [--sweep] (from the pack repository's root)
40
39
  import { parseExactJson, exactInteger, isExactIntegerValue } from '@volter/world-core/exact-json';
41
40
  import { hasOwnSurface } from '../lanes.ts';
42
41
  import { archiveSource } from '../archive-source.ts';
@@ -48,7 +47,7 @@ import { basename, join, resolve } from 'node:path';
48
47
  import { gunzipSync } from 'node:zlib';
49
48
  import { inMemoryWorld, walk, type Failure, usePacks } from '../walk/behavior-journey.ts';
50
49
  import { vendorFetch, vendorStream } from '../walk/vendor-fetch.ts';
51
- import { at as selectorAt, captureAt, expandFills, isResp, isGit, isLine, isSocket, isWait, type Journey, type JourneyStep, type LineStep } from '../walk/journey-kit.ts';
50
+ import { at as selectorAt, captureAt, expandFills, isResp, isGit, isSocket, isWait, type Journey, type JourneyStep, type LineStep } from '../walk/journey-kit.ts';
52
51
  import { lanesOf, shapeChecker } from '../walk/shape.ts';
53
52
  import { applySpecPatch, discoveryToOpenAPI, mergeDocuments, readSpecDocuments, slug, type SpecPatch } from '../spec.ts';
54
53
  import { packsRoot } from './packs-root.ts';
@@ -63,7 +62,7 @@ usePacks(PACKS);
63
62
  * still be the sample's type. `schema` is the served spec's schema of the answer, which says where null is allowed. */
64
63
  /** `placeholder`: strings of the published input matching it stand for ids the World mints (openai's `vs_abc123`), so the
65
64
  * step sends a string there, not that one. */
66
- type Example = { names?: string[]; wire?: 'client'; patchRemoved?: boolean; id: string; operation: string; lane?: string; status?: number; output: unknown; input?: unknown; partial?: boolean; placeholder?: RegExp; target?: string; restXml?: string; lines?: string[]; sample?: boolean; schema?: SpecSchema; request?: { method: string; url: string; headers?: Record<string, string> }; authorizationAbsent?: boolean };
65
+ type Example = { names?: string[]; wire?: 'client'; patchRemoved?: boolean; id: string; operation: string; lane?: string; status?: number; output: unknown; input?: unknown; partial?: boolean; placeholder?: RegExp; target?: string; restXml?: RestXml; lines?: string[]; sample?: boolean; schema?: SpecSchema; request?: { method: string; url: string; headers?: Record<string, string> }; authorizationAbsent?: boolean };
67
66
  /** A served spec's schema, its references resolved lazily (`resolveSchema`). */
68
67
  type SpecSchema = { $ref?: string; nullable?: boolean; properties?: Record<string, SpecSchema>; items?: SpecSchema; allOf?: SpecSchema[]; anyOf?: SpecSchema[]; oneOf?: SpecSchema[]; [k: string]: unknown };
69
68
  let specSchemas: Record<string, SpecSchema> = {};
@@ -71,6 +70,7 @@ let specSchemas: Record<string, SpecSchema> = {};
71
70
  const schemasByOperation: Record<string, Record<string, SpecSchema>> = {};
72
71
  /** Each operation's request-body schema and its JSON answers' schemas by status, as the served spec gives them. */
73
72
  const operationSchemas: Record<string, { request?: SpecSchema; responses: Record<string, SpecSchema> }> = {};
73
+ const operationKey = (operation: string, lane: string | undefined): string => lane ? `${lane}:${operation}` : operation;
74
74
 
75
75
  /** A page's placeholder id in an answer-map key is the id sent at the corresponding published request position.
76
76
  * Only declared placeholders bind, and an ambiguous repeated placeholder cannot stand for a guessed map key. */
@@ -94,7 +94,7 @@ function answerKeys(output: unknown, published: unknown, sent: unknown, placehol
94
94
  return mapped(output);
95
95
  }
96
96
 
97
- /** The schemas the twin serves: the vendor's, with the pack's spec patches (spec/patches.json) applied as derive-pack
97
+ /** The schemas the twin serves: the vendor's, with the pack's spec patches (spec/patches.json) applied as the derivation
98
98
  * applies them. A sample is checked against what the twin is derived from; the published examples themselves stay the
99
99
  * vendor's bytes. */
100
100
  function served<T extends object>(pkg: string, doc: T): T {
@@ -107,7 +107,9 @@ function served<T extends object>(pkg: string, doc: T): T {
107
107
 
108
108
  // ── a restXml service's examples (S3's): the input where each member's binding puts it, the answer read by its shape ──
109
109
  type Json = any;
110
- let xmlModel: { shapes: Record<string, Json> } | undefined;
110
+ type SmithyModel = { shapes: Record<string, Json> };
111
+ type RestXml = { id: string; model: SmithyModel };
112
+ const restXmlService = (model: SmithyModel): boolean => Object.values(model.shapes).some((shape) => shape.type === 'service' && shape.traits?.['aws.protocols#restXml'] !== undefined);
111
113
  /** A translated placeholder-key map is a complete map even when its surrounding documented request is partial. */
112
114
  function requestMapKeysHold(published: unknown, translated: unknown, sent: unknown, placeholder?: RegExp): boolean {
113
115
  if (!placeholder || !published || typeof published !== 'object') return true;
@@ -122,10 +124,10 @@ function requestMapKeysHold(published: unknown, translated: unknown, sent: unkno
122
124
  }
123
125
 
124
126
  const PRELUDE: Record<string, string> = { Boolean: 'boolean', PrimitiveBoolean: 'boolean', Integer: 'integer', PrimitiveInteger: 'integer', Long: 'long', PrimitiveLong: 'long', Float: 'float', Double: 'double' };
125
- const scalarType = (target: string): string => xmlModel?.shapes[target]?.type ?? PRELUDE[target.split('#')[1] ?? ''] ?? 'string';
126
- const scalarOf = (target: string, v: unknown): unknown => {
127
+ const scalarType = (model: SmithyModel, target: string): string => model.shapes[target]?.type ?? PRELUDE[target.split('#')[1] ?? ''] ?? 'string';
128
+ const scalarOf = (model: SmithyModel, target: string, v: unknown): unknown => {
127
129
  if (typeof v !== 'string') return v;
128
- const t = scalarType(target);
130
+ const t = scalarType(model, target);
129
131
  if (t === 'boolean') return v === 'true';
130
132
  if (['integer', 'long', 'short', 'byte'].includes(t)) return /^[+-]?\d+$/.test(v) ? exactInteger(v) : v;
131
133
  return ['float', 'double'].includes(t) && v !== '' && !Number.isNaN(Number(v)) ? Number(v) : v;
@@ -134,39 +136,39 @@ const arrayOf = (v: unknown): unknown[] => (v === undefined || v === '' ? [] : A
134
136
 
135
137
  /** An answer's XML members (xml-view) read by the model's shape: each member under its xmlName, a flattened list the
136
138
  * repeated element, a wrapped list its container's member elements, scalars as their shape's type. */
137
- function typedXml(target: string, v: unknown): unknown {
138
- const shape = xmlModel?.shapes[target];
139
- if (!shape || shape.type !== 'structure') return scalarOf(target, v);
139
+ function typedXml(model: SmithyModel, target: string, v: unknown): unknown {
140
+ const shape = model.shapes[target];
141
+ if (!shape || shape.type !== 'structure') return scalarOf(model, target, v);
140
142
  if (!v || typeof v !== 'object') return v === '' ? {} : v;
141
143
  const obj = v as Record<string, unknown>;
142
144
  const out: Record<string, unknown> = {};
143
145
  for (const [name, m] of Object.entries((shape.members ?? {}) as Record<string, Json>)) {
144
146
  if (Object.keys(m.traits ?? {}).some((t) => /^smithy\.api#http(Header|PrefixHeaders|ResponseCode)$/.test(t))) continue;
145
147
  const el = String(m.traits?.['smithy.api#xmlName'] ?? name);
146
- const ts = xmlModel!.shapes[m.target];
148
+ const ts = model.shapes[m.target];
147
149
  const raw = obj[el];
148
150
  if (ts?.type === 'list') {
149
- if (m.traits?.['smithy.api#xmlFlattened'] !== undefined) { if (raw !== undefined) out[name] = arrayOf(raw).map((i) => typedXml(ts.member.target, i)); continue; }
151
+ if (m.traits?.['smithy.api#xmlFlattened'] !== undefined) { if (raw !== undefined) out[name] = arrayOf(raw).map((i) => typedXml(model, ts.member.target, i)); continue; }
150
152
  if (raw === undefined) continue;
151
153
  const memberEl = String(ts.member.traits?.['smithy.api#xmlName'] ?? 'member');
152
- out[name] = arrayOf(raw === '' ? undefined : (raw as Record<string, unknown>)[memberEl]).map((i) => typedXml(ts.member.target, i));
153
- } else if (raw !== undefined) out[name] = typedXml(m.target, raw);
154
+ out[name] = arrayOf(raw === '' ? undefined : (raw as Record<string, unknown>)[memberEl]).map((i) => typedXml(model, ts.member.target, i));
155
+ } else if (raw !== undefined) out[name] = typedXml(model, m.target, raw);
154
156
  }
155
157
  return out;
156
158
  }
157
159
 
158
160
  /** A restXml operation's success answer as its output structure: the body's members by shape (or its payload
159
161
  * member's), and the members its headers carry. */
160
- function restXmlAnswer(opId: string, body: unknown, headers: Record<string, string> = {}): unknown {
161
- const out = xmlModel!.shapes[xmlModel!.shapes[opId]?.output?.target ?? ''];
162
+ function restXmlAnswer({ id: opId, model }: RestXml, body: unknown, headers: Record<string, string> = {}): unknown {
163
+ const out = model.shapes[model.shapes[opId]?.output?.target ?? ''];
162
164
  if (!out) return body;
163
165
  const members = Object.entries((out.members ?? {}) as Record<string, Json>);
164
166
  const payload = members.find(([, m]) => m.traits?.['smithy.api#httpPayload'] !== undefined);
165
- const answer = (payload ? { [payload[0]]: typedXml(payload[1].target, body) } : typedXml(xmlModel!.shapes[opId].output.target, body)) as Record<string, unknown>;
167
+ const answer = (payload ? { [payload[0]]: typedXml(model, payload[1].target, body) } : typedXml(model, model.shapes[opId].output.target, body)) as Record<string, unknown>;
166
168
  const lower = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
167
169
  for (const [name, m] of members) {
168
170
  const h = m.traits?.['smithy.api#httpHeader'];
169
- if (h !== undefined && lower[String(h).toLowerCase()] !== undefined) answer[name] = scalarOf(m.target, lower[String(h).toLowerCase()]);
171
+ if (h !== undefined && lower[String(h).toLowerCase()] !== undefined) answer[name] = scalarOf(model, m.target, lower[String(h).toLowerCase()]);
170
172
  // a map bound to a header prefix (S3's Metadata, `x-amz-meta-*`): every header under it, by the rest of its name
171
173
  const prefix = m.traits?.['smithy.api#httpPrefixHeaders'];
172
174
  if (prefix !== undefined) answer[name] = Object.fromEntries(Object.entries(lower).filter(([k]) => k.startsWith(String(prefix).toLowerCase())).map(([k, v]) => [k.slice(String(prefix).length), v]));
@@ -176,8 +178,8 @@ function restXmlAnswer(opId: string, body: unknown, headers: Record<string, stri
176
178
 
177
179
  /** Where a restXml example's input is not what the step sends: each member the example gives, at its binding (a path
178
180
  * label, a query key, a header; a payload or body member is the step's body and is not compared). */
179
- function restXmlInputProblems(opId: string, input: Record<string, unknown>, path: string, headers: Record<string, string>, placeholder: RegExp, rawPath?: string): string[] {
180
- const shape = xmlModel!.shapes[xmlModel!.shapes[opId]?.input?.target ?? ''];
181
+ function restXmlInputProblems({ id: opId, model }: RestXml, input: Record<string, unknown>, path: string, headers: Record<string, string>, placeholder: RegExp, rawPath?: string): string[] {
182
+ const shape = model.shapes[model.shapes[opId]?.input?.target ?? ''];
181
183
  const url = new URL(path, 'http://twin.local');
182
184
  // the step as written, its captures unfilled: a value it takes from a capture is the World's own id (an S3 upload's
183
185
  // UploadId), which stands for the example's literal as a spec's ids stand for the World's (architecture, "Published examples")
@@ -215,11 +217,13 @@ function lenientJson(text: string): string {
215
217
  * response's `example`/`examples` (a `$ref` to `components.examples` followed), a Swagger 2 response's `examples`, Stripe's
216
218
  * fixtures (one sample per resource, beside its spec), and OpenAI's `x-oaiMeta.examples` (its `response` is JSON text; one that is not JSON, a stream or an abridged answer, is
217
219
  * kept as text and compared with the published framing and payload). */
218
- function published(pkg: string): Example[] {
220
+ function published(pkg: string, lane: string | undefined): Example[] {
219
221
  // Both the vendor API and its lanes publish examples; a root spec must not hide a lane. A lane's example names its
220
222
  // lane, the operation being the lane's own (its owner is `<lane>:<id>` in the vendor's owners)
221
- const own = [...(hasOwnSurface(pkg) ? fromSpec(pkg) : []), ...fromDocs(pkg)];
222
- return [...own, ...lanesOf(pkg).flatMap((lane) => published(lane).map((e) => ({ ...e, lane: e.lane ? `${basename(lane)}:${e.lane}` : basename(lane) })))];
223
+ const smithy = ['smithy.json.gz', 'smithy.json'].map((name) => join(pkg, 'spec', name)).find(existsSync);
224
+ const model = smithy ? parseExactJson((smithy.endsWith('.gz') ? gunzipSync(readFileSync(smithy)) : readFileSync(smithy)).toString('utf8')) as SmithyModel : undefined;
225
+ const own = [...(hasOwnSurface(pkg) ? fromSpec(pkg, lane, model) : []), ...fromDocs(pkg, lane, model)].map((e) => ({ ...e, lane }));
226
+ return [...own, ...lanesOf(pkg).flatMap((dir) => published(dir, lane ? `${lane}:${basename(dir)}` : basename(dir)))];
223
227
  }
224
228
 
225
229
  /** The examples a vendor's documentation pages publish beside its spec (a schedule page's expressions, each with what
@@ -231,7 +235,7 @@ function published(pkg: string): Example[] {
231
235
  * reference writes every property, and every scalar as a string, `"verified": "true"` beside a schema saying boolean)
232
236
  * shows what the answer can hold, not one request's answer: its example is a sample against the served schema, and a
233
237
  * string where the schema says number, integer or boolean is read as that type, in the answer and the request body. */
234
- function fromDocs(pkg: string): Example[] {
238
+ function fromDocs(pkg: string, lane: string | undefined, model: SmithyModel | undefined): Example[] {
235
239
  const file = join(pkg, 'spec', 'doc-examples.json');
236
240
  if (!existsSync(file)) return [];
237
241
  const docs = parseExactJson(readFileSync(file, 'utf8')) as Array<{ wire?: 'client'; title: string; source: string; quote?: string; operation: string; input?: unknown; output?: unknown; means: string; status?: number; rendered?: boolean; placeholders?: { names: string[]; quote: string }; request?: { method: string; url: string; headers?: Record<string, string> } }>;
@@ -261,11 +265,10 @@ function fromDocs(pkg: string): Example[] {
261
265
  // a page that names its own variables for the reader to fill ("Make sure to fill in the … BRANCH_NAME … variables")
262
266
  // lists them with that sentence quoted: each stands for any value, and the published text stays as written
263
267
  for (const d of docs) if (d.placeholders && (!d.placeholders.names?.length || !d.placeholders.quote?.trim())) throw new Error(`${file}: "${d.title}" names placeholders without the page's words for them (placeholders.quote)`);
264
- const smithy = ['smithy.json.gz', 'smithy.json'].some((f) => existsSync(join(pkg, 'spec', f)));
265
268
  // a page's example is a fragment (an expression, not the request that carries it): the body holds it
266
269
  return docs.map((d) => {
267
270
  if (!d.rendered) return fromPage(d, d.output, d.input);
268
- const schemas = operationSchemas[d.operation];
271
+ const schemas = operationSchemas[operationKey(d.operation, lane)];
269
272
  const schema = schemas?.responses[String(d.status ?? 200)];
270
273
  if (!schema) throw new Error(`${file}: "${d.title}" is rendered from a schema the served spec does not give (${d.operation}, ${d.status ?? 200})`);
271
274
  // the renderer writes any string it cannot fill as the word "string": in a rendered input it stands for any string
@@ -276,7 +279,13 @@ function fromDocs(pkg: string): Example[] {
276
279
  function fromPage(d: (typeof docs)[number], output: unknown, input: unknown): Example {
277
280
  const named = d.placeholders?.names.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
278
281
  const placeholder = named ? new RegExp(`${placeholderIds.source}|^(?:${named})$`, placeholderIds.flags) : placeholderIds;
279
- return { names: d.placeholders?.names, ...(d.wire ? { wire: d.wire } : {}), id: `${d.operation}#doc ${d.title}`, operation: d.operation, output, ...(d.status ? { status: d.status } : {}), ...(input !== undefined ? { input, partial: true, placeholder } : {}), ...(smithy ? (xmlModel ? { restXml: Object.keys(xmlModel.shapes).find((k) => k.endsWith(`#${d.operation}`)) } : { target: d.operation }) : {}), ...(d.request ? { request: d.request } : {}), ...(authorizationAbsent.has(d) ? { authorizationAbsent: true } : {}) };
282
+ let restXml: RestXml | undefined;
283
+ if (model && restXmlService(model)) {
284
+ const id = Object.keys(model.shapes).find((key) => key.endsWith(`#${d.operation}`));
285
+ if (!id) throw new Error(`${file}: "${d.title}" names no operation in its XML service (${d.operation})`);
286
+ restXml = { id, model };
287
+ }
288
+ return { names: d.placeholders?.names, ...(d.wire ? { wire: d.wire } : {}), id: `${d.operation}#doc ${d.title}`, operation: d.operation, output, ...(d.status ? { status: d.status } : {}), ...(input !== undefined ? { input, partial: true, placeholder } : {}), ...(model ? (restXml ? { restXml } : { target: d.operation }) : {}), ...(d.request ? { request: d.request } : {}), ...(authorizationAbsent.has(d) ? { authorizationAbsent: true } : {}) };
280
289
  }
281
290
  }
282
291
 
@@ -351,11 +360,23 @@ function decodedExample(media: string, value: unknown): unknown {
351
360
  try { return parseExactJson(value); } catch { return value; }
352
361
  }
353
362
 
354
- function fromSpec(pkg: string): Example[] {
363
+ function fromSpec(pkg: string, lane: string | undefined, model: SmithyModel | undefined): Example[] {
355
364
  const rfc = existsSync(join(pkg, 'spec')) ? readdirSync(join(pkg, 'spec')).find((f) => /^rfc\d+\.txt$/.test(f)) : undefined;
356
365
  if (rfc) return scenarios(readFileSync(join(pkg, 'spec', rfc), 'utf8'));
357
- // a spec vendored as published or gzipped, as derive-pack reads it
358
- const spec = ['smithy.json.gz', 'smithy.json', 'openapi.json.gz', 'openapi.json', 'openapi.yaml.gz', 'openapi.yaml', 'discovery.json.gz', 'discovery.json'].map((f) => join(pkg, 'spec', f)).find((p) => existsSync(p));
366
+ const out: Example[] = [];
367
+ if (model) {
368
+ const restXml = restXmlService(model);
369
+ for (const [id, shape] of Object.entries(model.shapes)) {
370
+ const examples = shape.traits?.['smithy.api#examples'] as Array<{ title: string; input?: unknown; output?: unknown; error?: { content?: unknown } }> | undefined;
371
+ const operation = id.split('#')[1];
372
+ // an awsJson operation is named by its x-amz-target header, `<service>.<operation>`; its body is the input. A
373
+ // restXml operation (S3's) is named by its method and path, and its input is where each member's binding puts it
374
+ for (const e of examples ?? []) out.push({ id: `${operation}#${e.title}`, operation, output: e.error ? e.error.content : e.output, input: e.input ?? {}, ...(restXml ? { restXml: { id, model } } : { target: operation }) });
375
+ }
376
+ return out;
377
+ }
378
+ // a spec vendored as published or gzipped, as the derivation reads it
379
+ const spec = ['openapi.json.gz', 'openapi.json', 'openapi.yaml.gz', 'openapi.yaml', 'discovery.json.gz', 'discovery.json'].map((f) => join(pkg, 'spec', f)).find((p) => existsSync(p));
359
380
  // a Protocol Buffers service publishes no examples in its .proto files: its examples are its documentation's alone
360
381
  if (!spec && existsSync(join(pkg, 'spec', 'proto'))) return [];
361
382
  // a command table (Redis's src/commands/*.json) publishes no examples either: its examples are its documentation's
@@ -366,23 +387,9 @@ function fromSpec(pkg: string): Example[] {
366
387
  const documents = spec ? undefined : readSpecDocuments(join(pkg, 'spec'));
367
388
  if (!spec && !documents) throw new Error(`${pkg}: no published-example reader for this spec yet`);
368
389
  const text = spec ? (spec.endsWith('.gz') ? gunzipSync(readFileSync(spec)) : readFileSync(spec)).toString('utf8') : '';
369
- const out: Example[] = [];
370
- if (spec && /smithy\.json(\.gz)?$/.test(spec)) {
371
- const model = parseExactJson(text) as { shapes: Record<string, { type?: string; traits?: Record<string, unknown> }> };
372
- const restXml = Object.values(model.shapes).some((sh) => sh.type === 'service' && sh.traits?.['aws.protocols#restXml'] !== undefined);
373
- if (restXml) xmlModel = model as { shapes: Record<string, Json> };
374
- for (const [id, shape] of Object.entries(model.shapes)) {
375
- const examples = shape.traits?.['smithy.api#examples'] as Array<{ title: string; input?: unknown; output?: unknown; error?: { content?: unknown } }> | undefined;
376
- const operation = id.split('#')[1];
377
- // an awsJson operation is named by its x-amz-target header, `<service>.<operation>`; its body is the input. A
378
- // restXml operation (S3's) is named by its method and path, and its input is where each member's binding puts it
379
- for (const e of examples ?? []) out.push({ id: `${operation}#${e.title}`, operation, output: e.error ? e.error.content : e.output, input: e.input ?? {}, ...(restXml ? { restXml: id } : { target: operation }) });
380
- }
381
- return out;
382
- }
383
390
  type Op = { operationId?: string; responses?: Record<string, Record<string, unknown>>; 'x-oaiMeta'?: { examples?: unknown } };
384
391
  type Doc = { paths: Record<string, Record<string, Op>>; components?: { examples?: Record<string, { value?: unknown }>; schemas?: Record<string, SpecSchema> }; definitions?: Record<string, SpecSchema> };
385
- // a Google API Discovery document publishes no examples; it is read as the OpenAPI document derive-pack reads it as,
392
+ // a Google API Discovery document publishes no examples; it is read as the OpenAPI document the derivation reads it as,
386
393
  // its patches applied to the Discovery document first (spec-ir-discovery.ts)
387
394
  const discovery = spec !== undefined && /discovery\.json(\.gz)?$/.test(spec);
388
395
  const docs = (documents ? Object.values(documents.documents) : [discovery ? discoveryToOpenAPI(parseExactJson(text)) : /\.yaml(\.gz)?$/.test(spec!) ? parseExactJson(JSON.stringify(parseYaml(text, { intAsBigInt: true }), (_key, value) => typeof value === 'bigint' ? exactInteger(String(value)) : value)) : parseExactJson(text)]) as Doc[];
@@ -399,8 +406,9 @@ function fromSpec(pkg: string): Example[] {
399
406
  if (!op || typeof op !== 'object' || !('responses' in op)) continue;
400
407
  // Swagger 2: the request is the `in: body` parameter's schema, an answer its response's `schema`
401
408
  const bodyParam = ((op as { parameters?: Array<{ in?: string; schema?: SpecSchema }> }).parameters ?? []).find((x) => x.in === 'body');
402
- schemasByOperation[idOf(op, method, path)] = specSchemas;
403
- operationSchemas[idOf(op, method, path)] = {
409
+ const key = operationKey(idOf(op, method, path), lane);
410
+ schemasByOperation[key] = specSchemas;
411
+ operationSchemas[key] = {
404
412
  request: json((op as { requestBody?: { content?: unknown } }).requestBody?.content) ?? bodyParam?.schema,
405
413
  responses: Object.fromEntries(Object.entries(op.responses ?? {}).flatMap(([code, r]) => { const sch = json(r.content) ?? (r as { schema?: SpecSchema }).schema; return sch ? [[code, sch]] : []; })),
406
414
  };
@@ -829,17 +837,17 @@ function outputSchemaMismatches(schema: SpecSchema | undefined, value: unknown,
829
837
  * recorded with the per-example classification. Request echoes and generated output are absent. */
830
838
  const storedCollectionFields: Record<string, Record<string, string[]>> = {
831
839
  'ai-gateway': { listModels: ['data'] },
832
- aws: { ListBuckets: ['Buckets'], ListMultipartUploads: ['Uploads'], ListObjects: ['Contents', 'CommonPrefixes'], ListObjectsV2: ['Contents', 'CommonPrefixes'] },
833
- cloudflare: { ListBuckets: ['Buckets'], ListMultipartUploads: ['Uploads'], ListObjects: ['Contents', 'CommonPrefixes'], ListObjectsV2: ['Contents', 'CommonPrefixes'] },
840
+ aws: { 's3:ListBuckets': ['Buckets'], 's3:ListMultipartUploads': ['Uploads'], 's3:ListObjects': ['Contents', 'CommonPrefixes'], 's3:ListObjectsV2': ['Contents', 'CommonPrefixes'] },
841
+ cloudflare: { 'r2:ListBuckets': ['Buckets'], 'r2:ListMultipartUploads': ['Uploads'], 'r2:ListObjects': ['Contents', 'CommonPrefixes'], 'r2:ListObjectsV2': ['Contents', 'CommonPrefixes'] },
834
842
  discord: { oauth2_token: ['guild.roles'], get_my_oauth2_authorization: ['scopes'] },
835
843
  github: { 'repos/get-branch': ['commit.parents'], 'repos/get-commit': ['parents', 'files'], 'repos/get-content': ['', 'entries'], 'repos/compare-commits': ['commits', 'files'], 'git/get-tree': ['tree'], 'git/list-matching-refs': [''], 'repos/get-repo-rulesets': [''] },
836
- hackernews: { getItem: ['kids'], getUser: ['submitted'], getUpdates: ['items', 'profiles'], getTopStories: [''], getNewStories: [''], getBestStories: [''], getAskStories: [''], getShowStories: [''], getJobStories: [''] },
844
+ hackernews: { 'api:getItem': ['kids'], 'api:getUser': ['submitted'], 'api:getUpdates': ['items', 'profiles'], 'api:getTopStories': [''], 'api:getNewStories': [''], 'api:getBestStories': [''], 'api:getAskStories': [''], 'api:getShowStories': [''], 'api:getJobStories': [''] },
837
845
  linkedin: { findOrganizationAclsByRoleAssignee: ['elements'] },
838
846
  'npm-registry': { 'libnpmsearch.searchStream': ['objects', 'objects.[].package.maintainers'] },
839
847
  openrouter: { getCurrentKey: ['data.allowed_data_regions'], getModels: ['data'] },
840
848
  redis: { SCAN: ['[].1'] },
841
849
  resend: { 'domains/get': ['records'], 'emails/list-receiving': ['data', 'data.[].attachments'] },
842
- sentry: { listOrganizations: ['', '[].features'] },
850
+ sentry: { 'api:listOrganizations': ['', '[].features'] },
843
851
  slack: { conversations_rename: ['channel.previous_names'], conversations_invite: ['channel.previous_names'], conversations_members: ['members'], conversations_list: ['channels', 'channels.[].previous_names'], users_conversations: ['channels', 'channels.[].previous_names'], conversations_info: ['channel.previous_names'], conversations_replies: ['messages'], conversations_history: ['messages'], usergroups_list: ['usergroups'] },
844
852
  tinybird: { datasources: ['datasources'], jobs: ['jobs'], pipes: ['pipes'], tokens: ['tokens'], token_get: ['scopes'], token_refresh: ['scopes'], pipe_set_endpoint: ['nodes'] },
845
853
  };
@@ -931,7 +939,7 @@ if (!vendor || vendor.startsWith('-')) { console.error('usage: twin-standard exa
931
939
  const PKG = join(PACKS, vendor);
932
940
  const file = join(PKG, 'journeys', 'vendor-examples.json');
933
941
  await loadPlaceholders(PKG);
934
- const examples = published(PKG);
942
+ const examples = published(PKG, undefined);
935
943
  const byId = new Map(examples.map((e) => [e.id, e]));
936
944
 
937
945
  if (byId.size !== examples.length) throw new Error(`${vendor}: ${examples.length - byId.size} published examples share an id`);
@@ -948,9 +956,11 @@ const shape = await shapeChecker(PKG);
948
956
  // (a page of an operation the spec lacks can match another's route)
949
957
  for (const e of examples) {
950
958
  if (e.patchRemoved || !e.request || !shape) continue;
951
- const routed = shape.operationOf(e.request.method, e.request.url, {})?.op.id;
959
+ const hit = shape.operationOf(e.request.method, new URL(e.request.url, `http://${journey.host ?? 'twin.local'}`).href, e.request.headers);
960
+ const routed = hit ? operationKey(hit.op.id, hit.lane) : undefined;
952
961
  if (e.operation === 'gap' && routed === undefined) continue;
953
- if (routed !== e.operation) throw new Error(`spec/doc-examples.json: "${e.id}" sends ${e.request.method} ${e.request.url}, which the spec routes to ${routed ?? 'no operation'}, not ${e.operation}`);
962
+ const expected = operationKey(e.operation, e.lane);
963
+ if (routed !== expected) throw new Error(`spec/doc-examples.json: "${e.id}" sends ${e.request.method} ${e.request.url}, which the spec routes to ${routed ?? 'no operation'}, not ${expected}`);
954
964
  }
955
965
  const sweeping = process.argv.includes('--sweep');
956
966
  const sweepHits: string[] = [];
@@ -1065,17 +1075,19 @@ await walk(journey, world.root(), failures, {
1065
1075
  },
1066
1076
  after: (step, _path, answer, stepFailures) => {
1067
1077
  if (isGit(step) || isWait(step) || isSocket(step)) return;
1068
- if (!isResp(step) && !isLine(step as JourneyStep & LineStep)) {
1078
+ const request = answer.request;
1079
+ const hit = request ? shape?.operationOf(request.method, request.url, request.headers) : undefined;
1080
+ const operation = hit?.op;
1081
+ if (request) {
1069
1082
  const st = step as JourneyStep;
1070
- const method = (st.method ?? 'GET').toUpperCase();
1083
+ const method = request.method;
1071
1084
  // a read by what the operation does (an RPC wire POSTs its reads), else by its verb
1072
- const op = shape?.operationOf(method, _path, { ...(journey.headers ?? {}), ...(st.headers ?? {}) })?.op;
1073
- const opClass = op?.class;
1085
+ const opClass = operation?.class;
1074
1086
  const isRead = opClass ? ['retrieve', 'list', 'computed'].includes(opClass) : method === 'GET';
1075
- if (isRead) reads.push({ path: bare(_path), body: JSON.stringify(answer.body ?? '') });
1087
+ if (isRead) reads.push({ path: bare(request.url), body: JSON.stringify(answer.body ?? '') });
1076
1088
  else if (st.example && answer.status < 300 && answer.body && typeof answer.body === 'object' && !Array.isArray(answer.body) && Object.keys(answer.body).length <= 3) {
1077
1089
  const body = answer.body as Record<string, unknown>;
1078
- const segs = bare(_path).split('/');
1090
+ const segs = bare(request.url).split('/');
1079
1091
  // an action on a subject (`/emails/{id}/cancel`) touches the subject; a create touches the new id under the collection;
1080
1092
  // a write whose subject is neither minted nor in its path (an RPC call naming it in its body) is left to the judge
1081
1093
  const minted = typeof body.id === 'string' ? body.id : '';
@@ -1083,8 +1095,7 @@ await walk(journey, world.root(), failures, {
1083
1095
  if (minted || idInPath) {
1084
1096
  const base = minted && !segs.includes(minted) ? `${segs.join('/')}/${minted}` : /^[a-z_-]+$/.test(segs.at(-1) ?? '') && segs.length > 2 ? segs.slice(0, -1).join('/') : segs.join('/');
1085
1097
  // only an effect the vendor gives a door to read: a GET of the subject or of its collection (a moderation has neither)
1086
- const hdrs = { ...(journey.headers ?? {}), ...(st.headers ?? {}) };
1087
- const gets = (at: string): boolean => shape?.operationOf('GET', at, hdrs)?.op.method.toUpperCase() === 'GET';
1098
+ const gets = (at: string): boolean => shape?.operationOf('GET', new URL(at, request.url).href, request.headers)?.op.method.toUpperCase() === 'GET';
1088
1099
  const door = !shape || gets(base) || gets(base.split('/').slice(0, -1).join('/'));
1089
1100
  if (door) thinWrites.push({ step: st.name, base, id: base.split('/').at(-1) ?? '', from: reads.length, deletes: method === 'DELETE' || opClass === 'delete' });
1090
1101
  }
@@ -1104,38 +1115,40 @@ await walk(journey, world.root(), failures, {
1104
1115
  if (!s.example) return;
1105
1116
  const ex = byId.get(s.example);
1106
1117
  if (!ex) { unknown.push(s.example); return; }
1107
- specSchemas = schemasByOperation[ex.operation] ?? {};
1118
+ const key = operationKey(ex.operation, ex.lane);
1119
+ specSchemas = schemasByOperation[key] ?? {};
1108
1120
  replayed.add(s.example);
1109
- if (ex.operation === 'gap' && ex.request) {
1110
- const sent = new URL(_path, `https://${journey.host}`);
1111
- if (s.method !== ex.request.method || sent.href !== ex.request.url) failures.push({ step: s.name, beat: 'EXAMPLE', detail: 'the unknown request differs from its recorded method or URL' });
1121
+ if (ex.operation === 'gap' && ex.request && request) {
1122
+ if (request.method !== ex.request.method || request.url !== ex.request.url) failures.push({ step: s.name, beat: 'EXAMPLE', detail: 'the unknown request differs from its recorded method or URL' });
1112
1123
  }
1113
- if (ex.authorizationAbsent && Object.keys({ ...(journey.headers ?? {}), ...(s.headers ?? {}) }).some((key) => key.toLowerCase() === 'authorization')) failures.push({ step: s.name, beat: 'EXAMPLE', detail: 'the recording has no Authorization header, but the replay sends one' });
1124
+ if (ex.authorizationAbsent && request && new Headers(request.headers).has('authorization')) failures.push({ step: s.name, beat: 'EXAMPLE', detail: 'the recording has no Authorization header, but the replay sends one' });
1114
1125
  // the step calls the example's operation (the spec's, as the walk's SHAPE check reads the request)
1115
- if (!isResp(step) && !isLine(step) && shape) {
1116
- const called = shape.operationOf(s.method, _path, { ...(journey.headers ?? {}), ...(s.headers ?? {}) })?.op.id;
1117
- if (called && called !== ex.operation) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `calls ${called}, not the example's operation ${ex.operation}` });
1126
+ if (request && shape) {
1127
+ const called = hit ? operationKey(hit.op.id, hit.lane) : undefined;
1128
+ if (called && called !== key) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `calls ${called}, not the example's operation ${key}` });
1118
1129
  }
1119
1130
  // the example is sent as published: its input as the body, to its operation
1120
1131
  // a published field the step leaves out (`unsent`: a rendered value naming an object the World does not hold) must
1121
1132
  // be one the operation's request schema does not require
1122
1133
  for (const [path, why] of Object.entries(s.unsent ?? {})) {
1123
1134
  if (!why) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `unsent ${path} gives no reason` });
1124
- if (requiredAt(operationSchemas[ex.operation]?.request, path)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `unsent ${path} is required by the operation's request schema` });
1135
+ if (requiredAt(operationSchemas[key]?.request, path)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `unsent ${path} is required by the operation's request schema` });
1125
1136
  }
1126
1137
  const publishedBody = exampleBody({ ...s, body: ex.input, form: undefined, bodyBase64: undefined }, ex.input);
1127
1138
  const inputType = Object.entries(ex.request?.headers ?? {}).find(([k]) => k.toLowerCase() === 'content-type')?.[1]
1128
- ?? Object.entries({ ...journey.headers, ...s.headers }).find(([k]) => k.toLowerCase() === 'content-type')?.[1];
1139
+ ?? (request ? new Headers(request.headers).get('content-type') ?? undefined : undefined);
1129
1140
  const publishedInput = s.unsent && publishedBody !== undefined ? withoutPaths(publishedBody, Object.keys(s.unsent)) : publishedBody;
1130
1141
  // the body the step sends, every check below reading it alike: no body (a GET) is the empty input a page publishes for it
1131
1142
  const body = isResp(s) ? s.resp : exampleBody(s, publishedInput) ?? {};
1132
1143
  // A placeholder used as a request-map key is bound by its scalar request position, just as an answer key is.
1133
1144
  const input = answerKeys(publishedInput, publishedInput, body, ex.placeholder, true);
1134
- if (ex.restXml) for (const p of restXmlInputProblems(ex.restXml, (input ?? {}) as Record<string, unknown>, shape?.pathOf?.(_path) ?? _path, { ...(journey.headers ?? {}), ...(s.headers ?? {}) }, placeholderIds, (s as { path?: string }).path)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `the request is not the example's published input: ${p}` });
1145
+ if (ex.restXml) {
1146
+ if (request) for (const p of restXmlInputProblems(ex.restXml, (input ?? {}) as Record<string, unknown>, shape?.pathOf(request.method, request.url, request.headers) ?? request.url, request.headers, placeholderIds, (s as { path?: string }).path)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `the request is not the example's published input: ${p}` });
1147
+ }
1135
1148
  else if (input !== undefined && (!requestMapKeysHold(publishedInput, input, body, ex.placeholder) || (ex.partial ? !holds(body, input, ex.placeholder, undefined, ex.names, false, inputType) : !same(body, input, ex.placeholder ?? /(?!)/, undefined, ex.names, false, inputType)))) failures.push({ step: s.name, beat: 'EXAMPLE', detail: typeof input === 'string' ? comparedText(body, input, ex.placeholder ?? /(?!)/, undefined, ex.names, inputType)?.problem ?? "the body is not the example's published input" : "the body is not the example's published input" });
1136
1149
  // a scenario's turn is sent as the RFC writes it: its client lines (none for the greeting, a connect)
1137
1150
  if (ex.lines && JSON.stringify([s.send ?? []].flat()) !== JSON.stringify(ex.lines)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `sends ${JSON.stringify(s.send ?? null)}, not the scenario's ${JSON.stringify(ex.lines)}` });
1138
- const target = Object.entries({ ...journey.headers, ...s.headers }).find(([k]) => k.toLowerCase() === 'x-amz-target')?.[1];
1151
+ const target = request ? new Headers(request.headers).get('x-amz-target') : undefined;
1139
1152
  if (ex.target && !target?.endsWith(`.${ex.target}`)) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `x-amz-target is ${target}, not the example's operation ${ex.target}` });
1140
1153
  if (ex.status && answer.status !== ex.status && !s.exampleDiffers?.status) failures.push({ step: s.name, beat: 'EXAMPLE', detail: `answered ${answer.status}, the example ${ex.status}` });
1141
1154
  if (stepFailures.length) return;
@@ -1166,9 +1179,8 @@ await walk(journey, world.root(), failures, {
1166
1179
  const published = isResp(s) && publishedValue !== undefined ? (Array.isArray(s.resp?.[0]) ? publishedValue : [publishedValue]) : publishedValue;
1167
1180
  const responseType = answer.headers?.['content-type'];
1168
1181
  const literal = isResp(s) || (published !== null && typeof published !== 'object');
1169
- const valueSchema = ex.schema ?? operationSchemas[ex.operation]?.responses[String(answer.status)];
1170
- comparisonCollections = [...(storedCollectionFields[vendor]?.[ex.operation] ?? [])];
1171
- const operation = shape?.operationOf((s.method ?? 'GET').toUpperCase(), _path, { ...(journey.headers ?? {}), ...(s.headers ?? {}) })?.op;
1182
+ const valueSchema = ex.schema ?? operationSchemas[key]?.responses[String(answer.status)];
1183
+ comparisonCollections = [...(storedCollectionFields[vendor]?.[key] ?? [])];
1172
1184
  // A derived resource list names its holder; never treat every array under a read as a list.
1173
1185
  if (operation?.class === 'list' && operation.answers?.list) {
1174
1186
  const holder = operation.answers.key ? (published as Record<string, unknown>)?.[operation.answers.key] : published;
@@ -1209,7 +1221,7 @@ await walk(journey, world.root(), failures, {
1209
1221
  // an example of an operation the twin does not serve is breadth's gap (score-pack), counted here and not replayed
1210
1222
  type Owned = { owners?: () => Record<string, string> } | undefined;
1211
1223
  const owners = ((await vendorFetch(PACKS, vendor))?.(world.root()) as Owned)?.owners?.() ?? ((await vendorStream(PACKS, vendor))?.(world.root()) as Owned)?.owners?.() ?? {};
1212
- const ownerOf = (e: Example): string | undefined => e.wire === 'client' ? 'gap' : owners[e.lane ? `${e.lane}:${e.operation}` : e.operation];
1224
+ const ownerOf = (e: Example): string | undefined => e.wire === 'client' ? 'gap' : owners[operationKey(e.operation, e.lane)];
1213
1225
  const patchRemoved = examples.filter((e) => e.patchRemoved);
1214
1226
  const unserved = examples.filter((e) => !e.patchRemoved && ownerOf(e) === 'gap');
1215
1227
  const recorded = Object.keys(journey.notReplayed ?? {});
package/src/verdict.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  // A JUDGE'S INDEPENDENCE (the grade's outline-judged and life-judged, the published examples' verdict): a verdict rules
2
2
  // only when it names its judge (`judge.agent`) and the authors of what it judges (`authors`), and the judge is none of
3
3
  // them. The dispatcher stamps author names consistently; a judge started independently
4
- // (scripts/judge-brief.ts prints its brief) names its reviewer role (`judge.name`). One judging per pack: the
4
+ // (`twin-standard judge-brief` prints its brief) names its reviewer role (`judge.name`). One judging per pack: the
5
5
  // verdict stands, and later changes to what it judged keep it (architecture, "Writing a life", Judge).
6
6
 
7
7
  /** Committed attribution names people or author roles, never execution identities. */