@volter/twin-standard 1.0.113 → 1.0.115

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.
package/README.md CHANGED
@@ -8,6 +8,7 @@ place a pack is judged judges it by the same standard.
8
8
  ```bash
9
9
  twin-standard create <dir> --init <vendor> --package <@scope/name> # fixed files, in your repository
10
10
  twin-standard create <unit> --index # fixed handler map and fetch after derivation
11
+ twin-standard create <unit> --index --building # construction only: keep unfinished operations as gaps
11
12
  twin-standard gate <pack repository> [<pack or lane dir>…] # every unit passes the form section, or exit 1
12
13
  twin-standard gate-published <name>@<version>… [--registry <url>] # a published version, as the catalog grades a submission
13
14
  twin-standard grade <pack or lane dir>… [--static] [--form] # each criterion's result
package/dist/src/cli.js CHANGED
@@ -31,7 +31,7 @@ import { standardPackageFile } from "./package-file.js";
31
31
  import { gatePublished } from "./published.js";
32
32
  const [command, ...args] = process.argv.slice(2);
33
33
  const usage = () => {
34
- console.error('usage: twin-standard gate <repo> [units…] | gate-published <name>@<version>… [--registry <url>] | grade <unit>… [--static] [--form] | derive <unit> [--out <dir>] | read-page <url> [--raw] | check-sources <unit> [--recheck] | conform <pack dir> | <name>@<version> [--registry <url>] | create <dir> --init <vendor> --package <@scope/name> | create <unit> --index | facts <pack dir> [--out <file>] | prepare-consumer <pack dir> --app <new dir> --cli <volter executable> --integrity <sha512> --out <json> [--candidate <tgz>] | walk-consumer <pack dir> --prepared-consumer <json> [--out <file>] | assess <pack dir> --prepared-consumer <json> [--browser] [--out <file>] | --version');
34
+ console.error('usage: twin-standard gate <repo> [units…] | gate-published <name>@<version>… [--registry <url>] | grade <unit>… [--static] [--form] | derive <unit> [--out <dir>] | read-page <url> [--raw] | check-sources <unit> [--recheck] | conform <pack dir> | <name>@<version> [--registry <url>] | create <dir> --init <vendor> --package <@scope/name> | create <unit> --index [--building] | facts <pack dir> [--out <file>] | prepare-consumer <pack dir> --app <new dir> --cli <volter executable> --integrity <sha512> --out <json> [--candidate <tgz>] | walk-consumer <pack dir> --prepared-consumer <json> [--out <file>] | assess <pack dir> --prepared-consumer <json> [--browser] [--out <file>] | --version');
35
35
  process.exit(2);
36
36
  };
37
37
  if (command === '--version') {
@@ -26,8 +26,11 @@ type Decision = {
26
26
  * the vendor's own API's operation of that id, or, when its own API has none, the one lane's that has it. So an id two
27
27
  * units share is two decisions, and a lane's operation its root serves stays the lane's gap (Clerk's Backend and
28
28
  * Frontend APIs both name `ListOrganizationMemberships`). A demanded operation (journeys/demand.json, the vendor's
29
- * backend calls) is the unit's a row naming no lane would decide. */
30
- export declare function decisionTableProblems(unit: string, byUnit: Record<string, Owners>, decisions: Decision[], demanded?: string[], doors?: string[]): string[];
29
+ * backend calls) is the unit's a row naming no lane would decide. Construction may leave unfinished operations as
30
+ * gaps; the default and conformance callers require completion. Both modes hold implemented operations to the table. */
31
+ export declare function decisionTableProblems(unit: string, byUnit: Record<string, Owners>, decisions: Decision[], demanded?: string[], doors?: string[], options?: {
32
+ building?: boolean;
33
+ }): string[];
31
34
  /** Every check over the pack at `dir`, in order. */
32
35
  export declare function conformance(dir: string): Promise<ConformanceResult[]>;
33
36
  export {};
@@ -73,8 +73,9 @@ const manifestOf = async (unit) => (await import(__rewriteRelativeImportExtensio
73
73
  * the vendor's own API's operation of that id, or, when its own API has none, the one lane's that has it. So an id two
74
74
  * units share is two decisions, and a lane's operation its root serves stays the lane's gap (Clerk's Backend and
75
75
  * Frontend APIs both name `ListOrganizationMemberships`). A demanded operation (journeys/demand.json, the vendor's
76
- * backend calls) is the unit's a row naming no lane would decide. */
77
- export function decisionTableProblems(unit, byUnit, decisions, demanded = [], doors = []) {
76
+ * backend calls) is the unit's a row naming no lane would decide. Construction may leave unfinished operations as
77
+ * gaps; the default and conformance callers require completion. Both modes hold implemented operations to the table. */
78
+ export function decisionTableProblems(unit, byUnit, decisions, demanded = [], doors = [], options = {}) {
78
79
  const owners = { ...(byUnit[unit] ?? {}), ...Object.fromEntries(doors.map((id) => [`door:${id}`, 'door'])) };
79
80
  const holders = (id) => Object.keys(byUnit).filter((u) => id in byUnit[u]);
80
81
  const unnamedUnit = (id) => (id in (byUnit[''] ?? {}) ? '' : holders(id).length === 1 ? holders(id)[0] : undefined);
@@ -94,12 +95,13 @@ export function decisionTableProblems(unit, byUnit, decisions, demanded = [], do
94
95
  for (const d of mine) {
95
96
  if (!(d.operation in owners))
96
97
  out.push(`${d.operation} is decided for ${unit || 'the root'}, which has no such operation`);
97
- else if (owners[d.operation] === 'gap' && d.mechanism !== 'unmodeled')
98
+ else if (!options.building && owners[d.operation] === 'gap' && d.mechanism !== 'unmodeled')
98
99
  out.push(`${d.operation} is decided ${d.mechanism} and served by nothing`);
99
100
  }
100
- for (const op of demanded)
101
- if (op && unnamedUnit(op) === unit && owners[op] === 'gap')
102
- out.push(`${op} is demanded (journeys/demand.json) and served by nothing`);
101
+ if (!options.building)
102
+ for (const op of demanded)
103
+ if (op && unnamedUnit(op) === unit && owners[op] === 'gap')
104
+ out.push(`${op} is demanded (journeys/demand.json) and served by nothing`);
103
105
  return out;
104
106
  }
105
107
  /** Every operation a unit serves is decided, every operation demanded or refreshed is served (create-pack --index's
@@ -8,7 +8,9 @@ export declare function handlerIndex(dir: string): string;
8
8
  * serves, the demand it answers and the mechanism that serves it (`handler` or `core`); an operation it does not serve
9
9
  * is `unmodeled` in the manifest. The unit's owners are the vendor's dispatch's for that unit. A problem stops the index:
10
10
  * six packs once shipped answering 404 to everything, and scope once grew past demand, both unseen by the tools. */
11
- export declare function decisionProblems(dir: string): Promise<string[]>;
11
+ export declare function decisionProblems(dir: string, options?: {
12
+ building?: boolean;
13
+ }): Promise<string[]>;
12
14
  /** A unit's fetch.ts, a fixed file: `createPackFetch` over its parts as they stand (the manifest, the surface, the handler
13
15
  * map, and its doors, clock and screens where it has them). `--init` writes it, and `--index` again when a door file,
14
16
  * the clock or a screen is added. `name` is the fetch's export (`createClerkFetch`, a lane's `createFapiLaneFetch`). */
@@ -22,6 +22,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
22
22
  // vendor facts, the descriptor's hosts and the families
23
23
  // bun scripts/create-pack.ts <unit> --index write src/semantics/index.ts, the handler map, and src/fetch.ts from the
24
24
  // families' files (every exported function, by its name)
25
+ // bun scripts/create-pack.ts <unit> --index --building the same during construction; unfinished operations stay gaps,
26
+ // and implemented operations must match their decisions
25
27
  // bun scripts/create-pack.ts <unit> --brief [--out f] the author's brief: every operation the derived core does not
26
28
  // serve and no handler answers yet, by family, with what the
27
29
  // vendored spec says of it (summary, description, parameters,
@@ -101,7 +103,7 @@ export function handlerIndex(dir) {
101
103
  * serves, the demand it answers and the mechanism that serves it (`handler` or `core`); an operation it does not serve
102
104
  * is `unmodeled` in the manifest. The unit's owners are the vendor's dispatch's for that unit. A problem stops the index:
103
105
  * six packs once shipped answering 404 to everything, and scope once grew past demand, both unseen by the tools. */
104
- export async function decisionProblems(dir) {
106
+ export async function decisionProblems(dir, options = {}) {
105
107
  const out = [];
106
108
  const journeys = existsSync(join(dir, 'journeys')) ? join(dir, 'journeys') : join(dir, '..', 'journeys');
107
109
  const tablePath = join(journeys, 'decisions.json');
@@ -129,10 +131,10 @@ export async function decisionProblems(dir) {
129
131
  // a refresh reads the vendor's list or retrieve, and a World rooted at a second World's twin of the pack reads it from
130
132
  // that twin (pack-done's (g)): an operation a refresh scope names is one the pack serves
131
133
  const { manifest } = await import(__rewriteRelativeImportExtension(resolve(dir, 'src', 'manifest.ts')));
132
- out.push(...decisionTableProblems(unit, byUnit, table.decisions ?? [], demanded, manifest?.doors?.map((d) => d.id) ?? []));
134
+ out.push(...decisionTableProblems(unit, byUnit, table.decisions ?? [], demanded, manifest?.doors?.map((d) => d.id) ?? [], options));
133
135
  for (const [name, decl] of Object.entries(manifest?.resources ?? {})) {
134
136
  for (const id of [decl.refresh?.list ?? decl.refresh?.get, decl.refresh?.detail])
135
- if (id && id in owners && owners[id] === 'gap')
137
+ if (!options.building && id && id in owners && owners[id] === 'gap')
136
138
  out.push(`${id}: ${name}'s refresh reads it, and nothing serves it (serve it, or declare refresh none with why)`);
