@volter/twin-standard 1.0.148 → 1.0.150

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 (103) 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 +6 -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 -3
  25. package/dist/src/conformance.js +293 -8
  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 +38 -4
  29. package/dist/src/derive.js +21 -13
  30. package/dist/src/gate.d.ts +1 -0
  31. package/dist/src/gate.js +47 -4
  32. package/dist/src/grade.d.ts +1 -0
  33. package/dist/src/grade.js +1 -1
  34. package/dist/src/pack-facts.d.ts +9 -1
  35. package/dist/src/pack-facts.js +11 -2
  36. package/dist/src/pack-files.js +2 -1
  37. package/dist/src/protocol-3.js +40 -6
  38. package/dist/src/spec-ir-client.js +3 -2
  39. package/dist/src/spec-ir-commands.d.ts +73 -0
  40. package/dist/src/spec-ir-commands.js +63 -0
  41. package/dist/src/spec-ir.d.ts +2 -0
  42. package/dist/src/spec-ir.js +43 -0
  43. package/dist/src/tools/judge-brief.d.ts +1 -0
  44. package/dist/src/tools/judge-brief.js +67 -0
  45. package/dist/src/tools/life-coverage-walk.d.ts +2 -0
  46. package/dist/src/tools/life-coverage-walk.js +220 -0
  47. package/dist/src/tools/life-coverage.d.ts +2 -0
  48. package/dist/src/tools/life-coverage.js +271 -0
  49. package/dist/src/tools/packs-root.d.ts +9 -0
  50. package/dist/src/tools/packs-root.js +17 -0
  51. package/dist/src/tools/run-tool.d.ts +1 -0
  52. package/dist/src/tools/run-tool.js +18 -0
  53. package/dist/src/tools/score-pack.d.ts +2 -0
  54. package/dist/src/tools/score-pack.js +264 -0
  55. package/dist/src/tools/vendor-examples.d.ts +2 -0
  56. package/dist/src/tools/vendor-examples.js +1493 -0
  57. package/dist/src/types.d.ts +2 -0
  58. package/dist/src/verdict.js +1 -1
  59. package/dist/src/walk/behavior-journey.d.ts +6 -0
  60. package/dist/src/walk/behavior-journey.js +108 -8
  61. package/dist/src/walk/journey-kit.d.ts +23 -1
  62. package/dist/src/walk/journey-kit.js +7 -2
  63. package/dist/src/walk/rendered-scalar.d.ts +3 -0
  64. package/dist/src/walk/rendered-scalar.js +23 -0
  65. package/dist/src/walk/sse-example.js +45 -0
  66. package/package.json +2 -2
  67. package/src/assessment.ts +2 -1
  68. package/src/cli.ts +15 -2
  69. package/src/clients/harness.ts +4 -2
  70. package/src/clients/jira.ts +73 -0
  71. package/src/clients/polar.ts +73 -0
  72. package/src/clients/resend.ts +32 -0
  73. package/src/clients/run.ts +2 -2
  74. package/src/clients/sentry.ts +1 -25
  75. package/src/clients/smtp.ts +49 -0
  76. package/src/clients/stripe.ts +1 -1
  77. package/src/clients/volteridentity.ts +4 -3
  78. package/src/clients/x.ts +66 -0
  79. package/src/conformance.ts +226 -10
  80. package/src/consumer.ts +5 -1
  81. package/src/create-pack.ts +35 -5
  82. package/src/derive.ts +21 -14
  83. package/src/gate.ts +40 -6
  84. package/src/grade.ts +2 -2
  85. package/src/pack-facts.ts +10 -3
  86. package/src/pack-files.ts +2 -1
  87. package/src/protocol-3.ts +32 -6
  88. package/src/spec-ir-client.ts +3 -2
  89. package/src/spec-ir-commands.ts +81 -0
  90. package/src/spec-ir.ts +31 -0
  91. package/src/tools/judge-brief.ts +63 -0
  92. package/src/tools/life-coverage-walk.ts +173 -0
  93. package/src/tools/life-coverage.ts +237 -0
  94. package/src/tools/packs-root.ts +21 -0
  95. package/src/tools/run-tool.ts +10 -0
  96. package/src/tools/score-pack.ts +244 -0
  97. package/src/tools/vendor-examples.ts +1250 -0
  98. package/src/types.ts +2 -0
  99. package/src/verdict.ts +1 -1
  100. package/src/walk/behavior-journey.ts +65 -4
  101. package/src/walk/journey-kit.ts +21 -3
  102. package/src/walk/rendered-scalar.ts +17 -0
  103. package/src/walk/sse-example.ts +37 -0
