@volter/twin-standard 1.0.149 → 1.0.151

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 (83) hide show
  1. package/README.md +12 -3
  2. package/client-sdks/bun.lock +0 -32
  3. package/client-sdks/package.json +0 -2
  4. package/dist/client-sdks/bun.lock +0 -32
  5. package/dist/client-sdks/package.json +0 -2
  6. package/dist/src/assessment.d.ts +5 -4
  7. package/dist/src/assessment.js +2 -1
  8. package/dist/src/cli.js +21 -6
  9. package/dist/src/clients/harness.d.ts +3 -1
  10. package/dist/src/clients/harness.js +4 -2
  11. package/dist/src/clients/jira.d.ts +2 -0
  12. package/dist/src/clients/jira.js +71 -0
  13. package/dist/src/clients/polar.d.ts +2 -0
  14. package/dist/src/clients/polar.js +73 -0
  15. package/dist/src/clients/resend.js +38 -0
  16. package/dist/src/clients/run.js +1 -1
  17. package/dist/src/clients/sentry.js +1 -26
  18. package/dist/src/clients/smtp.d.ts +2 -0
  19. package/dist/src/clients/smtp.js +54 -0
  20. package/dist/src/clients/stripe.js +1 -1
  21. package/dist/src/clients/volteridentity.js +4 -3
  22. package/dist/src/clients/x.d.ts +2 -0
  23. package/dist/src/clients/x.js +64 -0
  24. package/dist/src/conformance.d.ts +4 -1
  25. package/dist/src/conformance.js +256 -6
  26. package/dist/src/consumer.js +5 -1
  27. package/dist/src/create-pack.d.ts +5 -0
  28. package/dist/src/create-pack.js +31 -3
  29. package/dist/src/protocol-3.js +30 -2
  30. package/dist/src/spec-ir.d.ts +2 -0
  31. package/dist/src/spec-ir.js +43 -0
  32. package/dist/src/tools/judge-brief.d.ts +1 -0
  33. package/dist/src/tools/judge-brief.js +67 -0
  34. package/dist/src/tools/life-coverage-walk.d.ts +2 -0
  35. package/dist/src/tools/life-coverage-walk.js +220 -0
  36. package/dist/src/tools/life-coverage.d.ts +2 -0
  37. package/dist/src/tools/life-coverage.js +271 -0
  38. package/dist/src/tools/packs-root.d.ts +9 -0
  39. package/dist/src/tools/packs-root.js +17 -0
  40. package/dist/src/tools/run-tool.d.ts +1 -0
  41. package/dist/src/tools/run-tool.js +18 -0
  42. package/dist/src/tools/score-pack.d.ts +2 -0
  43. package/dist/src/tools/score-pack.js +264 -0
  44. package/dist/src/tools/vendor-examples.d.ts +2 -0
  45. package/dist/src/tools/vendor-examples.js +1493 -0
  46. package/dist/src/verdict.js +1 -1
  47. package/dist/src/walk/behavior-journey.d.ts +6 -0
  48. package/dist/src/walk/behavior-journey.js +8 -6
  49. package/dist/src/walk/journey-kit.d.ts +2 -0
  50. package/dist/src/walk/journey-kit.js +6 -2
  51. package/dist/src/walk/rendered-scalar.d.ts +3 -0
  52. package/dist/src/walk/rendered-scalar.js +23 -0
  53. package/dist/src/walk/sse-example.js +45 -0
  54. package/package.json +2 -2
  55. package/src/assessment.ts +2 -1
  56. package/src/cli.ts +15 -2
  57. package/src/clients/harness.ts +4 -2
  58. package/src/clients/jira.ts +73 -0
  59. package/src/clients/polar.ts +73 -0
  60. package/src/clients/resend.ts +32 -0
  61. package/src/clients/run.ts +2 -2
  62. package/src/clients/sentry.ts +1 -25
  63. package/src/clients/smtp.ts +49 -0
  64. package/src/clients/stripe.ts +1 -1
  65. package/src/clients/volteridentity.ts +4 -3
  66. package/src/clients/x.ts +66 -0
  67. package/src/conformance.ts +192 -8
  68. package/src/consumer.ts +5 -1
  69. package/src/create-pack.ts +27 -3
  70. package/src/protocol-3.ts +24 -2
  71. package/src/spec-ir.ts +31 -0
  72. package/src/tools/judge-brief.ts +63 -0
  73. package/src/tools/life-coverage-walk.ts +173 -0
  74. package/src/tools/life-coverage.ts +237 -0
  75. package/src/tools/packs-root.ts +21 -0
  76. package/src/tools/run-tool.ts +10 -0
  77. package/src/tools/score-pack.ts +244 -0
  78. package/src/tools/vendor-examples.ts +1250 -0
  79. package/src/verdict.ts +1 -1
  80. package/src/walk/behavior-journey.ts +6 -2
  81. package/src/walk/journey-kit.ts +7 -2
  82. package/src/walk/rendered-scalar.ts +17 -0
  83. package/src/walk/sse-example.ts +37 -0
