@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
@@ -25,6 +25,7 @@ import ts from 'typescript';
25
25
  import { fileURLToPath } from 'node:url';
26
26
  import { sharingLanesIn } from "./lanes.js";
27
27
  import { isPackArtifact } from "./pack-files.js";
28
+ import { credentialIssuerImports } from "./credential-issuer-import.js";
28
29
  // A path relative to another with '/' between its parts on every platform: git lists files that way, and every rule
29
30
  // here compares and splits on '/'; Windows' relative() joins with '\', which hid every lane's files and spec.
30
31
  const rel = (from, to) => relative(from, to).split(sep).join('/');
@@ -190,7 +191,7 @@ function noReport(unit) {
190
191
  /** An operation's family: the first segment of its path after a version (`/v1/organizations/{id}` is `organizations`), a
191
192
  * dotted RPC method's first part (`chat.postMessage` is `chat`), a line command's name (architecture, "Pack layout"). */
192
193
  function familyOf(path) {
193
- // the interim rule ("What an author writes"), until derive-pack writes the spec's own grouping on each operation
194
+ // the interim rule ("What an author writes"), until the derivation writes the spec's own grouping on each operation
194
195
  const segs = path.split('/').filter((x) => x && !/^v?\d+(\.\d+)?$|^\d{4}-\d{2}-\d{2}$/.test(x) && !x.startsWith('{'));
195
196
  if (!segs.length)
196
197
  return 'operations'; // named in a header: every path is `/`
@@ -289,7 +290,7 @@ const criteria = [
289
290
  },
290
291
  {
291
292
  id: 'derived-reproducible', section: 'form',
292
- asks: 'derive-pack regenerates src/generated/ byte for byte',
293
+ asks: 'twin-standard derive regenerates src/generated/ byte for byte',
293
294
  check(unit) {
294
295
  const gen = join(unit.dir, 'src', 'generated');
295
296
  if (!existsSync(gen))
@@ -309,10 +310,10 @@ const criteria = [
309
310
  // a standing written from one checkout reads the same from any other
310
311
  if (run.status === null || run.signal !== null || run.error)
311
312
  return { pass: false, cacheable: false,
312
- reason: `derive-pack interrupted: status=${run.status} signal=${run.signal} error=${run.error?.message ?? ''}` };
313
+ reason: `derive interrupted: status=${run.status} signal=${run.signal} error=${run.error?.message ?? ''}` };
313
314
  if (run.status !== 0)
314
- return { ...fail(`derive-pack refused: ${String(run.stderr || run.stdout).split(unit.dir).join('.').split(tmp).join('<out>').trim().split('\n').slice(-2).join(' ').slice(0, 200)}`), cacheable: run.status === 2 };
315
- return changed.length ? fail(`derive-pack writes differently: ${changed.slice(0, 5).join(', ')}${changed.length > 5 ? ` and ${changed.length - 5} more` : ''}`) : pass(`${filesUnder(gen).length} files reproduced`);
315
+ return { ...fail(`derive refused: ${String(run.stderr || run.stdout).split(unit.dir).join('.').split(tmp).join('<out>').trim().split('\n').slice(-2).join(' ').slice(0, 200)}`), cacheable: run.status === 2 };
316
+ return changed.length ? fail(`derive writes differently: ${changed.slice(0, 5).join(', ')}${changed.length > 5 ? ` and ${changed.length - 5} more` : ''}`) : pass(`${filesUnder(gen).length} files reproduced`);
316
317
  },
317
318
  },
318
319
  {
@@ -387,7 +388,7 @@ const criteria = [
387
388
  },
388
389
  {
389
390
  id: 'handler-contract', section: 'form',
390
- asks: 'every file in semantics/ reaches no randomness, clock, environment, network or module but the kernel, the states and the shared helpers; each handler file is its family\'s and exports only its operations, named by the fixed rule',
391
+ asks: 'every file in semantics/ reaches no randomness, clock, environment, network or module but the kernel, the states, the shared helpers and a public subpath of its declared credential issuer pinned at an exact version as a direct runtime dependency; each handler file is its family\'s and exports only its operations, named by the fixed rule',
391
392
  check(unit) {
392
393
  const dir = join(unit.dir, 'src', 'semantics');
393
394
  if (!existsSync(dir))
@@ -395,6 +396,7 @@ const criteria = [
395
396
  const ops = operationsOf(unit);
396
397
  const families = new Set(ops.values());
397
398
  const found = [];
399
+ const issuerImport = credentialIssuerImports(unit.dir, packageOf(unit));
398
400
  for (const f of filesUnder(dir).filter((x) => /\.tsx?$/.test(x) && !TEST_FILE.test(x))) {
399
401
  const text = stripComments(readFileSync(join(dir, f), 'utf8'));
400
402
  const raw = readFileSync(join(dir, f), 'utf8');
@@ -413,7 +415,7 @@ const criteria = [
413
415
  const engineModule = /^\.\.\/engine\/[A-Za-z0-9_-]+\.ts$/.test(from) || vendorPath(unit, from, 3, /^engine\/[A-Za-z0-9_-]+\.ts$/);
414
416
  // a lane over its vendor's state takes the vendor's states and shared helpers ("Pack layout")
415
417
  const vendorShared = vendorPath(unit, from, 3, /^(semantics\/(states|shared)|manifest)\.ts$/);
416
- if (from !== '@volter/world-core' && !/^\.\/(states|shared)\.ts$/.test(from) && !mapImport && !engineModule && !vendorShared)
418
+ if (from !== '@volter/world-core' && !/^\.\/(states|shared)\.ts$/.test(from) && !mapImport && !engineModule && !vendorShared && !issuerImport(from))
417
419
  found.push(`${f} imports ${from}`);
418
420
  }
419
421
  // the handler map is typed over the narrow context, so the compiler holds every handler it names to the contract
@@ -546,12 +548,13 @@ const criteria = [
546
548
  },
547
549
  {
548
550
  id: 'kernel-contract', section: 'form',
549
- asks: "every file of src/ imports the kernel through its root, the pack contract, and nothing past it: no `@volter/world-core/runtime` or other entry (cli.ts alone takes args and lifecycle), no node builtin, no other package but @volter/world-ui and the generated GraphQL library loader",
551
+ asks: "every file of src/ imports the kernel through its root, the pack contract, and nothing past it: no `@volter/world-core/runtime` or other entry (cli.ts alone takes args and lifecycle), no node builtin, no other package but @volter/world-ui, a public subpath of its declared credential issuer pinned at an exact version as a direct runtime dependency, and the generated GraphQL library loader",
550
552
  check(unit) {
551
553
  const src = join(unit.dir, 'src');
552
554
  if (!existsSync(src))
553
555
  return fail('src/ is missing');
554
556
  const found = [];
557
+ const issuerImport = credentialIssuerImports(unit.dir, packageOf(unit));
555
558
  for (const f of filesUnder(src).filter((x) => /\.tsx?$/.test(x) && !TEST_FILE.test(x) && !x.startsWith('generated/'))) {
556
559
  // the file's imports, re-exports and dynamic imports as TypeScript reads them (a string that only reads like one,
557
560
  // an error message's "from '<name>'", is none)
@@ -596,6 +599,8 @@ const criteria = [
596
599
  continue;
597
600
  if (from === '@volter/world-core' || from === '@volter/world-ui')
598
601
  continue;
602
+ if (issuerImport(from))
603
+ continue;
599
604
  if (f === 'cli.ts' && (from === '@volter/world-core/args' || from === '@volter/world-core/lifecycle'))
600
605
  continue;
601
606
  found.push(`${f} imports ${from}`);
@@ -4,6 +4,10 @@ export type SpecDocuments = {
4
4
  };
5
5
  /** The documents of `spec/openapi/`, by name, or undefined when the pack vendors one spec file. */
6
6
  export declare function readSpecDocuments(specDir: string): SpecDocuments | undefined;
7
+ /** Response declarations as served, independent of their representation media or generated surface version. */
8
+ export declare function declaredResponseStatuses(specDir: string): Map<string, Set<string>>;
9
+ /** Declared schema fields, including nested objects that are not inferred as generated resources. */
10
+ export declare function schemaFieldNames(specDir: string): Set<string>;
7
11
  /** The union of the documents as one OpenAPI document; each contradiction left unpatched is named, by pointer. */
8
12
  export declare function mergeDocuments(docs: SpecDocuments): {
9
13
  doc: Json;
@@ -9,6 +9,13 @@ import { parse as parseYaml } from 'yaml';
9
9
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
10
10
  import { join } from 'node:path';
11
11
  import { gunzipSync } from 'node:zlib';
12
+ import { applySpecPatch } from "./spec-patches.js";
13
+ import { slug } from "./spec-ir.js";
14
+ function readDocument(file) {
15
+ const bytes = readFileSync(file);
16
+ const text = (file.endsWith('.gz') ? gunzipSync(bytes) : bytes).toString('utf8');
17
+ return /\.ya?ml(\.gz)?$/.test(file) ? parseExactJson(JSON.stringify(parseYaml(text, { intAsBigInt: true }), (_, value) => typeof value === 'bigint' ? exactInteger(String(value)) : value)) : parseExactJson(text);
18
+ }
12
19
  /** The documents of `spec/openapi/`, by name, or undefined when the pack vendors one spec file. */
13
20
  export function readSpecDocuments(specDir) {
14
21
  const dir = join(specDir, 'openapi');
@@ -16,14 +23,114 @@ export function readSpecDocuments(specDir) {
16
23
  return undefined;
17
24
  const documents = {};
18
25
  for (const f of readdirSync(dir).filter((f) => /\.(json|ya?ml)(\.gz)?$/.test(f)).sort()) {
19
- const bytes = readFileSync(join(dir, f));
20
- const text = (f.endsWith('.gz') ? gunzipSync(bytes) : bytes).toString('utf8');
21
- documents[f.replace(/\.(json|ya?ml)(\.gz)?$/, '')] = /\.ya?ml(\.gz)?$/.test(f) ? parseExactJson(JSON.stringify(parseYaml(text, { intAsBigInt: true }), (_, v) => typeof v === 'bigint' ? exactInteger(String(v)) : v)) : parseExactJson(text);
26
+ documents[f.replace(/\.(json|ya?ml)(\.gz)?$/, '')] = readDocument(join(dir, f));
22
27
  }
23
28
  if (Object.keys(documents).length < 2)
24
29
  throw new Error(`${dir}: one API in several documents needs at least two; a single document is spec/openapi.{json,yaml}`);
25
30
  return { documents };
26
31
  }
32
+ const openapiFiles = ['openapi.json.gz', 'openapi.json', 'openapi.yaml.gz', 'openapi.yaml'];
33
+ function readPatchedDocument(specDir, filenames) {
34
+ const file = filenames.map((name) => join(specDir, name)).find(existsSync);
35
+ const documents = file === undefined ? readSpecDocuments(specDir) : undefined;
36
+ let doc = file === undefined ? documents : readDocument(file);
37
+ if (doc === undefined)
38
+ return undefined;
39
+ const patches = join(specDir, 'patches.json');
40
+ if (existsSync(patches))
41
+ for (const patch of readDocument(patches).patches)
42
+ applySpecPatch(doc, patch);
43
+ if (documents !== undefined) {
44
+ const merged = mergeDocuments(doc);
45
+ if (merged.contradictions.length)
46
+ throw new Error(`OpenAPI response declarations conflict: ${merged.contradictions.join('; ')}`);
47
+ doc = merged.doc;
48
+ }
49
+ return doc;
50
+ }
51
+ /** Response declarations as served, independent of their representation media or generated surface version. */
52
+ export function declaredResponseStatuses(specDir) {
53
+ const doc = readPatchedDocument(specDir, openapiFiles);
54
+ const statuses = new Map();
55
+ if (doc === undefined)
56
+ return statuses;
57
+ for (const [path, item] of Object.entries(doc.paths ?? {})) {
58
+ for (const [method, operation] of Object.entries(item)) {
59
+ if (!operation || typeof operation !== 'object' || !operation.responses)
60
+ continue;
61
+ statuses.set(operation.operationId || slug(method, path), new Set(Object.keys(operation.responses)));
62
+ }
63
+ }
64
+ return statuses;
65
+ }
66
+ /** Declared schema fields, including nested objects that are not inferred as generated resources. */
67
+ export function schemaFieldNames(specDir) {
68
+ const doc = readPatchedDocument(specDir, ['smithy.json.gz', 'smithy.json', ...openapiFiles, 'discovery.json.gz', 'discovery.json']);
69
+ const fields = new Set();
70
+ if (doc === undefined)
71
+ return fields;
72
+ for (const shape of Object.values(doc.shapes ?? {})) {
73
+ if (shape.type !== 'structure' && shape.type !== 'union')
74
+ continue;
75
+ for (const [name, member] of Object.entries(shape.members ?? {})) {
76
+ fields.add(name);
77
+ for (const trait of ['smithy.api#jsonName', 'smithy.api#xmlName']) {
78
+ const wireName = member.traits?.[trait];
79
+ if (typeof wireName === 'string')
80
+ fields.add(wireName);
81
+ }
82
+ }
83
+ }
84
+ const schema = (node) => {
85
+ if (Array.isArray(node)) {
86
+ for (const item of node)
87
+ schema(item);
88
+ return;
89
+ }
90
+ if (!node || typeof node !== 'object')
91
+ return;
92
+ for (const [name, property] of Object.entries(node.properties ?? {})) {
93
+ fields.add(name);
94
+ if (typeof property?.xml?.name === 'string')
95
+ fields.add(property.xml.name);
96
+ schema(property);
97
+ }
98
+ for (const keyword of ['$defs', 'definitions', 'patternProperties', 'dependentSchemas']) {
99
+ for (const child of Object.values(node[keyword] ?? {}))
100
+ schema(child);
101
+ }
102
+ for (const keyword of ['items', 'additionalItems', 'additionalProperties', 'unevaluatedProperties', 'unevaluatedItems', 'not', 'contains', 'propertyNames', 'if', 'then', 'else', 'allOf', 'anyOf', 'oneOf', 'prefixItems'])
103
+ schema(node[keyword]);
104
+ };
105
+ for (const catalog of [doc.components?.schemas, doc.definitions, doc.schemas]) {
106
+ for (const node of Object.values(catalog ?? {}))
107
+ schema(node);
108
+ }
109
+ const body = (node) => {
110
+ schema(node?.schema);
111
+ for (const media of Object.values(node?.content ?? {}))
112
+ schema(media?.schema);
113
+ };
114
+ for (const catalog of [doc.components?.responses, doc.components?.requestBodies, doc.components?.parameters, doc.components?.headers, doc.responses, doc.parameters]) {
115
+ for (const node of Object.values(catalog ?? {}))
116
+ body(node);
117
+ }
118
+ for (const paths of [doc.paths, doc.webhooks, doc.components?.pathItems]) {
119
+ for (const item of Object.values(paths ?? {})) {
120
+ for (const parameter of item.parameters ?? [])
121
+ body(parameter);
122
+ for (const method of ['get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace']) {
123
+ const operation = item[method];
124
+ for (const parameter of operation?.parameters ?? [])
125
+ body(parameter);
126
+ body(operation?.requestBody);
127
+ for (const response of Object.values(operation?.responses ?? {}))
128
+ body(response);
129
+ }
130
+ }
131
+ }
132
+ return fields;
133
+ }
27
134
  /** A node without its words (description, summary, examples, and the tags a document files it under): what two
28
135
  * documents must agree on. */
29
136
  const substance = (n) => JSON.stringify(n, (k, v) => (['description', 'summary', 'example', 'examples', 'x-mint', 'tags'].includes(k) ? undefined : v && typeof v === 'object' && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b))) : v));
@@ -6,7 +6,7 @@
6
6
  // `spec/`, Creating a pack, step 2): its method, its URL as the client
7
7
  // builds it (a label is the Python expression the client interpolates), the query keys it sends (literal, or the keys
8
8
  // of the dict it urlencodes) and its body's keys, each typed by the client's own annotation of the value (`null`
9
- // where the client does not say). A spec patch corrects the table as it corrects a JSON spec (derive-pack): what the
9
+ // where the client does not say). A spec patch corrects the table as it corrects a JSON spec (twin-standard derive): what the
10
10
  // client builds as a raw string (a token's scopes) comes from the vendor's reference page that way.
11
11
  //
12
12
  // The calls at one method and path are one operation, their query and body keys together. The client says nothing
@@ -3,7 +3,7 @@
3
3
  // its group, the version it arrived in, its arity, its arguments, its flags and its reply's schema); a pack whose wire
4
4
  // carries Redis commands (Upstash's REST API: a command and its arguments in the path or the body) vendors the files of
5
5
  // the commands its vendor serves and derives its command surface from them, as a line protocol's surface is its RFC's
6
- // command table (spec-ir-lines.ts). A spec patch corrects the data as it corrects a JSON spec (derive-pack).
6
+ // command table (spec-ir-lines.ts). A spec patch corrects the data as it corrects a JSON spec (twin-standard derive).
7
7
  function argumentOf(a) {
8
8
  return {
9
9
  name: String(a.name),
@@ -1,6 +1,6 @@
1
1
  // A line protocol's spec as data: the commands a client sends and the replies each may be answered,
2
2
  // read from the RFC's own table of them. The IR a byte-stream pack derives its surface from, as an HTTP
3
- // pack derives its from an OpenAPI document; a spec patch corrects it the same way (derive-pack).
3
+ // pack derives its from an OpenAPI document; a spec patch corrects it the same way (twin-standard derive).
4
4
  //
5
5
  // SMTP's table is RFC 5321 §4.3.2 "Command-Reply Sequences": each command, then its intermediate (I),
6
6
  // success (S) and error (E) codes, where `I: 354 -> data -> S: 250` says what answers the lines that
@@ -40,7 +40,7 @@ export type ProtoFile = {
40
40
  };
41
41
  /** One `.proto` file's declarations. */
42
42
  export declare function parseProto(path: string, text: string): ProtoFile;
43
- /** The vendored tree (path → text) as data, so a spec patch corrects it as it corrects a JSON spec (derive-pack). */
43
+ /** The vendored tree (path → text) as data, so a spec patch corrects it as it corrects a JSON spec (twin-standard derive). */
44
44
  export type ProtoSpec = {
45
45
  format: 'proto';
46
46
  files: ProtoFile[];
@@ -1,6 +1,6 @@
1
1
  // A pack's corrections to its vendor's spec (spec/patches.json): RFC 6902 operations (add, replace, remove, move, copy),
2
- // applied to the parsed spec so the vendored file stays the vendor's own bytes. derive-pack.ts applies them before the
3
- // IR; vendor-examples.ts reads the patched schemas, which are what the twin is derived from.
2
+ // applied to the parsed spec so the vendored file stays the vendor's own bytes. the derivation applies them before the
3
+ // IR; `twin-standard examples` reads the patched schemas, which are what the twin is derived from.
4
4
  /** Apply one patch to `doc` in place. */
5
5
  export function applySpecPatch(doc, p) {
6
6
  const at = (pointer) => {
@@ -3,8 +3,7 @@
3
3
  // runtime). The verdict names its judge and the authors; the grade refuses one whose judge is an author
4
4
  // (packages/twin-standard/src/verdict.ts). One judging per pack: the verdict stands through later changes.
5
5
  //
6
- // twin-standard judge-brief <vendor> --authors <author name>[,<agent>...] (from the pack repository's root; twin-world's
7
- // bun scripts/judge-brief.ts runs the same)
6
+ // twin-standard judge-brief <vendor> --authors <author name>[,<agent>...] (from the pack repository's root)
8
7
  import { attributionName } from "../verdict.js";
9
8
  import { createHash } from 'node:crypto';
10
9
  import { existsSync, readFileSync } from 'node:fs';
@@ -16,7 +16,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
16
16
  import { join } from 'node:path';
17
17
  import { listActions, observeTransitions, stateOf } from '@volter/world-core/runtime';
18
18
  import { inMemoryWorld, walk, usePacks } from "../walk/behavior-journey.js";
19
- import { lanesOf, shapeChecker } from "../walk/shape.js";
19
+ import { lanesOf, lineShape, shapeChecker } from "../walk/shape.js";
20
20
  import { sharingLanesIn } from "../lanes.js";
21
21
  /** Registers, in the `bun test` run that imports it, the walks of `vendor` in the pack repository at `PACKS`. */
22
22
  export async function coverageWalks(PACKS, vendor) {
@@ -36,9 +36,12 @@ export async function coverageWalks(PACKS, vendor) {
36
36
  const { manifest } = (await import(__rewriteRelativeImportExtension(join(PACKS, vendor, 'src', 'manifest.ts'))));
37
37
  // the lanes over the vendor's state its serve factory serves are walked by this life, their machines with the vendor's
38
38
  // ("Pack layout"); two manifests naming one resource's field would collide in the keys below, so that refuses
39
- const laneManifests = await Promise.all(sharingLanes(vendor).map(async (l) => (await import(__rewriteRelativeImportExtension(join(PACKS, vendor, l, 'src', 'manifest.ts')))).manifest));
39
+ const units = [{ dir: join(PACKS, vendor), manifest, lane: undefined }, ...await Promise.all(sharingLanes(vendor).map(async (l) => {
40
+ const dir = join(PACKS, vendor, l);
41
+ return { dir, lane: l, manifest: (await import(__rewriteRelativeImportExtension(join(dir, 'src', 'manifest.ts')))).manifest };
42
+ }))];
40
43
  // a vendor of lanes' root (a VendorManifest) declares no resources of its own: its lanes do
41
- const allResources = [manifest, ...laneManifests].flatMap((mf) => Object.entries(mf.resources ?? {}));
44
+ const allResources = units.flatMap(({ manifest: mf }) => Object.entries(mf.resources ?? {}));
42
45
  const machines = new Map();
43
46
  for (const [resource, decl] of allResources)
44
47
  for (const [field, machine] of Object.entries(decl.state ?? {})) {
@@ -50,23 +53,28 @@ export async function coverageWalks(PACKS, vendor) {
50
53
  machines.set(name, machine);
51
54
  }
52
55
  const where = new Map();
53
- for (const mf of [manifest, ...laneManifests])
56
+ const httpTransitions = new Set();
57
+ for (const { dir, manifest: mf } of units) {
58
+ const http = !lineShape(dir);
54
59
  for (const [resource, decl] of Object.entries(mf.resources ?? {})) {
55
60
  for (const [field, m] of Object.entries(decl.state ?? {}))
56
61
  m.transitions.forEach((t, i) => {
62
+ if (http)
63
+ httpTransitions.add(t);
57
64
  const place = { at: `${resource}.${field}#${i}`, field, type: decl.storedAs ?? resource, service: mf.service ?? mf.vendor };
58
65
  const places = where.get(t) ?? [];
59
66
  if (!places.some((p) => p.at === place.at && p.type === place.type && p.service === place.service))
60
67
  where.set(t, [...places, place]);
61
68
  });
62
69
  }
70
+ }
63
71
  const moves = new Map();
64
72
  const subjectKey = (service, type, id) => JSON.stringify([service, type, id]);
65
73
  observeTransitions((_decl, t, outcome, from, entry) => {
66
74
  const places = where.get(t) ?? [];
67
75
  if (outcome === 'refused') {
68
- // A failed check on an already committed write is no refusal the application received.
69
- if (entry)
76
+ // HTTP refusals are credited from their received status and body; a line protocol owns its session's replies.
77
+ if (entry || httpTransitions.has(t))
70
78
  return;
71
79
  const groups = new Set(places.map((p) => JSON.stringify([p.service, p.type, p.field])));
72
80
  if (groups.size === 1)
@@ -100,11 +108,14 @@ export async function coverageWalks(PACKS, vendor) {
100
108
  const shape = await shapeChecker(join(PACKS, vendor));
101
109
  const answered = [];
102
110
  try {
103
- await walk(life, root, failures, { beats: w.beats, hooks: { after: (step, path, answer) => {
111
+ await walk(life, root, failures, { beats: w.beats, hooks: { after: (_step, _path, answer) => {
104
112
  // a line step's refusals are the machine's own (observeTransitions); a request's are read off its answer
105
- const op = 'method' in step ? shape?.operationOf(step.method, path, { ...(life.headers ?? {}), ...(step.headers ?? {}) })?.op.id : undefined;
106
- if (op)
107
- answered.push({ op, text: JSON.stringify(answer.body ?? ''), body: answer.body });
113
+ const request = answer.request;
114
+ if (!request)
115
+ return;
116
+ const hit = shape?.operationOf(request.method, request.url, request.headers);
117
+ if (hit)
118
+ answered.push({ op: hit.op.id, lane: hit.lane, status: answer.status, body: answer.wireBody });
108
119
  } } });
109
120
  // a declared refusal is reached when a step on its operation was answered with it: its code, or its message
110
121
  // up to the first placeholder
@@ -112,26 +123,27 @@ export async function coverageWalks(PACKS, vendor) {
112
123
  const holds = (value, code) => (value !== null && typeof value === 'object'
113
124
  ? Object.values(value).some((v) => holds(v, code))
114
125
  : (typeof value === 'string' || typeof value === 'number') && String(value) === code);
115
- const answers = (a, r) => r.code !== undefined ? holds(a.body, String(r.code)) : a.text.includes(r.message.split('{')[0].trim());
116
- for (const [resource, decl] of allResources) {
117
- for (const [field, m] of Object.entries(decl.state ?? {})) {
118
- m.transitions.forEach((t, i) => {
119
- if (!t.operation)
120
- return;
121
- const on = answered.filter((a) => a.op === t.operation);
122
- if (t.refusal && on.some((a) => answers(a, t.refusal)))
123
- seen.add(`${resource}.${field}#${i}|refused|*`);
124
- for (const [from, r] of Object.entries(t.refusals ?? {}))
125
- if (on.some((a) => answers(a, r)))
126
- seen.add(`${resource}.${field}#${i}|refused|${from}`);
127
- });
126
+ const answers = (a, r) => a.status === r.status && (r.code !== undefined ? holds(a.body, String(r.code)) : JSON.stringify(a.body)?.includes(r.message.split('{')[0].trim()) === true);
127
+ for (const { manifest: mf, lane } of units)
128
+ for (const [resource, decl] of Object.entries(mf.resources ?? {})) {
129
+ for (const [field, m] of Object.entries(decl.state ?? {})) {
130
+ m.transitions.forEach((t, i) => {
131
+ if (!t.operation)
132
+ return;
133
+ const on = answered.filter((a) => a.op === t.operation && a.lane === lane);
134
+ if (t.refusal && on.some((a) => answers(a, t.refusal)))
135
+ seen.add(`${resource}.${field}#${i}|refused|*`);
136
+ for (const [from, r] of Object.entries(t.refusals ?? {}))
137
+ if (on.some((a) => answers(a, r)))
138
+ seen.add(`${resource}.${field}#${i}|refused|${from}`);
139
+ });
140
+ }
128
141
  }
129
- }
130
142
  // every change the life made to a declared state field, from the World's log in order: a move is the declared
131
143
  // transition that allows it (from its state to the next, by its operation when both name one); a change no
132
144
  // transition allows is an undeclared move
133
145
  const fieldsOf = new Map();
134
- for (const mf of [manifest, ...laneManifests])
146
+ for (const { manifest: mf } of units)
135
147
  for (const [resource, decl] of Object.entries(mf.resources ?? {})) {
136
148
  const key = JSON.stringify([mf.service ?? mf.vendor, decl.storedAs ?? resource]);
137
149
  for (const [field, m] of Object.entries(decl.state ?? {}))
@@ -139,7 +151,7 @@ export async function coverageWalks(PACKS, vendor) {
139
151
  }
140
152
  const current = new Map();
141
153
  // the services the life wrote: the vendor's, or its lanes' (a vendor of lanes' root names none of its own)
142
- const services = [...new Set([manifest, ...laneManifests].map((mf) => mf.service).filter((x) => typeof x === 'string'))];
154
+ const services = [...new Set(units.map(({ manifest: mf }) => mf.service).filter((x) => typeof x === 'string'))];
143
155
  if (!services.length)
144
156
  services.push(manifest.vendor);
145
157
  for (const a of services.flatMap((service) => listActions(service, root))) {
@@ -32,8 +32,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
32
32
  // A walk that fails, and a file in scope (the pack's src/ on disk, its sharing lanes' too) that declares code yet
33
33
  // measures no line, stop the measurement: neither is a file with nothing to reach.
34
34
  //
35
- // Usage: twin-standard coverage <vendor> [--write | --out <file>] [--prune] (from the pack repository's root; twin-world's
36
- // bun scripts/life-coverage.ts runs the same) (--prune removes the stale records) (--write keeps journeys/coverage.json)
35
+ // Usage: twin-standard coverage <vendor> [--write | --out <file>] [--prune] (from the pack repository's root)
36
+ // (--prune removes the stale records) (--write keeps journeys/coverage.json)
37
37
  import { spawnSync } from 'node:child_process';
38
38
  import ts from 'typescript';
39
39
  import { existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
@@ -1,8 +1,8 @@
1
1
  // Where the contributor's tools (walk, examples, coverage, judge-brief) find the pack they are given: a vendor's directory
2
2
  // under the pack repository they run from. A contributor runs them from their own repository's root (`twin-standard walk
3
- // <vendor>`), so that root is the working directory; twin-world's scripts, which find Volter's pack repository beside
4
- // their checkout, name it before loading a tool (setPacksRoot), and the kernel checkout a walk must measure
5
- // (setKernelCheckout). Each tool reads both once, when it loads.
3
+ // <vendor>`), so that root is the working directory; a tool that runs another in a process of its own (run-tool.ts)
4
+ // names it before loading the tool (setPacksRoot), with the kernel checkout a walk must measure (setKernelCheckout).
5
+ // Each tool reads both once, when it loads.
6
6
  import { join } from 'node:path';
7
7
  let packs = process.cwd();
8
8
  let kernel;
@@ -3,7 +3,7 @@
3
3
  // life per pack is walked through the pack's own fetch on the in-memory World, and the report says:
4
4
  // - the life: whether a judge's plausible verdict names its current content (a verdict its author wrote is not one);
5
5
  // - correctness: every check on every step (status, expectations, captures, SHAPE, the beats) and the life
6
- // answering byte for byte the same on a second fresh World;
6
+ // answering the same on a second fresh World under its declared engine outputs;
7
7
  // - coverage: the hand-written code the life and the vendor's published examples run and the declared state logic
8
8
  // they reach (life-coverage.ts), with what they never reach;
9
9
  // - the vendor's published examples: replayed and judged (vendor-examples.ts);
@@ -12,8 +12,7 @@
12
12
  // This is source-journey evidence, not installed readiness or pack completion. pack-done owns completion and consumes
13
13
  // this report alongside the exact artifact's installed customer assessment in the one final run.
14
14
  //
15
- // Usage: twin-standard walk <vendor> [--write] [--report <path>] (from the pack repository's root; twin-world's
16
- // bun scripts/score-pack.ts <vendor> runs the same)
15
+ // Usage: twin-standard walk <vendor> [--write] [--report <path>] (from the pack repository's root)
17
16
  // --write keeps journeys/score.json and journeys/coverage.json; --report writes the report and what it lacks for done
18
17
  // to <path> as JSON and changes nothing in the checkout
19
18
  import { parseExactJson } from '@volter/world-core/exact-json';
@@ -26,6 +25,7 @@ import { dirname, join, sep } from 'node:path';
26
25
  import { inMemoryWorld, walk, withPinnedWorldDraws, usePacks } from "../walk/behavior-journey.js";
27
26
  import { expandFills, waitMs } from "../walk/journey-kit.js";
28
27
  import { vendorFetch, vendorStream } from "../walk/vendor-fetch.js";
28
+ import { compareReplays } from "../walk/replay.js";
29
29
  import { existenceProbe } from "../walk/journey-kit.js";
30
30
  import { kernelCheckout, packsRoot, toolPath } from "./packs-root.js";
31
31
  const PACKS = packsRoot();
@@ -85,7 +85,7 @@ await walk(life, world.root(), failures, { beats: true });
85
85
  const answers = async () => {
86
86
  const seen = [];
87
87
  // the World's own draws (its seed, its keys) are the same in both: two Worlds differ only by what is beyond them (R9)
88
- await withPinnedWorldDraws(() => walk(life, world.root(), [], { beats: false, hooks: { after: (step, p, a) => seen.push(`${step.name ?? ''}\u0000${p} ${a.status} ${JSON.stringify(a.body)}`) } }));
88
+ await withPinnedWorldDraws(() => walk(life, world.root(), failures, { beats: false, hooks: { after: (step, path, answer) => seen.push({ name: step.name ?? '', path, status: answer.status, body: answer.body, ...(answer.sql ? { sql: answer.sql } : {}), ...(answer.connection ? { connection: answer.connection } : {}) }) } }));
89
89
  return seen;
90
90
  };
91
91
  const [first, second] = [await answers(), await answers()];
@@ -184,9 +184,9 @@ const flags = [];
184
184
  }
185
185
  }
186
186
  }
187
- const differs = first.findIndex((line, i) => line !== second[i]);
188
- if (differs >= 0 || first.length !== second.length)
189
- failures.push({ step: '(whole life)', beat: 'REPLAYABLE', detail: `a second fresh World answered differently at "${(first[differs] ?? second[differs] ?? first.at(-1) ?? '').split('\u0000')[0]}" (the ${differs + 1}th answered step; waits are not counted)` });
187
+ const replayComparison = compareReplays(first, second);
188
+ for (const detail of replayComparison.diagnostics)
189
+ failures.push({ step: '(whole life)', beat: 'REPLAYABLE', detail });
190
190
  // ── breadth ──────────────────────────────────────────────────────────────────────────────────
191
191
  const factory = await vendorFetch(PACKS, vendor);
192
192
  const fetch = factory?.(world.root());