package/dist/src/gate.js CHANGED
@@ -42,6 +42,48 @@ export function unitsOf(repo) {
42
42
  * routes by hand: only the fetch reaches a lane. A directory with no manifest and no lane is no Protocol 3 pack. */
43
43
  const ROOT_ENTRIES = new Set(['README.md', 'package.json', 'LICENSE', 'CHANGELOG.md', 'tsconfig.json', 'tsconfig.build.json', 'gate.ts', 'census.json', 'vendor', 'journeys', 'spec', 'generated', 'src', 'node_modules']);
44
44
  const ROOT_SRC = new Set(['manifest.ts', 'fetch.ts', 'index.ts', 'cli.ts', 'server.ts']);
45
+ /** The copied root seed is data, never an alternate executable seed runner. */
46
+ function rootSeedFailures(dir) {
47
+ const path = join(dir, 'src', 'semantics', 'seed.ts');
48
+ if (!existsSync(path))
49
+ return [];
50
+ const text = readFileSync(path, 'utf8');
51
+ if (ts.transpileModule(text, { fileName: path, reportDiagnostics: true }).diagnostics?.some(diagnostic => diagnostic.category === ts.DiagnosticCategory.Error))
52
+ return ['handler-contract: root seed has invalid TypeScript syntax'];
53
+ const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true);
54
+ const literal = (node) => {
55
+ if (ts.isStringLiteral(node) || ts.isNumericLiteral(node) || [ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword, ts.SyntaxKind.NullKeyword].includes(node.kind))
56
+ return true;
57
+ if (ts.isPrefixUnaryExpression(node))
58
+ return node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand);
59
+ if (ts.isArrayLiteralExpression(node))
60
+ return node.elements.every(element => !ts.isSpreadElement(element) && !ts.isOmittedExpression(element) && literal(element));
61
+ return ts.isObjectLiteralExpression(node) && node.properties.every(property => ts.isPropertyAssignment(property)
62
+ && !ts.isComputedPropertyName(property.name) && literal(property.initializer));
63
+ };
64
+ let imports = 0, seeds = 0;
65
+ for (const statement of source.statements) {
66
+ if (ts.isImportDeclaration(statement)) {
67
+ const clause = statement.importClause, bindings = clause?.namedBindings;
68
+ if (!clause?.isTypeOnly || clause.name || !bindings || !ts.isNamedImports(bindings)
69
+ || bindings.elements.length !== 1 || bindings.elements[0].name.text !== 'SeedCall' || bindings.elements[0].propertyName
70
+ || !ts.isStringLiteral(statement.moduleSpecifier) || statement.moduleSpecifier.text !== '@volter/world-core')
71
+ return ['handler-contract: root seed imports only the SeedCall type from world-core'];
72
+ imports++;
73
+ continue;
74
+ }
75
+ if (!ts.isVariableStatement(statement) || !statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)
76
+ || !(statement.declarationList.flags & ts.NodeFlags.Const) || statement.declarationList.declarations.length !== 1)
77
+ return ['handler-contract: root seed exports only const seed: SeedCall[] literal data'];
78
+ const declaration = statement.declarationList.declarations[0];
79
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== 'seed' || !declaration.type || !ts.isArrayTypeNode(declaration.type)
80
+ || !ts.isTypeReferenceNode(declaration.type.elementType) || declaration.type.elementType.typeName.getText(source) !== 'SeedCall' || declaration.type.elementType.typeArguments?.length
81
+ || !declaration.initializer || !ts.isArrayLiteralExpression(declaration.initializer) || !literal(declaration.initializer))
82
+ return ['handler-contract: root seed must be literal SeedCall[] data'];
83
+ seeds++;
84
+ }
85
+ return imports === 1 && seeds === 1 ? [] : ['handler-contract: root seed requires one SeedCall type import and one seed export'];
86
+ }
45
87
  /** What the repository ignores (a build's dist/, a World's state) is no file of the pack, as protocol-3's layout reads it. */
