@volter/twin-standard 1.0.159 → 1.0.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/check-sources.js +2 -2
- package/dist/src/clients/resend.first-use.json +2 -2
- package/dist/src/conformance.d.ts +1 -4
- package/dist/src/conformance.js +4 -19
- package/dist/src/create-pack.d.ts +1 -1
- package/dist/src/create-pack.js +13 -12
- package/dist/src/derive.js +3 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/pack-facts.d.ts +3 -0
- package/dist/src/pack-facts.js +36 -1
- package/dist/src/protocol-3.js +5 -5
- package/dist/src/spec-ir-client.js +1 -1
- package/dist/src/spec-ir-commands.js +1 -1
- package/dist/src/spec-ir-lines.js +1 -1
- package/dist/src/spec-ir-proto.d.ts +1 -1
- package/dist/src/spec-patches.js +2 -2
- package/dist/src/tools/judge-brief.js +1 -2
- package/dist/src/tools/life-coverage.js +2 -2
- package/dist/src/tools/packs-root.js +3 -3
- package/dist/src/tools/score-pack.js +1 -2
- package/dist/src/tools/vendor-examples.js +4 -5
- package/dist/src/verdict.js +1 -1
- package/dist/src/walk/behavior-journey.js +18 -1
- package/dist/src/walk/journey-kit.d.ts +2 -2
- package/dist/src/walk/serve-pack.js +6 -0
- package/dist/src/walk/shape.d.ts +1 -1
- package/dist/src/walk/shape.js +1 -1
- package/package.json +6 -4
- package/src/check-sources.ts +2 -2
- package/src/clients/resend.first-use.json +2 -2
- package/src/conformance.ts +4 -19
- package/src/create-pack.ts +13 -12
- package/src/derive.ts +3 -3
- package/src/index.ts +1 -1
- package/src/pack-facts.ts +25 -2
- package/src/protocol-3.ts +5 -5
- package/src/spec-ir-client.ts +1 -1
- package/src/spec-ir-commands.ts +1 -1
- package/src/spec-ir-lines.ts +1 -1
- package/src/spec-ir-proto.ts +1 -1
- package/src/spec-patches.ts +2 -2
- package/src/tools/judge-brief.ts +1 -2
- package/src/tools/life-coverage.ts +2 -2
- package/src/tools/packs-root.ts +3 -3
- package/src/tools/score-pack.ts +1 -2
- package/src/tools/vendor-examples.ts +4 -5
- package/src/verdict.ts +1 -1
- package/src/walk/behavior-journey.ts +14 -1
- package/src/walk/journey-kit.ts +2 -2
- package/src/walk/serve-pack.ts +5 -0
- package/src/walk/shape.ts +1 -1
|
@@ -44,6 +44,12 @@ async function serve(pack, options) {
|
|
|
44
44
|
}
|
|
45
45
|
throw error;
|
|
46
46
|
}
|
|
47
|
+
// a raw-TCP pack's HTTP control seam is the side port its descriptor declares for HTTP (a mail relay's policy door),
|
|
48
|
+
// where the pack's factory names it by that side port's option
|
|
49
|
+
const declared = descriptor?.transport === 'raw-tcp' ? descriptor.sidePorts?.find((side) => (side.scheme ?? 'http') === 'http') : undefined;
|
|
50
|
+
const control = declared ? server[declared.option] : undefined;
|
|
51
|
+
if (server.inspectPort === undefined && typeof control === 'number')
|
|
52
|
+
server = { ...server, inspectPort: control };
|
|
47
53
|
if (!database)
|
|
48
54
|
return server;
|
|
49
55
|
const backing = database;
|
package/dist/src/walk/shape.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export type LineShape = {
|
|
|
69
69
|
* command-reply table reads them; moves `conn` past an intermediate reply. */
|
|
70
70
|
check: (conn: LineConnection, line: string | null, codes: number[]) => string[];
|
|
71
71
|
};
|
|
72
|
-
/** The SHAPE of a line protocol (
|
|
72
|
+
/** The SHAPE of a line protocol (the derivation, from an RFC's command-reply table): each reply a
|
|
73
73
|
* code the spec allows the command it answers, or one any command may answer. A line after an
|
|
74
74
|
* intermediate reply (DATA's payload, a SASL response) is answered as that command's `then`. */
|
|
75
75
|
export declare function lineShape(pkg: string): LineShape | undefined;
|
package/dist/src/walk/shape.js
CHANGED
|
@@ -292,7 +292,7 @@ function unitResponseOwner(pkg, manifest) {
|
|
|
292
292
|
};
|
|
293
293
|
return { pathOf, checker };
|
|
294
294
|
}
|
|
295
|
-
/** The SHAPE of a line protocol (
|
|
295
|
+
/** The SHAPE of a line protocol (the derivation, from an RFC's command-reply table): each reply a
|
|
296
296
|
* code the spec allows the command it answers, or one any command may answer. A line after an
|
|
297
297
|
* intermediate reply (DATA's payload, a SASL response) is answered as that command's `then`. */
|
|
298
298
|
export function lineShape(pkg) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-standard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.160",
|
|
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",
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"yaml": "2.9.0"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@volter/world-core": "3.0.
|
|
81
|
-
"@volter/world-runtime": "^3.0.
|
|
80
|
+
"@volter/world-core": "3.0.165",
|
|
81
|
+
"@volter/world-runtime": "^3.0.190",
|
|
82
82
|
"playwright": "^1.63.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
@@ -110,7 +110,9 @@
|
|
|
110
110
|
"openai": "^7.25.0",
|
|
111
111
|
"stripe": "^23.0.0",
|
|
112
112
|
"@google/genai": "2.13.0",
|
|
113
|
-
"@google/generative-ai": "0.24.1"
|
|
113
|
+
"@google/generative-ai": "0.24.1",
|
|
114
|
+
"nodemailer": "^10.0.6",
|
|
115
|
+
"twitter-api-v2": "^1.29.1"
|
|
114
116
|
},
|
|
115
117
|
"engines": {
|
|
116
118
|
"node": ">=22.6"
|
package/src/check-sources.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// check-sources — the evidence a derived pack cites, fetched (docs/contributing/architecture.md,
|
|
3
3
|
// "Protocol 3", Correcting the spec and The semantics layer). Every URL a state transition's `source`,
|
|
4
4
|
// a handler's `// source:` line or a spec patch's `why` names is fetched once and its answer recorded in `spec/sources.json`; a patch's
|
|
5
|
-
// `quote` is looked for in the text of the page its `source` names.
|
|
5
|
+
// `quote` is looked for in the text of the page its `source` names. the derivation reads the record offline
|
|
6
6
|
// and refuses a dead source, one never checked, and a quote its page does not hold.
|
|
7
7
|
//
|
|
8
8
|
// Documentation pages, never a vendor's API: each is fetched at most once per run, sixteen at a time,
|
|
9
9
|
// and a page already recorded as found is not fetched again (--recheck fetches everything).
|
|
10
10
|
//
|
|
11
|
-
// Usage: twin-standard check-sources <pack dir> [--recheck]
|
|
11
|
+
// Usage: twin-standard check-sources <pack dir> [--recheck]
|
|
12
12
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
14
14
|
import { unitAt } from './unit.ts';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"source": "https://www.npmjs.com/package/resend/v/6.6.0",
|
|
4
|
-
"about": "Send a synthetic sign-in email through the official SDK and inspect the retained message
|
|
4
|
+
"about": "Send a synthetic sign-in email through the official SDK and inspect the retained message after restart. Advance the frozen World clock to settle synthetic delivery before retained-state inspection.",
|
|
5
5
|
"files": {
|
|
6
6
|
"package.json": "{\n \"name\": \"resend-installed-customer\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"resend\": \"6.6.0\"\n }\n}\n",
|
|
7
7
|
".env.example": "RESEND_API_KEY=\n",
|
|
8
|
-
"inspect-email.mjs": "import assert from 'node:assert/strict';\nimport { readFileSync } from 'node:fs';\nimport { Resend } from 'resend';\n\nassert.ok(process.env.VOLTER_WORLD, 'run this script through the World');\nconst { id } = JSON.parse(readFileSync('last-email.json', 'utf8'));\nconst resend = new Resend(process.env.RESEND_API_KEY);\nconst saved = await resend.emails.get(id);\nassert.equal(saved.error, null);\nassert.equal(saved.data.last_event, 'delivered');\
|
|
8
|
+
"inspect-email.mjs": "import assert from 'node:assert/strict';\nimport { readFileSync } from 'node:fs';\nimport { Resend } from 'resend';\n\nassert.ok(process.env.VOLTER_WORLD, 'run this script through the World');\nconst { id } = JSON.parse(readFileSync('last-email.json', 'utf8'));\nconst resend = new Resend(process.env.RESEND_API_KEY);\nconst saved = await resend.emails.get(id);\nassert.equal(saved.error, null);\nassert.equal(saved.data.last_event, 'delivered');\nassert.equal(saved.data.subject, 'Your local sign-in code');\nassert.match(saved.data.text, /123456/);\nassert.match(saved.data.html, /<strong>123456<\\/strong>/);\nconsole.log('the retained email reads back with its sign-in code');\n",
|
|
9
9
|
"send-email.mjs": "import assert from 'node:assert/strict';\nimport { writeFileSync } from 'node:fs';\nimport { Resend } from 'resend';\n\nassert.ok(process.env.VOLTER_WORLD, 'run this script through the World');\nconst resend = new Resend(process.env.RESEND_API_KEY);\nconst email = {\n from: 'Example app <onboarding@resend.dev>',\n to: ['owner@world.test'],\n subject: 'Your local sign-in code',\n text: 'Your synthetic sign-in code is 123456.',\n html: '<p>Your synthetic sign-in code is <strong>123456</strong>.</p>'\n};\nconst sent = await resend.emails.send(email);\nassert.equal(sent.error, null);\nassert.ok(sent.data.id);\nconst saved = await resend.emails.get(sent.data.id);\nassert.equal(saved.error, null);\nassert.equal(saved.data.id, sent.data.id);\nassert.equal(saved.data.subject, email.subject);\nassert.equal(saved.data.text, email.text);\nassert.deepEqual(saved.data.to, email.to);\nwriteFileSync('last-email.json', JSON.stringify({ id: sent.data.id }) + '\\n');\nconsole.log('email sent and read back locally');\n"
|
|
10
10
|
},
|
|
11
11
|
"run": [
|
package/src/conformance.ts
CHANGED
|
@@ -72,9 +72,7 @@ async function credentialFields(server: Served, door: NonNullable<PackDescriptor
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** The files conformance reads from a unit: its manifest, its spec's record, its vendor's life and its decision table
|
|
75
|
-
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published())
|
|
76
|
-
* pack's standing counts it in the form only when every unit holds them (twin-world's scripts/pack-standing.ts, by
|
|
77
|
-
* unitFilesMissing): the one list of both. */
|
|
75
|
+
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published()). */
|
|
78
76
|
export const UNIT_FILES = ['src/manifest.ts', 'spec/SOURCE.md', 'journeys/customer-life.json', 'journeys/decisions.json'] as const;
|
|
79
77
|
|
|
80
78
|
/** The published package must carry every conformance input its units hold, including lanes. */
|
|
@@ -89,8 +87,6 @@ export function unpublishedUnitFiles(unit: string): string[] {
|
|
|
89
87
|
}).map((f) => `${f} is absent from package.json files (conformance reads it)`);
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
/** Each of UNIT_FILES a unit lacks where conformance reads it: the manifest of every unit, the life at its vendor's root
|
|
93
|
-
* (lifeServed), the spec's record and the decision table of a unit with a spec (decided()). */
|
|
94
90
|
const trackedJourneySources = new Map<string, string[]>();
|
|
95
91
|
/** Table ownership follows one index snapshot per checkout/run. */
|
|
96
92
|
function hasJourneySource(unit: string): boolean {
|
|
@@ -123,17 +119,6 @@ function hasJourneySource(unit: string): boolean {
|
|
|
123
119
|
return source.some(path => path.startsWith(prefix));
|
|
124
120
|
}
|
|
125
121
|
|
|
126
|
-
export function unitFilesMissing(unit: string): string[] {
|
|
127
|
-
const vendor = existsSync(join(unit, 'package.json')) ? unit : dirname(unit);
|
|
128
|
-
const spec = hasOwnSurface(unit);
|
|
129
|
-
const at: Record<(typeof UNIT_FILES)[number], string | undefined> = {
|
|
130
|
-
'src/manifest.ts': unit,
|
|
131
|
-
'spec/SOURCE.md': spec ? unit : undefined,
|
|
132
|
-
'journeys/customer-life.json': vendor,
|
|
133
|
-
'journeys/decisions.json': spec ? (hasJourneySource(unit) ? unit : vendor) : undefined,
|
|
134
|
-
};
|
|
135
|
-
return UNIT_FILES.filter((f) => at[f] !== undefined && !existsSync(join(at[f]!, f))).map((f) => `${relative(unit, join(at[f]!, f))} is missing (conformance reads it)`);
|
|
136
|
-
}
|
|
137
122
|
/** A unit's fetch export: a pack's `create<Vendor>Fetch`, a lane's `create<Lane>LaneFetch`. */
|
|
138
123
|
const fetchName = (dir: string): string => (existsSync(join(dir, 'package.json')) ? `create${pascal(basename(dir))}Fetch` : `create${pascal(basename(dir))}LaneFetch`);
|
|
139
124
|
const manifestOf = async (unit: string): Promise<Manifest> => ((await import(join(unit, 'src', 'manifest.ts'))) as { manifest: Manifest }).manifest;
|
|
@@ -259,7 +244,7 @@ async function cited(pack: string): Promise<string[]> {
|
|
|
259
244
|
const record = (readJson(join(unit, 'spec', 'sources.json')) ?? {}) as Record<string, SourceRecord>;
|
|
260
245
|
for (const [url, quotes] of await citations(unit)) {
|
|
261
246
|
const r = record[url];
|
|
262
|
-
if (!r) { out.push(`${nameOf(pack, unit)}: ${url} is cited and not recorded (
|
|
247
|
+
if (!r) { out.push(`${nameOf(pack, unit)}: ${url} is cited and not recorded (twin-standard check-sources ${nameOf(pack, unit)})`); continue; }
|
|
263
248
|
if (!sourceHeld(url, r)) out.push(`${nameOf(pack, unit)}: ${url} did not answer with verified evidence when checked`);
|
|
264
249
|
for (const q of quotes) if (r.quotes?.[q] !== true) out.push(`${nameOf(pack, unit)}: "${q.slice(0, 80)}" is not on ${url}`);
|
|
265
250
|
}
|
|
@@ -747,8 +732,8 @@ async function clientCases(pack: string, scratch: string, consumer?: ConsumerRes
|
|
|
747
732
|
Object.assign(issued, (await credentialFields(server, door, host)).outputs);
|
|
748
733
|
}
|
|
749
734
|
const job = join(scratch, 'client-job.json');
|
|
750
|
-
// a
|
|
751
|
-
//
|
|
735
|
+
// a pack carries its facts (generated/pack-facts.json, `twin-standard facts`), in source and release alike; one that
|
|
736
|
+
// has none yet has them compiled here, as that command compiles them, for the client the case runs
|
|
752
737
|
let packFacts = join(pack, 'generated', 'pack-facts.json');
|
|
753
738
|
if (!existsSync(packFacts)) {
|
|
754
739
|
const { compilePackFacts, renderPackFacts } = await import('./pack-facts.ts');
|
package/src/create-pack.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// create-pack — the files of a Protocol 3 pack that nobody should write (the board's `create-pack` card;
|
|
3
3
|
// docs/contributing/architecture.md, "Creating a pack"), and the brief an author writes the rest from.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// twin-standard create <dir> --init <vendor> --package <@scope/name>
|
|
6
|
+
// write every fixed file of a new pack that does not exist yet
|
|
7
|
+
// twin-standard create <dir> --init <vendor> --package <@scope/name> --lanes a,b
|
|
7
8
|
// the same for a pack of lanes, one per API of the vendor, each
|
|
8
9
|
// derived from its own spec (<dir>/<lane>/spec): each lane's
|
|
9
10
|
// manifest, fetch, index and semantics, and the pack's server
|
|
@@ -12,11 +13,11 @@
|
|
|
12
13
|
// index}.ts, src/semantics/{states,shared,doors}.ts,
|
|
13
14
|
// journeys/{demand,first-use}.json): the author fills the manifest's
|
|
14
15
|
// vendor facts, the descriptor's hosts and the families
|
|
15
|
-
//
|
|
16
|
+
// twin-standard create <unit> --index write src/semantics/index.ts, the handler map, and src/fetch.ts from the
|
|
16
17
|
// families' files (every exported function, by its name)
|
|
17
|
-
//
|
|
18
|
+
// twin-standard create <unit> --index --building the same during construction; unfinished operations stay gaps,
|
|
18
19
|
// and implemented operations must match their decisions
|
|
19
|
-
//
|
|
20
|
+
// twin-standard create <unit> --brief [--out f] the author's brief: every operation the derived core does not
|
|
20
21
|
// serve and no handler answers yet, by family, with what the
|
|
21
22
|
// vendored spec says of it (summary, description, parameters,
|
|
22
23
|
// the answers it documents and their examples), so a handler
|
|
@@ -69,7 +70,7 @@ export function handlerIndex(dir: string): string {
|
|
|
69
70
|
const ids = existsSync(surfacePath) ? ((parseExactJson(readFileSync(surfacePath, 'utf8')) as { operations?: Array<{ id: string }> }).operations ?? []).map((o) => o.id) : [];
|
|
70
71
|
// an index written before the derive has no operations to map, and every served call would fall to the gap (404): refuse
|
|
71
72
|
// it rather than write an empty map (2026-09-30: six packs committed so)
|
|
72
|
-
if (!existsSync(surfacePath) && fams.length) throw new Error(`${dir}: no src/generated/surface.gen.json yet; derive the unit (
|
|
73
|
+
if (!existsSync(surfacePath) && fams.length) throw new Error(`${dir}: no src/generated/surface.gen.json yet; derive the unit (twin-standard derive) before --index`);
|
|
73
74
|
const where = new Map(fams.flatMap((f) => f.handlers.map((h) => [h, f.family] as const)));
|
|
74
75
|
const named = new Set([...new Set(ids)].map(handlerName));
|
|
75
76
|
const stray = [...where.keys()].filter((h) => !named.has(h));
|
|
@@ -269,7 +270,7 @@ export async function brief(unit: string): Promise<string> {
|
|
|
269
270
|
const s = spec.get(o.id);
|
|
270
271
|
lines.push(`### ${o.id}${unmodeled.has(o.id) ? ' (unmodeled)' : ''}`, '', `\`${o.method.toUpperCase()} ${o.path}\` — ${o.class}${s?.summary ? `: ${oneLine(s.summary)}` : ''}`);
|
|
271
272
|
// the citation a handler carries, ready to paste: by operationId where the spec names one, else by its JSON pointer,
|
|
272
|
-
// quoting the spec's own words (
|
|
273
|
+
// quoting the spec's own words (the derivation refuses a quote the spec does not hold)
|
|
273
274
|
const words = oneLine(s?.summary ?? s?.description ?? '').split(/(?<=\.)\s/)[0]!.replace(/"/g, '\\"');
|
|
274
275
|
const cite = s && (s as { operationId?: string }).operationId === o.id ? `spec:${o.id}` : `spec:/paths/${o.path.replace(/~/g, '~0').replace(/\//g, '~1')}/${o.method.toLowerCase()}`;
|
|
275
276
|
if (words) lines.push('', `Cite: \`// source: ${cite} "${words}"\``);
|
|
@@ -331,7 +332,7 @@ export function packIndexFile(dir: string, vendor: string): string {
|
|
|
331
332
|
|
|
332
333
|
/** A pack's `src/server.ts` (a fixed file), the kernel's serve seam around its fetch, naming only what its unit has so
|
|
333
334
|
* it typechecks and a bundler follows every import: the plain seam (serveHttp, which takes the sockets a fetch carries
|
|
334
|
-
* itself: "Other wires"), or, for a unit whose spec is a proto, the gRPC seam over the schema
|
|
335
|
+
* itself: "Other wires"), or, for a unit whose spec is a proto, the gRPC seam over the schema the derivation generated
|
|
335
336
|
* (src/generated/proto.gen.json, imported by its literal path). `--init` writes the plain one, `--index` the gRPC one once
|
|
336
337
|
* the unit's proto is derived. */
|
|
337
338
|
export function serverFile(vendor: string, grpc: boolean, wire = false, live = false): string {
|
|
@@ -436,7 +437,7 @@ export function initFiles(vendor: string): Record<string, string> {
|
|
|
436
437
|
'src/semantics/states.ts': `// ${vendor}'s state machines and state rulings, each transition cited (architecture, "State").\nexport const states = {};\n`,
|
|
437
438
|
'src/semantics/shared.ts': `// What every family of ${vendor} shares: its stored shapes, who is calling, and the vendor's answers.\nimport type { HandlerContext } from '@volter/world-core';\n\nexport type Row = Record<string, unknown>;\nexport type { HandlerContext };\n`,
|
|
438
439
|
'src/semantics/doors.ts': `// The World's doors (architecture, "Doors, screens and the gap"): what the vendor's own pages do that its API does\n// not, each declared in the manifest's \`doors\` (its id, method and path under \`/_twin/\`) and answered here by the\n// export its id names, over the contract's context.\nimport type { HandlerContext } from '@volter/world-core';\n\nexport type { HandlerContext };\n`,
|
|
439
|
-
'src/manifest.ts': `// ${vendor}'s manifest: the vendor facts its spec does not carry (architecture, "Protocol 3"). The surface is generated\n// (./generated/surface.gen.json, from ../spec by derive
|
|
440
|
+
'src/manifest.ts': `// ${vendor}'s manifest: the vendor facts its spec does not carry (architecture, "Protocol 3"). The surface is generated\n// (./generated/surface.gen.json, from ../spec by twin-standard derive). Every line marked FILL is a vendor fact to state from\n// the vendor's pages, cited.\nimport type { DerivedManifest } from '@volter/world-core';\n\nexport const manifest: DerivedManifest = {\n vendor: '${vendor}',\n service: '${vendor}',\n body: { json: 'always' },\n ids: { template: '{uuid}' }, // FILL: the vendor's id form\n time: 'iso', // FILL: iso, unix or unix-ms\n error: { error: { message: '{message}', code: '{code}' } }, // FILL: the vendor's error body\n readOnly: { status: 403, message: 'This twin was started read-only; writes are refused.' }, // FILL: the vendor's refusal\n notFound: { status: 404, message: 'Not found' }, // FILL: the vendor's not-found\n list: { style: 'envelope', envelope: { data: '{data}' }, limit: { param: 'limit', default: 20, max: 100 } }, // FILL: its paging\n deleted: {},\n resources: {}, // FILL: the resources the core serves (plain create, read, update, delete, list)\n discovery: { twinOf: 'FILL: what the twin is of', stores: 'FILL: what it keeps' },\n // the descriptor, as data (twin-world's architecture, "The descriptor"): the pack registers packOf(manifest)\n descriptor: {\n protocol: '3',\n transport: 'rest',\n archetype: 'crud',\n bin: 'world-${vendor}',\n resources: [], // FILL: the stored types\n specSource: 'FILL: the vendored spec (spec/SOURCE.md)',\n description: 'FILL: one line',\n adoption: { sdks: [], envStems: [] }, // FILL: its npm clients, and each credential variable's stem (TRIPO for TRIPO_API_KEY: the suffix removed)\n hosts: [], // FILL: the vendor's hosts\n endpointEnvNone: 'FILL: the SDK\\'s base-URL variable (endpointEnv) or why none',\n },\n};\n`,
|
|
440
441
|
'src/server.ts': serverFile(vendor, false),
|
|
441
442
|
'src/cli.ts': cliFile(vendor),
|
|
442
443
|
'src/index.ts': `// @volter/twin-${vendor} — the ${vendor} twin (Protocol 3). A fixed file: the descriptor is the manifest's.\nimport { packOf, registerPack } from '@volter/world-core';\nimport { manifest } from './manifest.ts';\n\nexport { create${Name}Fetch } from './fetch.ts';\nexport { create${Name}TwinServer } from './server.ts';\nexport { manifest };\n\n// The descriptor: twin-world's architecture, "The descriptor"; declared in manifest.ts.\nexport const pack = registerPack(packOf(manifest));\n`,
|
|
@@ -515,7 +516,7 @@ export async function fixedFiles(dir: string): Promise<Record<string, string>> {
|
|
|
515
516
|
if (!vendor || !/^[a-z][a-z0-9-]*$/.test(vendor)) throw new Error('Pack manifest must declare its vendor identity independently of the npm package name');
|
|
516
517
|
files['src/index.ts'] = packIndexFile(dir, vendor);
|
|
517
518
|
if (!existsSync(join(dir, 'src', 'stream.ts'))) {
|
|
518
|
-
// a unit whose spec is a proto (
|
|
519
|
+
// a unit whose spec is a proto (the derivation generated its schema) is served over gRPC: its serve seam in that form
|
|
519
520
|
const lanesOnly = !hasOwnSurface(dir) && readdirSync(dir).some((n) => existsSync(join(dir, n, 'src', 'manifest.ts')));
|
|
520
521
|
const grpc = !lanesOnly && existsSync(join(dir, 'src', 'generated', 'proto.gen.json'));
|
|
521
522
|
// a unit that takes broadcasts over RTMP (its manifest's `live`) has the live forms --index writes, so they reproduce
|
|
@@ -529,7 +530,7 @@ export async function fixedFiles(dir: string): Promise<Record<string, string>> {
|
|
|
529
530
|
export async function createPack(args: string[]): Promise<void> {
|
|
530
531
|
const [unit, ...rest] = args;
|
|
531
532
|
if (!unit || (!rest.includes('--index') && !rest.includes('--brief') && !rest.includes('--init'))) {
|
|
532
|
-
console.error('usage:
|
|
533
|
+
console.error('usage: twin-standard create <dir> --init <vendor> --package <@scope/name> | <unit> --index [--building] | --brief [--out <file>]');
|
|
533
534
|
process.exit(2);
|
|
534
535
|
}
|
|
535
536
|
if (rest.includes('--building') && (!rest.includes('--index') || rest.includes('--init') || rest.includes('--brief'))) {
|
|
@@ -598,7 +599,7 @@ export async function createPack(args: string[]): Promise<void> {
|
|
|
598
599
|
console.log(`create-pack: wrote ${join(dir, 'src', 'index.ts')}`);
|
|
599
600
|
// what the grade's typecheck compiles: a pack made before init wrote it gets it here
|
|
600
601
|
if (!existsSync(join(dir, 'tsconfig.json'))) { writeFileSync(join(dir, 'tsconfig.json'), TSCONFIG); console.log(`create-pack: wrote ${join(dir, 'tsconfig.json')}`); }
|
|
601
|
-
// a unit whose spec is a proto (
|
|
602
|
+
// a unit whose spec is a proto (the derivation generated its schema) is served over gRPC: its serve seam in that form,
|
|
602
603
|
// the one form a unit takes after --init (a server.ts of a wire of the pack's own, SMTP's, is never written over)
|
|
603
604
|
if (!lanesOnly && existsSync(join(dir, 'src', 'generated', 'proto.gen.json'))) {
|
|
604
605
|
const wire = existsSync(join(dir, 'src', 'stream.ts'));
|
package/src/derive.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { parseExactJson, exactInteger } from '@volter/world-core/exact-json';
|
|
|
5
5
|
// vendored spec (`<pack>/spec/openapi.json[.gz]`), builds the spec IR (./spec-ir.ts) and writes the pack's
|
|
6
6
|
// `src/generated/`. Deterministic: a second run over the same spec writes the same bytes. Never hand-edit what it writes.
|
|
7
7
|
//
|
|
8
|
-
// Usage: twin-standard derive <pack dir> [--out <dir>]
|
|
8
|
+
// Usage: twin-standard derive <pack dir> [--out <dir>]
|
|
9
9
|
|
|
10
10
|
import { parse as parseYaml } from 'yaml';
|
|
11
11
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -41,7 +41,7 @@ const PKG = unitAt(vendor).dir;
|
|
|
41
41
|
const rfc = existsSync(join(PKG, 'spec')) ? readdirSync(join(PKG, 'spec')).find((f) => /^rfc\d+\.txt$/.test(f)) : undefined;
|
|
42
42
|
const specPath = ['openapi.json.gz', 'openapi.json', 'openapi.yaml.gz', 'openapi.yaml', 'smithy.json.gz', 'smithy.json', 'discovery.json.gz', 'discovery.json', ...(rfc ? [rfc] : []), 'openapi', 'proto', 'commands', 'command-builders.json', 'client-ops.json'].map((f) => join(PKG, 'spec', f)).find((p) => existsSync(p));
|
|
43
43
|
if (!specPath) {
|
|
44
|
-
console.error(`derive
|
|
44
|
+
console.error(`derive: no spec at ${join(PKG, 'spec')}/openapi.{json,yaml}[.gz], smithy.json[.gz], rfcNNNN.txt, proto/, commands/, command-builders.json or client-ops.json`);
|
|
45
45
|
process.exit(SOURCE_REFUSAL);
|
|
46
46
|
}
|
|
47
47
|
const rpcService = basename(specPath) === 'proto';
|
|
@@ -308,7 +308,7 @@ if (existsSync(manifestPath)) {
|
|
|
308
308
|
}
|
|
309
309
|
// a resource's refresh scope ("The real-system adapters", What a pack declares) names the list that enumerates it or
|
|
310
310
|
// its retrieve, or says why the vendor offers no read-back; whether every stored resource declares one is
|
|
311
|
-
//
|
|
311
|
+
// not this build's to decide
|
|
312
312
|
// (a resource the spec names is read by its own list or retrieve; one the manifest names apart from the spec's, an RPC
|
|
313
313
|
// vendor's, by the list or read the scope names, with `items` where the answer holds them when the spec does not say)
|
|
314
314
|
const specNamed = new Set(ir.resources.map((r) => r.name));
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// of a submission — judges by the same standard at the version it pins. Changing a criterion is a release of this
|
|
5
5
|
// package: a criterion's id never changes meaning, a changed criterion takes a new id and a new standard version.
|
|
6
6
|
export { grade, STANDARDS, type CriterionGrade, type GradeOptions, type StandardGrade } from './grade.ts';
|
|
7
|
-
export { conformance, decisionTableProblems, type ConformanceResult, UNIT_FILES
|
|
7
|
+
export { conformance, decisionTableProblems, type ConformanceResult, UNIT_FILES } from './conformance.ts';
|
|
8
8
|
export { laneRootFailures, p3Gate, unitsOf } from './gate.ts';
|
|
9
9
|
export { conformPublished, gatePublished, vendorOfPackage, type PublishedGate } from './published.ts';
|
|
10
10
|
export { handlerName, protocol3, PLANNED, withCheckoutFiles } from './protocol-3.ts';
|
package/src/pack-facts.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type PackFacts = {
|
|
|
8
8
|
serverBacking?: { command: string };
|
|
9
9
|
maxRequestBodySize?: number;
|
|
10
10
|
adoption?: { sdks?: string[]; pypi?: string[]; scopes?: string[]; envStems?: string[]; worldIds?: string[]; tools?: Array<{ package: string; usage: 'build' | 'deployment' }>; configFiles?: Array<{ file: string; usage: 'application' | 'build' | 'deployment'; when?: string }> };
|
|
11
|
-
hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; demandPathPattern?: string | false; key?: string; exclude?: true }>;
|
|
11
|
+
hosts?: Array<{ host?: string; suffix?: string; hostPattern?: string; pathPattern?: string; demandPathPattern?: string | false; key?: string; exclude?: true; basic?: string }>;
|
|
12
12
|
hostsNone?: string;
|
|
13
13
|
hostsClaimed?: { door: string; note: string };
|
|
14
14
|
/** The subject types the twin projects (packRegistry `resources`) — invariant R2 at the resource
|
|
@@ -42,6 +42,8 @@ export type PackFacts = {
|
|
|
42
42
|
managedDatabase?: { kind: 'postgres'; arg: string; user: string; database: string; clock: 'wall'; credentialField: string; note: string };
|
|
43
43
|
/** Listeners the twin opens beside its main port (see TwinPack.sidePorts). */
|
|
44
44
|
sidePorts?: Array<{ name: string; option: string; flag: string; path?: string; scheme?: string; note: string }>;
|
|
45
|
+
/** The vendor's own requests a World's generated seed makes (see TwinPack.seed). */
|
|
46
|
+
seed?: TwinPack['seed'];
|
|
45
47
|
/** DERIVED, never declared: the pack's `create<Name>TwinServer` factory export — the
|
|
46
48
|
* `({port,root,readOnly}) => {port,stop}` contract the colocated host mounts
|
|
47
49
|
* (world-runtime/src/host.ts). Read off the module's own exports because the name is not
|
|
@@ -78,6 +80,7 @@ function validateHostRules(vendor: string, rules: NonNullable<PackFacts['hosts']
|
|
|
78
80
|
try { new RegExp(rule.demandPathPattern); } catch (e) { throw new Error(`pack-facts: ${vendor} demandPathPattern does not compile: ${(e as Error).message}`); }
|
|
79
81
|
}
|
|
80
82
|
if (rule.exclude !== undefined && rule.exclude !== true) throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — exclude is \`true\` or absent, never a falsy flag`);
|
|
83
|
+
if (rule.basic !== undefined && (typeof rule.basic !== 'string' || !/^[^:\s]+$/.test(rule.basic) || rule.exclude)) throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — basic names a user (no colon, no space) on an included host`);
|
|
81
84
|
const key = rule.key ?? vendor;
|
|
82
85
|
// A foreign key is a WORLD identity too: `volter-world init` emits <KEY>_TWIN_URL only for the
|
|
83
86
|
// ids in adoption.worldIds (covers.ts injectorVendorKeysFor), so a key not listed there would
|
|
@@ -221,6 +224,26 @@ export async function compilePackFacts(dirs: Array<{ vendor: string; dir: string
|
|
|
221
224
|
}
|
|
222
225
|
facts.sidePorts = pack.sidePorts;
|
|
223
226
|
}
|
|
227
|
+
if (pack.seed !== undefined) {
|
|
228
|
+
// ADR 0033 §3: one door per kind of fact, each the vendor's own request at its own host; only a domain's DNS record
|
|
229
|
+
// is a `/_twin/` stand-in
|
|
230
|
+
const seed = pack.seed as Record<string, unknown>;
|
|
231
|
+
const door = (value: unknown, what: string, standIn = false): void => {
|
|
232
|
+
let url: URL;
|
|
233
|
+
try { url = new URL(String(value)); } catch { throw new Error(`pack-facts: ${vendor} seed ${what} "${String(value)}" is not a URL`); }
|
|
234
|
+
if (url.protocol !== 'https:') throw new Error(`pack-facts: ${vendor} seed ${what} is the vendor's own https request`);
|
|
235
|
+
if (!standIn && url.pathname.startsWith('/_twin/')) throw new Error(`pack-facts: ${vendor} seed ${what} is the vendor's own request, never a /_twin/ door`);
|
|
236
|
+
};
|
|
237
|
+
if (!seed || typeof seed !== 'object' || Array.isArray(seed) || Object.keys(seed).some((k) => k !== 'signUp' && k !== 'domain')) throw new Error(`pack-facts: ${vendor} seed is { signUp?, domain? }`);
|
|
238
|
+
if (seed.signUp !== undefined) door(seed.signUp, 'signUp');
|
|
239
|
+
if (seed.domain !== undefined) {
|
|
240
|
+
const domain = seed.domain as Record<string, unknown>;
|
|
241
|
+
if (!domain || typeof domain !== 'object' || Array.isArray(domain) || Object.keys(domain).sort().join() !== 'add,record,settles,verify') throw new Error(`pack-facts: ${vendor} seed.domain is { add, record, verify, settles }`);
|
|
242
|
+
door(domain.add, 'domain.add'); door(domain.verify, 'domain.verify'); door(domain.record, 'domain.record', true);
|
|
243
|
+
if (typeof domain.settles !== 'string' || !/^\d+[smhd]$/.test(domain.settles)) throw new Error(`pack-facts: ${vendor} seed.domain.settles is a World-clock time (10m)`);
|
|
244
|
+
}
|
|
245
|
+
facts.seed = pack.seed;
|
|
246
|
+
}
|
|
224
247
|
// The serve factory is DERIVED from the module's exports (see PackFacts.serveExport);
|
|
225
248
|
// a pack exporting SEVERAL factories must declare which one `cli.ts serve` boots — the
|
|
226
249
|
// exports alone cannot reveal that judgment (linear defaults to its DERIVED server).
|
|
@@ -240,7 +263,7 @@ export async function compilePackFacts(dirs: Array<{ vendor: string; dir: string
|
|
|
240
263
|
if (Object.keys(facts).length > 0) packs[vendor] = facts;
|
|
241
264
|
}
|
|
242
265
|
return {
|
|
243
|
-
'//': 'GENERATED by
|
|
266
|
+
'//': 'GENERATED by twin-standard facts from the pack descriptor; no prior-artifact entries are retained.',
|
|
244
267
|
packs: sortObject(packs),
|
|
245
268
|
undeclared,
|
|
246
269
|
};
|
package/src/protocol-3.ts
CHANGED
|
@@ -171,7 +171,7 @@ function noReport(unit: GradeUnit): string {
|
|
|
171
171
|
/** An operation's family: the first segment of its path after a version (`/v1/organizations/{id}` is `organizations`), a
|
|
172
172
|
* dotted RPC method's first part (`chat.postMessage` is `chat`), a line command's name (architecture, "Pack layout"). */
|
|
173
173
|
function familyOf(path: string): string {
|
|
174
|
-
// the interim rule ("What an author writes"), until
|
|
174
|
+
// the interim rule ("What an author writes"), until the derivation writes the spec's own grouping on each operation
|
|
175
175
|
const segs = path.split('/').filter((x) => x && !/^v?\d+(\.\d+)?$|^\d{4}-\d{2}-\d{2}$/.test(x) && !x.startsWith('{'));
|
|
176
176
|
if (!segs.length) return 'operations'; // named in a header: every path is `/`
|
|
177
177
|
const first = segs[0]!;
|
|
@@ -262,7 +262,7 @@ const criteria: Criterion[] = [
|
|
|
262
262
|
},
|
|
263
263
|
{
|
|
264
264
|
id: 'derived-reproducible', section: 'form',
|
|
265
|
-
asks: '
|
|
265
|
+
asks: 'twin-standard derive regenerates src/generated/ byte for byte',
|
|
266
266
|
check(unit) {
|
|
267
267
|
const gen = join(unit.dir, 'src', 'generated');
|
|
268
268
|
if (!existsSync(gen)) return fail('src/generated/ is missing');
|
|
@@ -279,9 +279,9 @@ const criteria: Criterion[] = [
|
|
|
279
279
|
// the pack's own path and the scratch directory are this checkout's, not the pack's: the verdict names neither, so
|
|
280
280
|
// a standing written from one checkout reads the same from any other
|
|
281
281
|
if (run.status === null || run.signal !== null || run.error) return { pass: false, cacheable: false,
|
|
282
|
-
reason: `derive
|
|
283
|
-
if (run.status !== 0) return { ...fail(`derive
|
|
284
|
-
return changed.length ? fail(`derive
|
|
282
|
+
reason: `derive interrupted: status=${run.status} signal=${run.signal} error=${run.error?.message ?? ''}` };
|
|
283
|
+
if (run.status !== 0) return { ...fail(`derive refused: ${String(run.stderr || run.stdout).split(unit.dir).join('.').split(tmp).join('<out>').trim().split('\n').slice(-2).join(' ').slice(0, 200)}`), cacheable: run.status === 2 };
|
|
284
|
+
return changed.length ? fail(`derive writes differently: ${changed.slice(0, 5).join(', ')}${changed.length > 5 ? ` and ${changed.length - 5} more` : ''}`) : pass(`${filesUnder(gen).length} files reproduced`);
|
|
285
285
|
},
|
|
286
286
|
},
|
|
287
287
|
{
|
package/src/spec-ir-client.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `spec/`, Creating a pack, step 2): its method, its URL as the client
|
|
7
7
|
// builds it (a label is the Python expression the client interpolates), the query keys it sends (literal, or the keys
|
|
8
8
|
// of the dict it urlencodes) and its body's keys, each typed by the client's own annotation of the value (`null`
|
|
9
|
-
// where the client does not say). A spec patch corrects the table as it corrects a JSON spec (derive
|
|
9
|
+
// where the client does not say). A spec patch corrects the table as it corrects a JSON spec (twin-standard derive): what the
|
|
10
10
|
// client builds as a raw string (a token's scopes) comes from the vendor's reference page that way.
|
|
11
11
|
//
|
|
12
12
|
// The calls at one method and path are one operation, their query and body keys together. The client says nothing
|
package/src/spec-ir-commands.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// its group, the version it arrived in, its arity, its arguments, its flags and its reply's schema); a pack whose wire
|
|
4
4
|
// carries Redis commands (Upstash's REST API: a command and its arguments in the path or the body) vendors the files of
|
|
5
5
|
// the commands its vendor serves and derives its command surface from them, as a line protocol's surface is its RFC's
|
|
6
|
-
// command table (spec-ir-lines.ts). A spec patch corrects the data as it corrects a JSON spec (derive
|
|
6
|
+
// command table (spec-ir-lines.ts). A spec patch corrects the data as it corrects a JSON spec (twin-standard derive).
|
|
7
7
|
|
|
8
8
|
export type CommandArgument = { name: string; type: string; optional?: boolean; multiple?: boolean; token?: string; arguments?: CommandArgument[] };
|
|
9
9
|
/** A command: `id` as a client names it (`SET`, `CLIENT GETNAME` for a container's subcommand); `arity` as Redis gives
|
package/src/spec-ir-lines.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A line protocol's spec as data: the commands a client sends and the replies each may be answered,
|
|
2
2
|
// read from the RFC's own table of them. The IR a byte-stream pack derives its surface from, as an HTTP
|
|
3
|
-
// pack derives its from an OpenAPI document; a spec patch corrects it the same way (derive
|
|
3
|
+
// pack derives its from an OpenAPI document; a spec patch corrects it the same way (twin-standard derive).
|
|
4
4
|
//
|
|
5
5
|
// SMTP's table is RFC 5321 §4.3.2 "Command-Reply Sequences": each command, then its intermediate (I),
|
|
6
6
|
// success (S) and error (E) codes, where `I: 354 -> data -> S: 250` says what answers the lines that
|
package/src/spec-ir-proto.ts
CHANGED
|
@@ -161,7 +161,7 @@ export function parseProto(path: string, text: string): ProtoFile {
|
|
|
161
161
|
return file;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
/** The vendored tree (path → text) as data, so a spec patch corrects it as it corrects a JSON spec (derive
|
|
164
|
+
/** The vendored tree (path → text) as data, so a spec patch corrects it as it corrects a JSON spec (twin-standard derive). */
|
|
165
165
|
export type ProtoSpec = { format: 'proto'; files: ProtoFile[] };
|
|
166
166
|
export function readProtoFiles(files: Record<string, string>): ProtoSpec {
|
|
167
167
|
return { format: 'proto', files: Object.entries(files).sort(([a], [b]) => a.localeCompare(b)).map(([path, text]) => parseProto(path, text)) };
|
package/src/spec-patches.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A pack's corrections to its vendor's spec (spec/patches.json): RFC 6902 operations (add, replace, remove, move, copy),
|
|
2
|
-
// applied to the parsed spec so the vendored file stays the vendor's own bytes.
|
|
3
|
-
// IR;
|
|
2
|
+
// applied to the parsed spec so the vendored file stays the vendor's own bytes. the derivation applies them before the
|
|
3
|
+
// IR; `twin-standard examples` reads the patched schemas, which are what the twin is derived from.
|
|
4
4
|
|
|
5
5
|
export type SpecPatch = { op: string; path: string; from?: string; value?: unknown };
|
|
6
6
|
|
package/src/tools/judge-brief.ts
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// runtime). The verdict names its judge and the authors; the grade refuses one whose judge is an author
|
|
4
4
|
// (packages/twin-standard/src/verdict.ts). One judging per pack: the verdict stands through later changes.
|
|
5
5
|
//
|
|
6
|
-
// twin-standard judge-brief <vendor> --authors <author name>[,<agent>...] (from the pack repository's root
|
|
7
|
-
// bun scripts/judge-brief.ts runs the same)
|
|
6
|
+
// twin-standard judge-brief <vendor> --authors <author name>[,<agent>...] (from the pack repository's root)
|
|
8
7
|
import { attributionName } from '../verdict.ts';
|
|
9
8
|
import { createHash } from 'node:crypto';
|
|
10
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
// A walk that fails, and a file in scope (the pack's src/ on disk, its sharing lanes' too) that declares code yet
|
|
25
25
|
// measures no line, stop the measurement: neither is a file with nothing to reach.
|
|
26
26
|
//
|
|
27
|
-
// Usage: twin-standard coverage <vendor> [--write | --out <file>] [--prune] (from the pack repository's root
|
|
28
|
-
//
|
|
27
|
+
// Usage: twin-standard coverage <vendor> [--write | --out <file>] [--prune] (from the pack repository's root)
|
|
28
|
+
// (--prune removes the stale records) (--write keeps journeys/coverage.json)
|
|
29
29
|
import { spawnSync } from 'node:child_process';
|
|
30
30
|
import ts from 'typescript';
|
|
31
31
|
import { existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
package/src/tools/packs-root.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Where the contributor's tools (walk, examples, coverage, judge-brief) find the pack they are given: a vendor's directory
|
|
2
2
|
// under the pack repository they run from. A contributor runs them from their own repository's root (`twin-standard walk
|
|
3
|
-
// <vendor>`), so that root is the working directory;
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// <vendor>`), so that root is the working directory; a tool that runs another in a process of its own (run-tool.ts)
|
|
4
|
+
// names it before loading the tool (setPacksRoot), with the kernel checkout a walk must measure (setKernelCheckout).
|
|
5
|
+
// Each tool reads both once, when it loads.
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
|
|
8
8
|
let packs = process.cwd();
|
package/src/tools/score-pack.ts
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
// This is source-journey evidence, not installed readiness or pack completion. pack-done owns completion and consumes
|
|
13
13
|
// this report alongside the exact artifact's installed customer assessment in the one final run.
|
|
14
14
|
//
|
|
15
|
-
// Usage: twin-standard walk <vendor> [--write] [--report <path>] (from the pack repository's root
|
|
16
|
-
// bun scripts/score-pack.ts <vendor> runs the same)
|
|
15
|
+
// Usage: twin-standard walk <vendor> [--write] [--report <path>] (from the pack repository's root)
|
|
17
16
|
// --write keeps journeys/score.json and journeys/coverage.json; --report writes the report and what it lacks for done
|
|
18
17
|
// to <path> as JSON and changes nothing in the checkout
|
|
19
18
|
|
|
@@ -35,8 +35,7 @@
|
|
|
35
35
|
// read for. `--sweep` prints, without changing done, every actor field (creator, user, *_by, ...) no `equals` pins and every
|
|
36
36
|
// list no expect closes: advice for fixing a ruling as a class (its hits include lists a pinned text already fixes).
|
|
37
37
|
//
|
|
38
|
-
// Usage: twin-standard examples <vendor> [--sweep] (from the pack repository's root
|
|
39
|
-
// bun scripts/vendor-examples.ts <vendor> runs the same)
|
|
38
|
+
// Usage: twin-standard examples <vendor> [--sweep] (from the pack repository's root)
|
|
40
39
|
import { parseExactJson, exactInteger, isExactIntegerValue } from '@volter/world-core/exact-json';
|
|
41
40
|
import { hasOwnSurface } from '../lanes.ts';
|
|
42
41
|
import { archiveSource } from '../archive-source.ts';
|
|
@@ -95,7 +94,7 @@ function answerKeys(output: unknown, published: unknown, sent: unknown, placehol
|
|
|
95
94
|
return mapped(output);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
/** The schemas the twin serves: the vendor's, with the pack's spec patches (spec/patches.json) applied as
|
|
97
|
+
/** The schemas the twin serves: the vendor's, with the pack's spec patches (spec/patches.json) applied as the derivation
|
|
99
98
|
* applies them. A sample is checked against what the twin is derived from; the published examples themselves stay the
|
|
100
99
|
* vendor's bytes. */
|
|
101
100
|
function served<T extends object>(pkg: string, doc: T): T {
|
|
@@ -376,7 +375,7 @@ function fromSpec(pkg: string, lane: string | undefined, model: SmithyModel | un
|
|
|
376
375
|
}
|
|
377
376
|
return out;
|
|
378
377
|
}
|
|
379
|
-
// a spec vendored as published or gzipped, as
|
|
378
|
+
// a spec vendored as published or gzipped, as the derivation reads it
|
|
380
379
|
const spec = ['openapi.json.gz', 'openapi.json', 'openapi.yaml.gz', 'openapi.yaml', 'discovery.json.gz', 'discovery.json'].map((f) => join(pkg, 'spec', f)).find((p) => existsSync(p));
|
|
381
380
|
// a Protocol Buffers service publishes no examples in its .proto files: its examples are its documentation's alone
|
|
382
381
|
if (!spec && existsSync(join(pkg, 'spec', 'proto'))) return [];
|
|
@@ -390,7 +389,7 @@ function fromSpec(pkg: string, lane: string | undefined, model: SmithyModel | un
|
|
|
390
389
|
const text = spec ? (spec.endsWith('.gz') ? gunzipSync(readFileSync(spec)) : readFileSync(spec)).toString('utf8') : '';
|
|
391
390
|
type Op = { operationId?: string; responses?: Record<string, Record<string, unknown>>; 'x-oaiMeta'?: { examples?: unknown } };
|
|
392
391
|
type Doc = { paths: Record<string, Record<string, Op>>; components?: { examples?: Record<string, { value?: unknown }>; schemas?: Record<string, SpecSchema> }; definitions?: Record<string, SpecSchema> };
|
|
393
|
-
// a Google API Discovery document publishes no examples; it is read as the OpenAPI document
|
|
392
|
+
// a Google API Discovery document publishes no examples; it is read as the OpenAPI document the derivation reads it as,
|
|
394
393
|
// its patches applied to the Discovery document first (spec-ir-discovery.ts)
|
|
395
394
|
const discovery = spec !== undefined && /discovery\.json(\.gz)?$/.test(spec);
|
|
396
395
|
const docs = (documents ? Object.values(documents.documents) : [discovery ? discoveryToOpenAPI(parseExactJson(text)) : /\.yaml(\.gz)?$/.test(spec!) ? parseExactJson(JSON.stringify(parseYaml(text, { intAsBigInt: true }), (_key, value) => typeof value === 'bigint' ? exactInteger(String(value)) : value)) : parseExactJson(text)]) as Doc[];
|
package/src/verdict.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A JUDGE'S INDEPENDENCE (the grade's outline-judged and life-judged, the published examples' verdict): a verdict rules
|
|
2
2
|
// only when it names its judge (`judge.agent`) and the authors of what it judges (`authors`), and the judge is none of
|
|
3
3
|
// them. The dispatcher stamps author names consistently; a judge started independently
|
|
4
|
-
// (
|
|
4
|
+
// (`twin-standard judge-brief` prints its brief) names its reviewer role (`judge.name`). One judging per pack: the
|
|
5
5
|
// verdict stands, and later changes to what it judged keep it (architecture, "Writing a life", Judge).
|
|
6
6
|
|
|
7
7
|
/** Committed attribution names people or author roles, never execution identities. */
|
|
@@ -14,7 +14,7 @@ import { createRequire } from 'node:module';
|
|
|
14
14
|
import { gzipSync } from 'node:zlib';
|
|
15
15
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
-
import { databaseSession, type DatabaseSession, getActivePackAssets, getActiveWorldStore, LocalPackAssets, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActivePackAssets, setActiveWorldStore, setDeliveryObserver, setInProcessTwins, settleEventDeliveries, worldDoorMatcher, worldNow, type InProcessTwin, type TwinStream, type TwinStreamConnection } from '@volter/world-core/runtime';
|
|
17
|
+
import { databaseSession, type DatabaseSession, getActivePackAssets, getActiveWorldStore, LocalPackAssets, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActivePackAssets, setActiveWorldStore, setDeliveryObserver, setInProcessMailServers, setInProcessTwins, settleEventDeliveries, worldDoorMatcher, worldNow, type InProcessTwin, type TwinStream, type TwinStreamConnection } from '@volter/world-core/runtime';
|
|
18
18
|
import { createHostTable, type HostFacts } from '@volter/world-core/host-routing';
|
|
19
19
|
import packFactsDocument from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
|
|
20
20
|
import { type Captured, type GitStep, type Journey, type JourneyStep, type LineStep, type SocketStep, type SqlStep, type RespStep, type MysqlStep, type MongoStep, isMongo, isMysql, isResp, at, captureAt, echoFailures, expandFills, expectFailures, fill, fillJson, formBody, isGit, isLine, isSql, isSocket, isWait, jwtAuthorization, oauth1Authorization, presignedPath, sigv4Headers, waitMs } from './journey-kit.ts';
|
|
@@ -237,6 +237,17 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
237
237
|
if (!companion || !keys.length) { collect.push({ step: '(setup)', beat: 'ADAPTER', detail: vendor === journey.vendor ? `the life's own vendor ${vendor} is no companion of it` : !companion ? `no create<Name>Fetch adapter for the companion ${vendor}` : `the companion ${vendor} declares no host rules (pack facts) a call could reach it by` }); return {}; }
|
|
238
238
|
companions.push({ vendor, keys, fetch: companion(root, { application }), databaseDoor: companion.managedDatabaseDoor });
|
|
239
239
|
}
|
|
240
|
+
// a companion that receives mail (an SMTP side port: gmail's, gmail.com's MX) is the mail server ctx.mail reaches for
|
|
241
|
+
// the domains its host rules claim, on its byte-stream door in process over this walk's World, as a World reaches its
|
|
242
|
+
// listener: the life declares the provider of every address it mails
|
|
243
|
+
const mailServers: Array<{ vendor: string; receives: (domain: string) => boolean; stream: TwinStream }> = [];
|
|
244
|
+
for (const c of companions) {
|
|
245
|
+
const sides = (walkFacts[c.vendor.split('/')[0]!] as { sidePorts?: Array<{ scheme?: string }> } | undefined)?.sidePorts ?? [];
|
|
246
|
+
if (!sides.some((side) => side.scheme === 'smtp')) continue;
|
|
247
|
+
const stream = await vendorStream(packsDir(), c.vendor, dependenciesDir());
|
|
248
|
+
if (!stream) { collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `the companion ${c.vendor} receives mail on an smtp side port and exports no create<Name>TwinStream` }); return {}; }
|
|
249
|
+
mailServers.push({ vendor: c.vendor, receives: (domain) => c.keys.some((key) => walkHosts[key]!(domain, '/')), stream: stream(root, { application }) });
|
|
250
|
+
}
|
|
240
251
|
const eventFields = new Map<string, Set<string>>();
|
|
241
252
|
for (const vendor of [journey.vendor, ...companions.map((companion) => companion.vendor)]) {
|
|
242
253
|
const directory = directoryFor(vendor);
|
|
@@ -735,6 +746,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
735
746
|
...companions.flatMap((c) => c.keys.map((key) => [key, c.fetch] as const)),
|
|
736
747
|
...ownKeys.map((key) => [key, twin] as const),
|
|
737
748
|
]));
|
|
749
|
+
const previousMailServers = setInProcessMailServers(mailServers);
|
|
738
750
|
try {
|
|
739
751
|
for (const step of journey.steps) {
|
|
740
752
|
await settleEventDeliveries(root);
|
|
@@ -938,6 +950,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
938
950
|
} finally {
|
|
939
951
|
setDeliveryObserver(previousObserver);
|
|
940
952
|
setInProcessTwins(previousTwins);
|
|
953
|
+
setInProcessMailServers(previousMailServers);
|
|
941
954
|
try {
|
|
942
955
|
if (sqlSession) await sqlSession.then((session) => session.end(), () => {});
|
|
943
956
|
} finally {
|