@@ -160,6 +160,9 @@ export function packFetchFile(dir, name) {
160
160
  const hasLive = existsSync(join(semantics, 'live.ts'));
161
161
  // a GraphQL API beside the REST one: its resolvers and the vendor's schema (graphql-wire.ts)
162
162
  const hasGraphql = existsSync(join(semantics, 'graphql.ts')) && existsSync(join(dir, 'src', 'generated', 'graphql-sdl.gen.json'));
163
+ // a stream whose documents are BSON (the MongoDB wire): the pack's own bson library, resolved from the pack as graphql's is
164
+ const packageJson = existsSync(join(dir, 'package.json')) ? parseExactJson(readFileSync(join(dir, 'package.json'), 'utf8')) : {};
165
+ const hasBson = existsSync(join(dir, 'src', 'stream.ts')) && packageJson.dependencies?.bson !== undefined;
163
166
  const screensDir = join(dir, 'src', 'screens');
164
167
  const screens = existsSync(screensDir) ? readdirSync(screensDir).filter((n) => /\.tsx?$/.test(n) && !/\.(test|uitest)\.tsx?$/.test(n) && !/^shared\.tsx?$/.test(n)).sort() : [];
165
168
  const id = (file) => file.replace(/\.tsx?$/, '');
@@ -205,6 +208,7 @@ export function packFetchFile(dir, name) {
205
208
  ...screens.map((f) => `import { screen as ${identifier(`screen_${id(f)}`)} } from './screens/${f}';`),
206
209
  ...laneImports,
207
210
  '',
211
+ ...(hasBson ? ["/** The pack's BSON library, resolved from the pack as graphql's loader is (architecture, the MongoDB wire). */", "export const loadBson = (): Promise<unknown> => import('bson');", ''] : []),
208
212
  `export function ${name}(options: PackFetchOptions = {}): DerivedFetch {`,
209
213
  ' return createPackFetch({',
210
214
  ' manifest,',
@@ -507,6 +511,27 @@ export function laneInitFiles(vendor, lanes) {
507
511
  }
508
512
  return files;
509
513
  }
514
+ /** A unit's fixed files as the manifest and the spec make them now (architecture, "Creating a pack"): a lane's fetch, and
515
+ * a pack's fetch, index, server and cli. A pack with a wire of its own (`src/stream.ts`, SMTP's and Redis's byte-stream
516
+ * door) writes its server and cli itself: they carry its listener beside the fetch, so they are not fixed. `--index`
517
+ * writes these and the grade's `fixed-reproducible` compares them byte for byte. */
518
+ export async function fixedFiles(dir) {
519
+ const files = { 'src/fetch.ts': packFetchFile(dir, fetchName(dir)) };
520
+ if (!existsSync(join(dir, 'package.json')))
521
+ return files;
522
+ const { manifest } = await import(__rewriteRelativeImportExtension(join(dir, 'src', 'manifest.ts')));
523
+ const vendor = manifest?.vendor;
524
+ if (!vendor || !/^[a-z][a-z0-9-]*$/.test(vendor))
525
+ throw new Error('Pack manifest must declare its vendor identity independently of the npm package name');
526
+ files['src/index.ts'] = packIndexFile(dir, vendor);
527
+ if (!existsSync(join(dir, 'src', 'stream.ts'))) {
528
+ // a unit whose spec is a proto (derive-pack generated its schema) is served over gRPC: its serve seam in that form
529
+ const lanesOnly = !hasOwnSurface(dir) && readdirSync(dir).some((n) => existsSync(join(dir, n, 'src', 'manifest.ts')));
530
+ files['src/server.ts'] = serverFile(vendor, !lanesOnly && existsSync(join(dir, 'src', 'generated', 'proto.gen.json')));
531
+ files['src/cli.ts'] = cliFile(vendor);
532
+ }
533
+ return files;
534
+ }
510
535
  export async function createPack(args) {
511
536
  const [unit, ...rest] = args;
512
537
  if (!unit || (!rest.includes('--index') && !rest.includes('--brief') && !rest.includes('--init'))) {
@@ -598,9 +623,12 @@ export async function createPack(args) {
598
623
  writeFileSync(path, handlerIndex(dir));
599
624
  console.log(`create-pack: wrote ${path}`);
600
625
  }
601
- // the fetch, again: a door file, the clock or a screen may have been added since
602
- writeFileSync(join(dir, 'src', 'fetch.ts'), packFetchFile(dir, fetchName(dir)));
603
- console.log(`create-pack: wrote ${join(dir, 'src', 'fetch.ts')}`);
626
+ // the fixed files, again: a door file, the clock, a screen, a lane or a proto may have been added since, and a pack
627
+ // made by an earlier create-pack takes the current form
628
+ for (const [rel, text] of Object.entries(await fixedFiles(dir))) {
629
+ writeFileSync(join(dir, rel), text);
630
+ console.log(`create-pack: wrote ${join(dir, rel)}`);
631
+ }
604
632
  // a pack's own index (a lane's is the vendor's to name): the descriptor with what the kernel derives from the surface
605
633
  if (existsSync(join(dir, 'package.json'))) {
606
634
  const { manifest } = await import(__rewriteRelativeImportExtension(join(dir, 'src', 'manifest.ts')));
@@ -315,6 +315,23 @@ const criteria = [
315
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`);
316
316
  },
317
317
  },
318
+ {
319
+ id: 'fixed-reproducible', section: 'form',
320
+ asks: 'create-pack regenerates the fixed files (fetch, and a pack\'s index, server and cli) byte for byte',
321
+ async check(unit) {
322
+ // loaded when graded: create-pack reads this module's handler names, so a static import would be a cycle
323
+ const { fixedFiles } = await import("./create-pack.js");
324
+ let want;
325
+ try {
326
+ want = await fixedFiles(unit.dir);
327
+ }
328
+ catch (error) {
329
+ return fail(`create-pack cannot write the fixed files: ${error instanceof Error ? error.message : String(error)}`.slice(0, 200));
330
+ }
331
+ const differ = Object.entries(want).filter(([rel, text]) => !existsSync(join(unit.dir, rel)) || readFileSync(join(unit.dir, rel), 'utf8') !== text).map(([rel]) => rel);
332
+ return differ.length ? fail(`create-pack --index writes differently: ${differ.join(', ')} (run it, never edit a fixed file)`) : pass(`${Object.keys(want).length} fixed files reproduced`);
333
+ },
334
+ },
318
335
  {
319
336
  id: 'layout', section: 'form',
320
337
  asks: "the unit's files exactly Pack layout's tree: fixed names at src/'s root, semantics/ its fixed files and one per spec family, screens/ one per declared screen, journeys/ its closed set",
@@ -540,8 +557,8 @@ const criteria = [
540
557
  // an error message's "from '<name>'", is none)
541
558
  let text = readFileSync(join(src, f), 'utf8');
542
559
  if (f === 'fetch.ts') {
543
- // The generated fetch supplies the kernel wire with the pack-local parser library.
544
- // Exempt only that exact factory part; other static or dynamic imports still count.
560
+ // The generated fetch supplies the kernel wire with the pack-local library: graphql's parser, a stream's BSON.
561
+ // Exempt only those exact generated parts; other static or dynamic imports still count.
545
562
  const source = ts.createSourceFile(f, text, ts.ScriptTarget.Latest, true);
546
563
  const loaders = [];
547
564
  const visit = (node) => {
@@ -556,6 +573,17 @@ const criteria = [
556
573
  factory.arguments[0] === graphql.parent)
557
574
  loaders.push({ start: node.getStart(source), end: node.end });
558
575
  }
576
+ // The generated BSON loader of a stream's pack (architecture, the MongoDB wire): exactly
577
+ // `export const loadBson = (): Promise<unknown> => import('bson');`, BSON supplied as graphql is.
578
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword &&
579
+ node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0]) && node.arguments[0].text === 'bson') {
580
+ const arrow = node.parent, declaration = arrow.parent, list = declaration?.parent, statement = list?.parent;
581
+ if (ts.isArrowFunction(arrow) && arrow.parameters.length === 0 && arrow.body === node &&
582
+ declaration && ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name) && declaration.name.text === 'loadBson' &&
583
+ statement && ts.isVariableStatement(statement) && statement.parent === source &&
584
+ statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword))
585
+ loaders.push({ start: node.getStart(source), end: node.end });
586
+ }
559
587
  ts.forEachChild(node, visit);
560
588
  };
561
589
  visit(source);
@@ -37,6 +37,8 @@ export type IrOperation = {
37
37
  bodyEncoding: 'json' | 'form' | 'multipart' | 'xml' | 'none';
38
38
  /** A form-encoded operation's number and boolean fields, by bracket path (formScalarsOf). */
39
39
  scalars?: Record<string, 'integer' | 'number' | 'boolean'>;
40
+ /** A form-encoded operation's string fields that take only listed values, by bracket path (formEnumsOf). */
41
+ enums?: Record<string, string[]>;
40
42
  successStatus: number;
41
43
  /** Binary media types declared by each response status, independent of the twin’s response headers. */
42
44
  responseMedia?: Record<string, string[]>;
@@ -295,6 +295,47 @@ function formScalarsOf(doc, op, pathItem, encoding) {
295
295
  visit(p, [name], 0);
296
296
  return Object.keys(out).length ? Object.fromEntries(Object.entries(out).sort(([a], [b]) => a.localeCompare(b))) : undefined;
297
297
  }
298
+ /** A form-encoded operation's string fields whose schema lists the values they take (`status`: active, inactive,
299
+ * canceled), by the bracket path formScalarsOf uses; a field another alternative leaves free (a string without an enum,
300
+ * or a number) takes any value and is not listed. */
301
+ function formEnumsOf(doc, op, pathItem, encoding) {
302
+ if (encoding !== 'form')
303
+ return undefined;
304
+ const out = {};
305
+ const visit = (schema, at, depth) => {
306
+ const s = deref(doc, schema);
307
+ if (!s || typeof s !== 'object' || depth > 12)
308
+ return;
309
+ const alts = [...(Array.isArray(s.anyOf) ? s.anyOf : []), ...(Array.isArray(s.oneOf) ? s.oneOf : [])].map((a) => deref(doc, a));
310
+ if (alts.length) {
311
+ const free = alts.some((a) => (a?.type === 'string' && !Array.isArray(a.enum)) || a?.type === 'integer' || a?.type === 'number' || a?.type === 'boolean');
312
+ const listed = alts.filter((a) => a?.type === 'string' && Array.isArray(a.enum)).flatMap((a) => a.enum.map(String));
313
+ if (!free && listed.length && at.length)
314
+ out[at.join('.')] = [...new Set(listed)];
315
+ for (const a of alts)
316
+ if (a?.type === 'object' || a?.type === 'array' || a?.properties)
317
+ visit(a, at, depth + 1);
318
+ return;
319
+ }
320
+ if (s.type === 'string' && Array.isArray(s.enum) && at.length) {
321
+ out[at.join('.')] = s.enum.map(String);
322
+ return;
323
+ }
324
+ if (s.type === 'array') {
325
+ visit(s.items, [...at, '[]'], depth + 1);
326
+ return;
327
+ }
328
+ for (const [name, p] of Object.entries(propsOf(doc, s)))
329
+ visit(p, [...at, name], depth + 1);
330
+ };
331
+ for (const p of [...(pathItem.parameters ?? []), ...(op.parameters ?? [])].map((q) => deref(doc, q)))
332
+ if (p?.in === 'query')
333
+ visit(p.schema, [String(p.name)], 0);
334
+ const body = deref(doc, deref(doc, op.requestBody)?.content?.['application/x-www-form-urlencoded']?.schema);
335
+ for (const [name, p] of Object.entries(propsOf(doc, body)))
336
+ visit(p, [name], 0);
337
+ return Object.keys(out).length ? Object.fromEntries(Object.entries(out).sort(([x], [y]) => x.localeCompare(y))) : undefined;
338
+ }
298
339
  /** The class of one operation, from its method, its path and what it answers. An operation this cannot place is `action`, never CRUD. */
299
340
  /** A path and its discriminator as one key: `/responses/{id}` and `/responses/{id}?beta=true` are different reads. */
300
341
  // a null value (the key present with any value) keys as the bare name
@@ -445,6 +486,7 @@ export function fromOpenAPI(spec) {
445
486
  const credentials = [...new Set(security.flatMap((req) => { const names = Object.keys(req ?? {}); return names.length ? names : ['anonymous']; }))].sort();
446
487
  const body = bodyOf(doc, op);
447
488
  const scalars = formScalarsOf(doc, op, item, body.bodyEncoding);
489
+ const enums = formEnumsOf(doc, op, item, body.bodyEncoding);
448
490
  const ownServer = String((op.servers ?? item.servers)?.[0]?.url ?? '');
449
491
  const ownBase = /^https?:\/\//.test(ownServer) ? new URL(ownServer).pathname.replace(/\/+$/, '') : undefined;
450
492
  raw.push({
@@ -456,6 +498,7 @@ export function fromOpenAPI(spec) {
456
498
  ...(ownBase !== undefined && ownBase !== docBase ? { basePath: ownBase } : {}),
457
499
  ...body,
458
500
  ...(scalars ? { scalars } : {}),
501
+ ...(enums ? { enums } : {}),
459
502
  successStatus: success.status,
460
503
  ...(Object.keys(responseMedia).length ? { responseMedia, ...(Object.keys(defaultResponseMedia).length ? { defaultResponseMedia } : {}) } : {}),
461
504
  ...(success.empty ? { emptySuccess: true } : {}),
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ // A JUDGE'S BRIEF (pack-done's (f); architecture, "What a judge rejects"): what a judge reads and writes for one vendor's
2
+ // outline, life and published examples, printed for a judge in another session (`codex exec`, `claude -p`, any agent
3
+ // runtime). The verdict names its judge and the authors; the grade refuses one whose judge is an author
4
+ // (packages/twin-standard/src/verdict.ts). One judging per pack: the verdict stands through later changes.
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)
8
+ import { attributionName } from "../verdict.js";
9
+ import { createHash } from 'node:crypto';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { packsRoot } from "./packs-root.js";
13
+ const PACKS = packsRoot();
14
+ /** The architecture a judge's list is read from: the twin-world checkout this standard is in, else its published copy. */
15
+ const ARCHITECTURE = join(import.meta.dir, '..', '..', '..', '..', 'docs', 'contributing', 'architecture.md');
16
+ const PUBLISHED_ARCHITECTURE = 'https://world-docs.volter.ai/docs/contributing/architecture.md';
17
+ const vendor = process.argv[2];
18
+ const authorsAt = process.argv.indexOf('--authors');
19
+ const authors = authorsAt > 0 ? (process.argv[authorsAt + 1] ?? '').split(',').map((a) => a.trim()).filter(Boolean).map((name) => attributionName(name)) : [];
20
+ if (!vendor || vendor.startsWith('-') || !authors.length) {
21
+ console.error('usage: twin-standard judge-brief <vendor> --authors <author name>[,<agent>...] (from the pack repository\'s root)');
22
+ process.exit(2);
23
+ }
24
+ const journeys = join(PACKS, vendor, 'journeys');
25
+ if (!existsSync(journeys)) {
26
+ console.error(`judge-brief: ${vendor} has no journeys/ in ${PACKS}`);
27
+ process.exit(2);
28
+ }
29
+ // the list of what a judge rejects, as architecture holds it
30
+ const architecture = existsSync(ARCHITECTURE) ? readFileSync(ARCHITECTURE, 'utf8') : await fetch(PUBLISHED_ARCHITECTURE).then((r) => (r.ok ? r.text() : ''), () => '');
31
+ const listAt = architecture.indexOf('#### What a judge rejects\n');
32
+ if (listAt < 0) {
33
+ console.error(`judge-brief: the architecture (${existsSync(ARCHITECTURE) ? ARCHITECTURE : PUBLISHED_ARCHITECTURE}) has no "#### What a judge rejects" section`);
34
+ process.exit(2);
35
+ }
36
+ const listFrom = architecture.indexOf('\n- ', listAt);
37
+ const listTo = architecture.indexOf('\n\n', listFrom + 1);
38
+ const rejects = architecture.slice(listFrom + 1, listTo < 0 ? undefined : listTo).trim();
39
+ const sha = (file) => createHash('sha256').update(readFileSync(file)).digest('hex');
40
+ const judged = [
41
+ { file: 'customer-life.outline.md', verdict: 'customer-life.outline.verdict.json', what: "the life's outline: the customer's story in acts" },
42
+ { file: 'customer-life.json', verdict: 'customer-life.verdict.json', what: 'the life: every step an application or person takes, its expectations' },
43
+ { file: 'vendor-examples.json', verdict: 'vendor-examples.verdict.json', what: "the published examples: the vendor's own documented requests and answers, replayed" },
44
+ ].filter((j) => existsSync(join(journeys, j.file)));
45
+ console.log(`You are the judge of the ${vendor} twin's journeys. You did not write them, and you judge them in a session other than
46
+ the run that wrote them. Judge them against the vendor and against the list below; do not edit any judged file, and run no
47
+ test. Read what you need: the pack's spec (${vendor}/spec/, SOURCE.md naming every page read), its demand
48
+ (${vendor}/journeys/demand.json: what the applications call), its decision table (journeys/decisions.json), and the
49
+ vendor's own documentation the spec cites.
50
+
51
+ What you judge (in ${vendor}/journeys/), each with the sha256 your verdict names:
52
+ ${judged.map((j) => `- ${j.file} (${j.what}): sha256 ${sha(join(journeys, j.file))}`).join('\n')}
53
+
54
+ What a judge rejects (docs/contributing/architecture.md, "What a judge rejects"):
55
+ ${rejects}
56
+
57
+ For each file write its verdict beside it, as JSON:
58
+ ${judged.map((j) => `- ${j.verdict}`).join('\n')}
59
+ {
60
+ "judge": { "agent": "<what you are: your runtime and model>", "name": "<your author name or reviewer role>" },
61
+ "authors": ${JSON.stringify(authors)},
62
+ "sha256": "<the judged file's sha256 above>",
63
+ "judgedAt": "<today, YYYY-MM-DD>",
64
+ "plausible": <true only when no ruling stands>,
65
+ "rulings": [ { "id": "<short name>", "steps": [<step indexes, or acts>], "barItem": "<the list's item>", "reason": "<what is wrong, citing the step and the vendor's page>", "correction": "<what the author changes>" } ]
66
+ }
67
+ A correction naming a wait gives it as a life writes one: one number and one unit (\`915m\`, \`3d\`). A verdict with any ruling is not plausible. The authors apply every ruling; this is the one judging, and the verdict stands through their changes.`);
@@ -0,0 +1,2 @@
1
+ /** Registers, in the `bun test` run that imports it, the walks of `vendor` in the pack repository at `PACKS`. */
2
+ export declare function coverageWalks(PACKS: string, vendor: string): Promise<void>;
@@ -0,0 +1,220 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { parseExactJson } from '@volter/world-core/exact-json';
10
+ // The walks `life-coverage.ts` measures: one pack's customer life, walked with its beats on
11
+ // the in-memory World, and the vendor's published examples replayed (journeys/vendor-examples.json, vendor-examples.ts)
12
+ // on a World of their own: a line only the vendor's own example reaches is reached for the reason the vendor gives. Bun measures coverage only inside `bun test`, so the measurement runs the walk through
13
+ // a test file life-coverage writes, which registers them (coverageWalks), and reads the lcov it leaves; the walk holding
14
+ // is its precondition, not what it measures. This module is no test file itself, so a published package holds it.
15
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { listActions, observeTransitions, stateOf } from '@volter/world-core/runtime';
18
+ import { inMemoryWorld, walk, usePacks } from "../walk/behavior-journey.js";
19
+ import { lanesOf, shapeChecker } from "../walk/shape.js";
20
+ import { sharingLanesIn } from "../lanes.js";
21
+ /** Registers, in the `bun test` run that imports it, the walks of `vendor` in the pack repository at `PACKS`. */
22
+ export async function coverageWalks(PACKS, vendor) {
23
+ const { expect, test } = await import('bun:test');
24
+ usePacks(PACKS);
25
+ const sharingLanes = (v) => (v.includes('/') ? [] : sharingLanesIn(join(PACKS, v), v));
26
+ const journeys = join(PACKS, vendor, 'journeys');
27
+ const walks = [{ file: 'customer-life.json', beats: true }, { file: 'vendor-examples.json', beats: false }].filter((w) => existsSync(join(journeys, w.file)));
28
+ const seen = new Set();
29
+ const undeclared = new Set();
30
+ for (const [n, w] of walks.entries())
31
+ test(`${vendor}: ${w.file} walks`, async () => {
32
+ const life = parseExactJson(readFileSync(join(journeys, w.file), 'utf8'));
33
+ const world = inMemoryWorld(`coverage-${vendor}-${n}`);
34
+ const failures = [];
35
+ // each declared transition the life moved through or was refused by, named by resource, field and its place
36
+ const { manifest } = (await import(__rewriteRelativeImportExtension(join(PACKS, vendor, 'src', 'manifest.ts'))));
37
+ // the lanes over the vendor's state its serve factory serves are walked by this life, their machines with the vendor's
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));
40
+ // 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 ?? {}));
42
+ const machines = new Map();
43
+ for (const [resource, decl] of allResources)
44
+ for (const [field, machine] of Object.entries(decl.state ?? {})) {
45
+ const name = `${resource}.${field}`;
46
+ const previous = machines.get(name);
47
+ // Lanes sharing one vendor import the same state declaration; distinct declarations still collide.
48
+ if (previous && previous !== machine)
49
+ throw new Error(`${vendor}: ${name} is declared by two manifests (the vendor's and a lane's)`);
50
+ machines.set(name, machine);
51
+ }
52
+ const where = new Map();
53
+ for (const mf of [manifest, ...laneManifests])
54
+ for (const [resource, decl] of Object.entries(mf.resources ?? {})) {
55
+ for (const [field, m] of Object.entries(decl.state ?? {}))
56
+ m.transitions.forEach((t, i) => {
57
+ const place = { at: `${resource}.${field}#${i}`, field, type: decl.storedAs ?? resource, service: mf.service ?? mf.vendor };
58
+ const places = where.get(t) ?? [];
59
+ if (!places.some((p) => p.at === place.at && p.type === place.type && p.service === place.service))
60
+ where.set(t, [...places, place]);
61
+ });
62
+ }
63
+ const moves = new Map();
64
+ const subjectKey = (service, type, id) => JSON.stringify([service, type, id]);
65
+ observeTransitions((_decl, t, outcome, from, entry) => {
66
+ const places = where.get(t) ?? [];
67
+ if (outcome === 'refused') {
68
+ // A failed check on an already committed write is no refusal the application received.
69
+ if (entry)
70
+ return;
71
+ const groups = new Set(places.map((p) => JSON.stringify([p.service, p.type, p.field])));
72
+ if (groups.size === 1)
73
+ for (const p of places)
74
+ seen.add(`${p.at}|refused|${from}`);
75
+ return;
76
+ }
77
+ // a move the machine allowed on a step with no write behind it (a line protocol's session, whose stage is the
78
+ // connection's): reached, as a refusal given is, where its declaration is unambiguous
79
+ if (!entry) {
80
+ const groups = new Set(places.map((p) => JSON.stringify([p.service, p.type, p.field])));
81
+ if (groups.size === 1)
82
+ for (const p of places)
83
+ seen.add(`${p.at}|moved|${from}`);
84
+ return;
85
+ }
86
+ if ((t.actor ?? 'api') !== entry.actor)
87
+ return;
88
+ const { action, field, actor } = entry;
89
+ const matching = places.filter((p) => p.service === action.service && p.type === action.subject.type && p.field === field);
90
+ if (!matching.length)
91
+ return;
92
+ for (const p of matching)
93
+ seen.add(`${p.at}|moved|${from}`);
94
+ const on = moves.get(action.id) ?? [];
95
+ on.push({ transition: t, from, field, actor, subject: subjectKey(action.service, action.subject.type, action.subject.id) });
96
+ moves.set(action.id, on);
97
+ });
98
+ const root = world.root();
99
+ // what each step was answered, by the spec operation it called: a refusal is seen at the wire, whichever code gave it
100
+ const shape = await shapeChecker(join(PACKS, vendor));
101
+ const answered = [];
102
+ try {
103
+ await walk(life, root, failures, { beats: w.beats, hooks: { after: (step, path, answer) => {
104
+ // 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 });
108
+ } } });
109
+ // a declared refusal is reached when a step on its operation was answered with it: its code, or its message
110
+ // up to the first placeholder
111
+ // a code is a value of the answer (X's `89` at errors[].code), never a run of digits inside another value (a token)
112
+ const holds = (value, code) => (value !== null && typeof value === 'object'
113
+ ? Object.values(value).some((v) => holds(v, code))
114
+ : (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
+ });
128
+ }
129
+ }
130
+ // every change the life made to a declared state field, from the World's log in order: a move is the declared
131
+ // transition that allows it (from its state to the next, by its operation when both name one); a change no
132
+ // transition allows is an undeclared move
133
+ const fieldsOf = new Map();
134
+ for (const mf of [manifest, ...laneManifests])
135
+ for (const [resource, decl] of Object.entries(mf.resources ?? {})) {
136
+ const key = JSON.stringify([mf.service ?? mf.vendor, decl.storedAs ?? resource]);
137
+ for (const [field, m] of Object.entries(decl.state ?? {}))
138
+ fieldsOf.set(key, [...(fieldsOf.get(key) ?? []), [resource, field, m]]);
139
+ }
140
+ const current = new Map();
141
+ // 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'))];
143
+ if (!services.length)
144
+ services.push(manifest.vendor);
145
+ for (const a of services.flatMap((service) => listActions(service, root))) {
146
+ if (a.op !== 'set' || !a.fields)
147
+ continue;
148
+ const key = subjectKey(a.service, a.subject.type, a.subject.id);
149
+ const prev = current.get(key);
150
+ const next = { ...(prev ?? {}), ...a.fields };
151
+ current.set(key, next);
152
+ if (!prev)
153
+ continue;
154
+ const opId = typeof a.input?.operationId === 'string' ? a.input.operationId : undefined;
155
+ // resources stored as one type (a response the beta and the GA API both serve) share its fields: a
156
+ // change is declared when any of their machines allows it
157
+ const changed = new Map();
158
+ const valueOf = (row, field) => field.split('.').reduce((value, part) => value && typeof value === 'object' ? value[part] : undefined, row);
159
+ for (const [resource, field, m] of fieldsOf.get(JSON.stringify([a.service, a.subject.type])) ?? []) {
160
+ if (!(field.split('.')[0] in a.fields))
161
+ continue;
162
+ const from = stateOf(m, valueOf(prev, field));
163
+ const to = stateOf(m, valueOf(next, field));
164
+ if (from === to)
165
+ continue;
166
+ const allows = (t, at) => (moves.get(a.id) ?? []).some((move) => move.transition === t && move.from === at && move.field === field && move.actor === (t.actor ?? 'api') &&
167
+ move.subject === subjectKey(a.service, a.subject.type, a.subject.id));
168
+ const i = m.transitions.findIndex((t) => allows(t, from) && (t.to ?? from) === to);
169
+ if (i >= 0)
170
+ seen.add(`${resource}.${field}#${i}|moved|${from}`);
171
+ // one write may carry several declared moves the machine walked (a batch the vendor works from
172
+ // validating through to completed as a read first looks); each step was observed as it was asked
173
+ const reached = new Set([from]);
174
+ for (let grew = true; grew && !reached.has(to);) {
175
+ grew = false;
176
+ for (const at of [...reached])
177
+ for (const t of m.transitions)
178
+ if (allows(t, at) && t.to && !reached.has(t.to)) {
179
+ reached.add(t.to);
180
+ grew = true;
181
+ }
182
+ }
183
+ const was = changed.get(field);
184
+ changed.set(field, { declared: (was?.declared ?? false) || reached.has(to), name: was?.name ?? `${resource}.${field}: ${from} → ${to} by ${opId ?? a.operation ?? '(unnamed)'}` });
185
+ }
186
+ for (const c of changed.values())
187
+ if (!c.declared)
188
+ undeclared.add(c.name);
189
+ }
190
+ }
191
+ finally {
192
+ world.restore();
193
+ observeTransitions(undefined);
194
+ }
195
+ // each walk adds what it reached; the last one written holds them all
196
+ if (process.env.LIFE_TRANSITIONS)
197
+ writeFileSync(process.env.LIFE_TRANSITIONS, JSON.stringify({ seen: [...seen].sort(), undeclared: [...undeclared].sort() }));
198
+ expect(failures.filter((f) => f.beat !== 'SHAPE')).toEqual([]);
199
+ // a walk's time grows with its steps and the state they build (Stripe's 609 example steps took 70 s uninstrumented,
200
+ // measured 2026-09-26, each request catching the World up over every row it holds)
201
+ }, 180_000);
202
+ // A pack's lanes reach its code through the pack's router (a management-API life making a database branch the data
203
+ // plane then serves): their lives and examples, walked here too, count toward the pack's code, not its state logic
204
+ // (each lane's machines are its own, measured when the lane is). A lane is measured alone, with no lanes of its own.
205
+ const PACK = join(PACKS, vendor);
206
+ const laneWalks = vendor.includes('/') ? [] : lanesOf(PACK).flatMap((dir) => [{ file: 'customer-life.json', beats: true }, { file: 'vendor-examples.json', beats: false }]
207
+ .filter((w) => existsSync(join(dir, 'journeys', w.file))).map((w) => ({ ...w, path: join(dir, 'journeys', w.file), lane: dir.split('/').slice(-2).join('/') })));
208
+ for (const [n, w] of laneWalks.entries())
209
+ test(`${vendor}: ${w.lane} ${w.file} walks for the pack's code`, async () => {
210
+ const world = inMemoryWorld(`coverage-${vendor}-lane-${n}`);
211
+ const failures = [];
212
+ try {
213
+ await walk(parseExactJson(readFileSync(w.path, 'utf8')), world.root(), failures, { beats: w.beats });
214
+ }
215
+ finally {
216
+ world.restore();
217
+ }
218
+ expect(failures.filter((f) => f.beat !== 'SHAPE')).toEqual([]);
219
+ }, 180_000);
220
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};