46
88
  function ignored(dir, names) {
47
89
  if (!names.length)
@@ -57,15 +99,16 @@ export function laneRootFailures(dir) {
57
99
  const skip = ignored(dir, stray);
58
100
  const outside = [
59
101
  ...stray.filter((n) => !skip.has(n)),
60
- ...(existsSync(join(dir, 'spec')) ? readdirSync(join(dir, 'spec')).filter((n) => !['sources.json', 'SOURCE.md', 'README.md'].includes(n)).map((n) => `spec/${n}`) : []),
61
- ...(existsSync(join(dir, 'src')) ? readdirSync(join(dir, 'src')).filter((n) => !ROOT_SRC.has(n) && !n.endsWith('.d.ts') && n !== 'semantics').map((n) => `src/${n}`) : []),
102
+ ...(existsSync(join(dir, 'spec')) ? readdirSync(join(dir, 'spec')).filter((n) => !isPackArtifact(n) && !['sources.json', 'SOURCE.md', 'README.md'].includes(n)).map((n) => `spec/${n}`) : []),
103
+ ...(existsSync(join(dir, 'src')) ? readdirSync(join(dir, 'src')).filter((n) => !isPackArtifact(n) && !ROOT_SRC.has(n) && !n.endsWith('.d.ts') && n !== 'semantics').map((n) => `src/${n}`) : []),
62
104
  // what the lanes share, and nothing else (architecture, "Pack layout": a vendor whose every API is a lane)
63
- ...(existsSync(join(dir, 'src', 'semantics')) ? readdirSync(join(dir, 'src', 'semantics')).filter((n) => n !== 'shared.ts').map((n) => `src/semantics/${n}`) : []),
105
+ ...(existsSync(join(dir, 'src', 'semantics')) ? readdirSync(join(dir, 'src', 'semantics')).filter((n) => !isPackArtifact(n) && !['shared.ts', 'seed.ts'].includes(n)).map((n) => `src/semantics/${n}`) : []),
64
106
  ];
65
107
  const failures = outside.length ? [`layout: ${outside.length} outside the vendor's root (its lanes hold the rest): ${outside.slice(0, 8).join(', ')}${outside.length > 8 ? ` and ${outside.length - 8} more` : ''}`] : [];
66
108
  // the vendor's front is declared and the kernel serves it: a manifest, and a fetch that is createVendorFetch over it
67
109
  const src = (n) => (existsSync(join(dir, 'src', n)) ? readFileSync(join(dir, 'src', n), 'utf8') : '');
68
110
  failures.push(...unpublishedUnitFiles(dir).map((f) => `layout: ${f}`));
111
+ failures.push(...rootSeedFailures(dir));
69
112
  if (!src('manifest.ts'))
70
113
  failures.push("layout: no src/manifest.ts: a vendor of lanes declares its lanes' routes, discovery and descriptor");
71
114
  if (!/\bcreateVendorFetch\(/.test(src('fetch.ts')))
@@ -109,7 +152,7 @@ export async function p3Gate(units, opts = {}) {
109
152
  }
110
153
  const grades = await grade(unit, { static: true, sections: ['form'], ...opts });
111
154
  const form = Object.values(grades).flatMap((g) => g.criteria).filter((c) => !c.pass);
112
- out.push({ unit, failures: form.map((c) => `${c.id}: ${c.reason}`) });
155
+ out.push({ unit, failures: form.map((c) => `${c.id}: ${c.reason}`), cacheable: Object.values(grades).every(g => g.criteria.every(c => c.cacheable !== false)) });
113
156
  }
114
157
  return out;
115
158
  });
@@ -7,6 +7,7 @@ export interface CriterionGrade {
7
7
  asks: string;
8
8
  pass: boolean;
9
9
  reason: string;
10
+ cacheable?: boolean;
10
11
  }