137
139
  }
138
140
  return out;
@@ -454,7 +456,11 @@ export function laneInitFiles(vendor, lanes) {
454
456
  export async function createPack(args) {
455
457
  const [unit, ...rest] = args;
456
458
  if (!unit || (!rest.includes('--index') && !rest.includes('--brief') && !rest.includes('--init'))) {
457
- console.error('usage: bun scripts/create-pack.ts <dir> --init <vendor> | <unit> --index | --brief [--out <file>]');
459
+ console.error('usage: bun scripts/create-pack.ts <dir> --init <vendor> | <unit> --index [--building] | --brief [--out <file>]');
460
+ process.exit(2);
461
+ }
462
+ if (rest.includes('--building') && (!rest.includes('--index') || rest.includes('--init') || rest.includes('--brief'))) {
463
+ console.error('create-pack: --building requires --index alone; it generates an unfinished pack, not completion evidence');
458
464
  process.exit(2);
459
465
  }
460
466
  const dir = resolve(unit);
@@ -576,12 +582,14 @@ export async function createPack(args) {
576
582
  if (odd)
577
583
  process.exit(1);
578
584
  if (!lanesOnly) {
579
- const problems = await decisionProblems(dir);
585
+ const problems = await decisionProblems(dir, { building: rest.includes('--building') });
580
586
  if (problems.length) {
581
587
  console.error(`create-pack: ${basename(dir)}: ${problems.length} problem(s) with what the unit serves:\n ${problems.join('\n ')}`);
582
588
  process.exit(1);
583
589
  }
584
590
  }
591
+ if (rest.includes('--building'))
592
+ console.log('create-pack: construction index only; unfinished operations remain gaps; run ordinary --index before completion');
585
593
  }
586
594
  if (rest.includes('--brief')) {
587
595
  const text = await brief(unit);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.121",
78
+ "@volter/world-core": "3.0.123",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
package/src/cli.ts CHANGED
@@ -24,7 +24,7 @@ import { gatePublished } from './published.ts';
24
24
 
25
25
  const [command, ...args] = process.argv.slice(2);
26
26
  const usage = (): never => {
27
- console.error('usage: twin-standard gate <repo> [units…] | gate-published <name>@<version>… [--registry <url>] | grade <unit>… [--static] [--form] | derive <unit> [--out <dir>] | read-page <url> [--raw] | check-sources <unit> [--recheck] | conform <pack dir> | <name>@<version> [--registry <url>] | create <dir> --init <vendor> --package <@scope/name> | create <unit> --index | facts <pack dir> [--out <file>] | prepare-consumer <pack dir> --app <new dir> --cli <volter executable> --integrity <sha512> --out <json> [--candidate <tgz>] | walk-consumer <pack dir> --prepared-consumer <json> [--out <file>] | assess <pack dir> --prepared-consumer <json> [--browser] [--out <file>] | --version');
27
+ console.error('usage: twin-standard gate <repo> [units…] | gate-published <name>@<version>… [--registry <url>] | grade <unit>… [--static] [--form] | derive <unit> [--out <dir>] | read-page <url> [--raw] | check-sources <unit> [--recheck] | conform <pack dir> | <name>@<version> [--registry <url>] | create <dir> --init <vendor> --package <@scope/name> | create <unit> --index [--building] | facts <pack dir> [--out <file>] | prepare-consumer <pack dir> --app <new dir> --cli <volter executable> --integrity <sha512> --out <json> [--candidate <tgz>] | walk-consumer <pack dir> --prepared-consumer <json> [--out <file>] | assess <pack dir> --prepared-consumer <json> [--browser] [--out <file>] | --version');
28
28
  process.exit(2);
29
29
  };
30
30
 
@@ -86,8 +86,9 @@ type Decision = { operation: string; mechanism: string; demand?: string; lane?:
86
86
  * the vendor's own API's operation of that id, or, when its own API has none, the one lane's that has it. So an id two
87
87
  * units share is two decisions, and a lane's operation its root serves stays the lane's gap (Clerk's Backend and
88
88
  * Frontend APIs both name `ListOrganizationMemberships`). A demanded operation (journeys/demand.json, the vendor's
89
- * backend calls) is the unit's a row naming no lane would decide. */
90
- export function decisionTableProblems(unit: string, byUnit: Record<string, Owners>, decisions: Decision[], demanded: string[] = [], doors: string[] = []): string[] {
89
+ * backend calls) is the unit's a row naming no lane would decide. Construction may leave unfinished operations as
90
+ * gaps; the default and conformance callers require completion. Both modes hold implemented operations to the table. */
91
+ export function decisionTableProblems(unit: string, byUnit: Record<string, Owners>, decisions: Decision[], demanded: string[] = [], doors: string[] = [], options: { building?: boolean } = {}): string[] {
91
92
  const owners = { ...(byUnit[unit] ?? {}), ...Object.fromEntries(doors.map((id) => [`door:${id}`, 'door'])) };
92
93
  const holders = (id: string): string[] => Object.keys(byUnit).filter((u) => id in byUnit[u]!);
93
94
  const unnamedUnit = (id: string): string | undefined => (id in (byUnit[''] ?? {}) ? '' : holders(id).length === 1 ? holders(id)[0] : undefined);
@@ -103,9 +104,9 @@ export function decisionTableProblems(unit: string, byUnit: Record<string, Owner
103
104
  }
104
105
  for (const d of mine) {
105
106
  if (!(d.operation in owners)) out.push(`${d.operation} is decided for ${unit || 'the root'}, which has no such operation`);
106
- else if (owners[d.operation] === 'gap' && d.mechanism !== 'unmodeled') out.push(`${d.operation} is decided ${d.mechanism} and served by nothing`);
107
+ else if (!options.building && owners[d.operation] === 'gap' && d.mechanism !== 'unmodeled') out.push(`${d.operation} is decided ${d.mechanism} and served by nothing`);
107
108
  }
108
- for (const op of demanded) if (op && unnamedUnit(op) === unit && owners[op] === 'gap') out.push(`${op} is demanded (journeys/demand.json) and served by nothing`);
109
+ if (!options.building) for (const op of demanded) if (op && unnamedUnit(op) === unit && owners[op] === 'gap') out.push(`${op} is demanded (journeys/demand.json) and served by nothing`);
109
110
  return out;
110
111
  }
111
112
 
@@ -14,6 +14,8 @@
14
14
  // vendor facts, the descriptor's hosts and the families
15
15
  // bun scripts/create-pack.ts <unit> --index write src/semantics/index.ts, the handler map, and src/fetch.ts from the
16
16
  // families' files (every exported function, by its name)
17
+ // bun scripts/create-pack.ts <unit> --index --building the same during construction; unfinished operations stay gaps,
18
+ // and implemented operations must match their decisions
17
19
  // bun scripts/create-pack.ts <unit> --brief [--out f] the author's brief: every operation the derived core does not
18
20
  // serve and no handler answers yet, by family, with what the
19
21
  // vendored spec says of it (summary, description, parameters,
@@ -93,7 +95,7 @@ export function handlerIndex(dir: string): string {
93
95
  * serves, the demand it answers and the mechanism that serves it (`handler` or `core`); an operation it does not serve
94
96
  * is `unmodeled` in the manifest. The unit's owners are the vendor's dispatch's for that unit. A problem stops the index:
95
97
  * six packs once shipped answering 404 to everything, and scope once grew past demand, both unseen by the tools. */
96
- export async function decisionProblems(dir: string): Promise<string[]> {
98
+ export async function decisionProblems(dir: string, options: { building?: boolean } = {}): Promise<string[]> {
97
99
  const out: string[] = [];
98
100
  const journeys = existsSync(join(dir, 'journeys')) ? join(dir, 'journeys') : join(dir, '..', 'journeys');
99
101
  const tablePath = join(journeys, 'decisions.json');
@@ -119,9 +121,9 @@ export async function decisionProblems(dir: string): Promise<string[]> {
119
121
  // a refresh reads the vendor's list or retrieve, and a World rooted at a second World's twin of the pack reads it from
120
122
  // that twin (pack-done's (g)): an operation a refresh scope names is one the pack serves
121
123
  const { manifest } = await import(resolve(dir, 'src', 'manifest.ts')) as { manifest?: { doors?: Array<{ id: string }>; resources?: Record<string, { refresh?: { list?: string; detail?: string; get?: string } }> } };
122
- out.push(...decisionTableProblems(unit, byUnit, table.decisions ?? [], demanded, manifest?.doors?.map((d) => d.id) ?? []));
124
+ out.push(...decisionTableProblems(unit, byUnit, table.decisions ?? [], demanded, manifest?.doors?.map((d) => d.id) ?? [], options));
123
125
  for (const [name, decl] of Object.entries(manifest?.resources ?? {})) {
124
- for (const id of [decl.refresh?.list ?? decl.refresh?.get, decl.refresh?.detail]) if (id && id in owners && owners[id] === 'gap') out.push(`${id}: ${name}'s refresh reads it, and nothing serves it (serve it, or declare refresh none with why)`);
126
+ for (const id of [decl.refresh?.list ?? decl.refresh?.get, decl.refresh?.detail]) if (!options.building && id && id in owners && owners[id] === 'gap') out.push(`${id}: ${name}'s refresh reads it, and nothing serves it (serve it, or declare refresh none with why)`);
125
127
  }
126
128
  return out;
127
129
  }
@@ -447,7 +449,11 @@ export function laneInitFiles(vendor: string, lanes: string[]): Record<string, s
447
449
  export async function createPack(args: string[]): Promise<void> {
448
450
  const [unit, ...rest] = args;
449
451
  if (!unit || (!rest.includes('--index') && !rest.includes('--brief') && !rest.includes('--init'))) {
450
- console.error('usage: bun scripts/create-pack.ts <dir> --init <vendor> | <unit> --index | --brief [--out <file>]');
452
+ console.error('usage: bun scripts/create-pack.ts <dir> --init <vendor> | <unit> --index [--building] | --brief [--out <file>]');
453
+ process.exit(2);
454
+ }
455
+ if (rest.includes('--building') && (!rest.includes('--index') || rest.includes('--init') || rest.includes('--brief'))) {
456
+ console.error('create-pack: --building requires --index alone; it generates an unfinished pack, not completion evidence');
451
457
  process.exit(2);
452
458
  }
453
459
  const dir = resolve(unit);
@@ -530,9 +536,10 @@ export async function createPack(args: string[]): Promise<void> {
530
536
  }
531
537
  if (odd) process.exit(1);
532
538
  if (!lanesOnly) {
533
- const problems = await decisionProblems(dir);
539
+ const problems = await decisionProblems(dir, { building: rest.includes('--building') });
534
540
  if (problems.length) { console.error(`create-pack: ${basename(dir)}: ${problems.length} problem(s) with what the unit serves:\n ${problems.join('\n ')}`); process.exit(1); }
535
541
  }
542
+ if (rest.includes('--building')) console.log('create-pack: construction index only; unfinished operations remain gaps; run ordinary --index before completion');
536
543
  }
537
544
  if (rest.includes('--brief')) {
538
545
  const text = await brief(unit);