11
12
  export interface StandardGrade {
12
13
  grade: number;
package/dist/src/grade.js CHANGED
@@ -26,7 +26,7 @@ export async function grade(dir, opts = {}) {
26
26
  result = await c.check(unit);
27
27
  }
28
28
  catch (e) {
29
- result = { pass: false, reason: `the check threw: ${e.message.slice(0, 160)}` };
29
+ result = { pass: false, cacheable: false, reason: `the check threw: ${e.message.slice(0, 160)}` };
30
30
  }
31
31
  opts.onCriterion?.(name, c.id, result);
32
32
  criteria.push({ id: c.id, section: c.section, asks: c.asks, ...result });
@@ -76,9 +76,17 @@ export type PackFacts = {
76
76
  };
77
77
  /** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
78
78
  managedService?: {
79
- kind: 'redis' | 'mongodb';
79
+ kind: 'redis' | 'mongodb' | 'clickhouse';
80
80
  scheme: string;
81
81
  note: string;
82
+ configurationArg?: '--configuration';
83
+ originalInputs?: {
84
+ version: 1;
85
+ manifestArg: '--original-inputs';
86
+ rootArg: '--original-root';
87
+ digestArg: '--original-inputs-sha256';
88
+ paths: string[];
89
+ };
82
90
  };
83
91
  /** The World's managed database the runtime binds the twin to (see TwinPack.managedDatabase). */
84
92
  managedDatabase?: {
@@ -184,12 +184,21 @@ export async function compilePackFacts(dirs) {
184
184
  }
185
185
  if (pack.managedService !== undefined) {
186
186
  const ms = pack.managedService;
187
- if (ms.kind !== 'redis' && ms.kind !== 'mongodb')
188
- throw new Error(`pack-facts: ${vendor} declares managedService.kind "${String(ms.kind)}"; the managed-infrastructure kinds a twin backs are redis and mongodb`);
187
+ if (ms.kind !== 'redis' && ms.kind !== 'mongodb' && ms.kind !== 'clickhouse')
188
+ throw new Error(`pack-facts: ${vendor} declares managedService.kind "${String(ms.kind)}"; the managed-infrastructure kinds a twin backs are redis, mongodb and clickhouse`);
189
189
  if (typeof ms.scheme !== 'string' || !/^[a-z][a-z0-9+.-]*$/.test(ms.scheme))
190
190
  throw new Error(`pack-facts: ${vendor} declares managedService.scheme "${String(ms.scheme)}"; it is the published URL's scheme`);
191
191
  if (typeof ms.note !== 'string' || ms.note.trim() === '')
192
192
  throw new Error(`pack-facts: ${vendor} declares managedService with no note`);
193
+ if (ms.configurationArg !== undefined && ms.configurationArg !== '--configuration')
194
+ throw new Error(`pack-facts: ${vendor} has an unsupported managed operator-configuration flag`);
195
+ if (ms.originalInputs !== undefined) {
196
+ const input = ms.originalInputs;
197
+ if (!input || typeof input !== 'object' || Array.isArray(input) || input.version !== 1 || input.manifestArg !== '--original-inputs' || input.rootArg !== '--original-root' || input.digestArg !== '--original-inputs-sha256'
198
+ || !Array.isArray(input.paths) || !input.paths.length || new Set(input.paths).size !== input.paths.length
199
+ || input.paths.some(path => typeof path !== 'string' || !path.startsWith('/') || path === '/' || path.slice(1).split('/').some(part => !part || part === '.' || part === '..' || /[\\\0\r\n]/.test(part))))
200
+ throw new Error(`pack-facts: ${vendor} has an invalid original-input consumer`);
201
+ }
193
202
  if (pack.bin === undefined)
194
203
  throw new Error(`pack-facts: ${vendor} declares managedService with no bin; the managed infrastructure starts the pack by its bin`);
195
204
  const other = Object.entries(packs).find(([, f]) => f.managedService?.kind === ms.kind);
@@ -1,4 +1,5 @@
1
1
  /** Release outputs and runtime state, relative to the pack directory being listed. */
2
2
  export function isPackArtifact(path) {
3
- return /^(dist|node_modules|\.volter)(\/|$)/.test(path) || path === 'generated/pack-facts.json';
3
+ return /^(dist|node_modules|\.volter)(\/|$)/.test(path) || path === 'generated/pack-facts.json'
4
+ || path.split('/').includes('.metadata_never_index');
4
5
  }
@@ -101,7 +101,7 @@ function filesUnder(dir, base = dir) {
101
101
  if (run.status !== 0 || run.error) {
102
102
  throw new Error(`git ls-files failed: directory=${JSON.stringify(top)} status=${run.status} signal=${JSON.stringify(run.signal)} error=${JSON.stringify(run.error?.message ?? null)} stderr=${JSON.stringify(String(run.stderr ?? ''))}`);
103
103
  }
104
- files = [...new Set(String(run.stdout).split('\0').filter(Boolean))];
104
+ files = [...new Set(String(run.stdout).split('\0').filter(f => f && !f.split('/').includes('.metadata_never_index')))];
105
105
  listing?.set(top, files);
106
106
  }
107
107
  const under = rel(top, dir);
@@ -114,6 +114,9 @@ function allFilesUnder(dir, base = dir, excludeArtifacts = false) {
114
114
  return [];
115
115
  const out = [];
116
116
  for (const name of readdirSync(dir)) {
117
+ // macOS directory bookkeeping is not a pack source or release output.
118
+ if (name === '.metadata_never_index')
119
+ continue;
117
120
  const path = join(dir, name);
118
121
  if (excludeArtifacts && isPackArtifact(rel(base, path)))
119
122
  continue;
@@ -304,11 +307,31 @@ const criteria = [
304
307
  }
305
308
  // the pack's own path and the scratch directory are this checkout's, not the pack's: the verdict names neither, so
306
309
  // a standing written from one checkout reads the same from any other
310
+ if (run.status === null || run.signal !== null || run.error)
311
+ return { pass: false, cacheable: false,
312
+ reason: `derive-pack interrupted: status=${run.status} signal=${run.signal} error=${run.error?.message ?? ''}` };
307
313
  if (run.status !== 0)
308
- 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)}`);
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 };
309
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`);
310
316
  },
311
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
+ },
312
335
  {
313
336
  id: 'layout', section: 'form',
314
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",
@@ -534,8 +557,8 @@ const criteria = [
534
557
  // an error message's "from '<name>'", is none)
535
558
  let text = readFileSync(join(src, f), 'utf8');
536
559
  if (f === 'fetch.ts') {
537
- // The generated fetch supplies the kernel wire with the pack-local parser library.
538
- // 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.
539
562
  const source = ts.createSourceFile(f, text, ts.ScriptTarget.Latest, true);
540
563
  const loaders = [];
541
564
  const visit = (node) => {
@@ -550,6 +573,17 @@ const criteria = [
550
573
  factory.arguments[0] === graphql.parent)
551
574
  loaders.push({ start: node.getStart(source), end: node.end });
552
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
+ }
553
587
  ts.forEachChild(node, visit);
554
588
  };
555
589
  visit(source);
@@ -641,7 +675,7 @@ const criteria = [
641
675
  manifest = (await import(__rewriteRelativeImportExtension(manifestPath))).manifest;
642
676
  }
643
677
  catch (e) {
644
- return fail(`manifest.ts does not load: ${e.message.slice(0, 120)}`);
678
+ return { ...fail(`manifest.ts does not load: ${e.message.slice(0, 120)}`), cacheable: false };
645
679
  }
646
680
  const resources = Object.entries(manifest?.resources ?? {});
647
681
  const ruled = resources.filter(([, r]) => (r.state && Object.keys(r.state).length) || (Array.isArray(r.notState) && r.notState.length));
@@ -654,7 +688,7 @@ const criteria = [
654
688
  states = (await import(__rewriteRelativeImportExtension(statesPath))).states;
655
689
  }
656
690
  catch (e) {
657
- return fail(`semantics/states.ts does not load: ${e.message.slice(0, 120)}`);
691
+ return { ...fail(`semantics/states.ts does not load: ${e.message.slice(0, 120)}`), cacheable: false };
658
692
  }
659
693
  if (!states)
660
694
  return fail('semantics/states.ts exports no `states`');
@@ -1,8 +1,9 @@
1
1
  // A vendor that publishes no spec of its API but ships its own client: the API as that client calls it. Tinybird
2
2
  // publishes no OpenAPI of its platform API (its reference pages omit endpoints its own CLI calls: /v0/tags,
3
3
  // /v0/sql_tables), and its Python client (`tinybird/client.py`, class TinyB, in tinybird-cli on PyPI) makes every
4
- // request through `_req(url, method=..., data=...)`. Each such call, read from the client's syntax tree (the pack's
5
- // `spec/extract-client-ops.py`), is vendored as data in `spec/client-ops.json`: its method, its URL as the client
4
+ // request through `_req(url, method=..., data=...)`. Each such call, read from the client's syntax tree, is vendored as
5
+ // data in `spec/client-ops.json` (the program that read it is not in the pack: a pack keeps no converter in its
6
+ // `spec/`, Creating a pack, step 2): its method, its URL as the client
6
7
  // builds it (a label is the Python expression the client interpolates), the query keys it sends (literal, or the keys
7
8
  // of the dict it urlencodes) and its body's keys, each typed by the client's own annotation of the value (`null`
8
9
  // where the client does not say). A spec patch corrects the table as it corrects a JSON spec (derive-pack): what the
@@ -35,4 +35,77 @@ type RedisCommandFile = Record<string, {
35
35
  }>;
36
36
  /** The command table of the vendored files (file name → its parsed JSON), in command order. */
37
37
  export declare function fromRedisCommands(files: Record<string, RedisCommandFile>, version: string): CommandTableSpec;
38
+ export type FieldCondition = {
39
+ form: 'always' | 'given' | 'wire-version' | 'other';
40
+ text?: string;
41
+ };
42
+ /** A field of a command document: where the client sets it and under what condition. `by` names a helper that sets
43
+ * it; `from` the builder it was read in, where that is not the command's own (a base class, a subclass). */
44
+ export type CommandField = {
45
+ name: string;
46
+ type: string;
47
+ optional?: boolean;
48
+ sentWhen: FieldCondition;
49
+ source: {
50
+ file: string;
51
+ line: number;
52
+ };
53
+ by?: string;
54
+ from?: string;
55
+ };
56
+ /** A command that is a document. `statements`: for a field that is a list of statements, the fields of one. */
57
+ export type DocumentCommandDef = {
58
+ id: string;
59
+ fields: CommandField[];
60
+ statements?: Record<string, CommandField[]>;
61
+ };
62
+ export type DocumentCommandTableSpec = {
63
+ format: 'command-table';
64
+ language: 'document';
65
+ version: string;
66
+ source: {
67
+ package: string;
68
+ commit: string;
69
+ licence: string;
70
+ };
71
+ commands: DocumentCommandDef[];
72
+ envelope: CommandField[];
73
+ };
74
+ type BuilderField = CommandField & {
75
+ typeFrom?: string;
76
+ value?: string;
77
+ also?: unknown[];
78
+ spreadUnder?: string;
79
+ };
80
+ type Builder = {
81
+ builder: string;
82
+ kind: 'command' | 'statement';
83
+ file: string;
84
+ line: number;
85
+ command: string | null;
86
+ class?: string;
87
+ extends?: string;
88
+ addsTo?: string;
89
+ fields: BuilderField[];
90
+ };
91
+ export type DriverCommandBuilders = {
92
+ format: 'driver-command-builders';
93
+ driver: {
94
+ package: string;
95
+ version: string;
96
+ commit: string;
97
+ licence: string;
98
+ };
99
+ builders: Builder[];
100
+ envelope: Array<{
101
+ layer: string;
102
+ file: string;
103
+ line: number;
104
+ fields: BuilderField[];
105
+ }>;
106
+ };
107
+ /** The command table of a client's command builders: each command with its own fields, the fields its subclasses
108
+ * add, the fields a base class adds to the commands that descend from it, and the fields of its statements. A builder this cannot place is
109
+ * an error naming it; a table with a silent hole is never returned. */
110
+ export declare function fromDriverCommandBuilders(doc: DriverCommandBuilders): DocumentCommandTableSpec;
38
111
  export {};
@@ -36,3 +36,66 @@ export function fromRedisCommands(files, version) {
36
36
  commands.sort((a, b) => a.id.localeCompare(b.id));
37
37
  return { format: 'command-table', version, commands };
38
38
  }
39
+ const fieldOf = (f, from) => ({
40
+ name: f.name, type: f.type, ...(f.optional ? { optional: true } : {}), sentWhen: f.sentWhen, source: f.source,
41
+ ...(f.by ? { by: f.by } : {}), ...(from ? { from } : {}),
42
+ });
43
+ /** The command table of a client's command builders: each command with its own fields, the fields its subclasses
44
+ * add, the fields a base class adds to the commands that descend from it, and the fields of its statements. A builder this cannot place is
45
+ * an error naming it; a table with a silent hole is never returned. */
46
+ export function fromDriverCommandBuilders(doc) {
47
+ const named = doc.builders.filter((b) => b.kind === 'command' && b.command !== null);
48
+ const classOf = new Map(named.filter((b) => b.class).map((b) => [b.class, b.command]));
49
+ const commands = new Map();
50
+ const add = (id, fields, from) => {
51
+ const held = commands.get(id) ?? { id, fields: [] };
52
+ for (const f of fields)
53
+ if (!held.fields.some((h) => h.name === f.name))
54
+ held.fields.push(fieldOf(f, from));
55
+ commands.set(id, held);
56
+ };
57
+ for (const b of named)
58
+ add(b.command, b.fields);
59
+ // what a subclass adds to its parent's document, and what the base of every command adds to all of them
60
+ const everyCommand = [];
61
+ for (const b of doc.builders.filter((b) => b.kind === 'command' && b.command === null)) {
62
+ if (b.addsTo === undefined)
63
+ throw new Error(`command-builders: ${b.builder} (${b.file}:${b.line}) names no command and adds to none`);
64
+ const parent = classOf.get(b.addsTo);
65
+ if (parent !== undefined)
66
+ add(parent, b.fields, b.builder);
67
+ else if (b.class === b.addsTo)
68
+ everyCommand.push(b);
69
+ else
70
+ throw new Error(`command-builders: ${b.builder} (${b.file}:${b.line}) adds to ${b.addsTo}, which builds no named command`);
71
+ }
72
+ // the base's fields go to the commands whose class descends from it, and to no other (a getMore is not one)
73
+ const parentOf = new Map(doc.builders.filter((b) => b.class && b.extends).map((b) => [b.class, b.extends]));
74
+ const descends = (cls, base) => { for (let at = cls, hops = 0; at !== undefined && hops < 16; at = parentOf.get(at), hops += 1)
75
+ if (at === base)
76
+ return true; return false; };
77
+ for (const b of everyCommand)
78
+ for (const n of named)
79
+ if (descends(n.extends, b.class))
80
+ add(n.command, b.fields, b.builder);
81
+ // a statement builder's fields belong to the command of its file that carries the statement list
82
+ for (const b of doc.builders.filter((b) => b.kind === 'statement')) {
83
+ const owner = named.find((n) => n.file === b.file && n.fields.some((f) => f.value === 'this.statements'));
84
+ const list = owner?.fields.find((f) => f.value === 'this.statements');
85
+ if (!owner || !list)
86
+ throw new Error(`command-builders: ${b.builder} (${b.file}:${b.line}) builds a statement and no command of its file carries a statement list`);
87
+ (commands.get(owner.command).statements ??= {})[list.name] = b.fields.map((f) => fieldOf(f));
88
+ }
89
+ const envelope = [];
90
+ for (const layer of doc.envelope)
91
+ for (const f of layer.fields)
92
+ if (!envelope.some((h) => h.name === f.name))
93
+ envelope.push(fieldOf(f, layer.layer));
94
+ if (commands.size === 0)
95
+ throw new Error('command-builders: no command was read');
96
+ return {
97
+ format: 'command-table', language: 'document', version: doc.driver.version,
98
+ source: { package: doc.driver.package, commit: doc.driver.commit, licence: doc.driver.licence },
99
+ commands: [...commands.values()].sort((a, b) => a.id.localeCompare(b.id)), envelope,
100
+ };
101
+ }
@@ -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>;