@volter/twin-standard 1.0.32 → 1.0.34

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.
@@ -0,0 +1,117 @@
1
+ export type PackFacts = {
2
+ /** Explicit vendor-server backing; this entry declares no pack protocol or factory. */
3
+ serverBacking?: {
4
+ command: string;
5
+ };
6
+ maxRequestBodySize?: number;
7
+ adoption?: {
8
+ sdks?: string[];
9
+ pypi?: string[];
10
+ scopes?: string[];
11
+ envStems?: string[];
12
+ worldIds?: string[];
13
+ tools?: Array<{
14
+ package: string;
15
+ usage: 'build' | 'deployment';
16
+ }>;
17
+ configFiles?: Array<{
18
+ file: string;
19
+ usage: 'application' | 'build' | 'deployment';
20
+ when?: string;
21
+ }>;
22
+ };
23
+ hosts?: Array<{
24
+ host?: string;
25
+ suffix?: string;
26
+ hostPattern?: string;
27
+ pathPattern?: string;
28
+ demandPathPattern?: string | false;
29
+ key?: string;
30
+ exclude?: true;
31
+ }>;
32
+ hostsNone?: string;
33
+ hostsClaimed?: {
34
+ door: string;
35
+ note: string;
36
+ };
37
+ /** The subject types the twin projects (packRegistry `resources`) — invariant R2 at the resource
38
+ * level checks a replay creates every one of them. */
39
+ resources?: string[];
40
+ /** The wire transport class (packRegistry PackTransport) — the invariant matrix keys
41
+ * variants off it too (a raw-tcp twin has no workerd fetch path, so R12b is n/a). */
42
+ transport?: string;
43
+ /** Optional native frontend of the same state owner, selected explicitly on the pack CLI. */
44
+ /** The serve-family (packRegistry PackArchetype) — the axis the invariant matrix keys
45
+ * strictness off. Closed enum, REQUIRED: every pack declares one (the catalog was
46
+ * fully classified 2026-09-02; the scaffolder's A1 TODO makes a new pack's
47
+ * ratification unskippable because an undeclared archetype is RED here). */
48
+ archetype?: string;
49
+ /** R16: the protocol major the package targets and its standing against this kernel. */
50
+ protocol?: {
51
+ declared?: string;
52
+ major: number;
53
+ standing: 'current' | 'deprecated' | 'refused' | 'assumed';
54
+ };
55
+ /** R20: pack-shipped asset paths (relative to the pack dir) the serve path reads through the seam. */
56
+ assets?: string[];
57
+ /** R2: declared types no replay can create, each with its reason (adopted debt, legible). */
58
+ resourcesUnreachable?: Record<string, string>;
59
+ endpointEnv?: {
60
+ name: string;
61
+ templates?: Record<string, string>;
62
+ };
63
+ endpointEnvNone?: string;
64
+ /** The door that issues the application a credential the twin accepts (see TwinPack.credentialDoor). */
65
+ credentialDoor?: {
66
+ path: string;
67
+ body: Record<string, unknown>;
68
+ fill: Record<string, string>;
69
+ };
70
+ credentialIssuer?: string;
71
+ /** The Prisma driver adapter this database twin is reached through (see TwinPack.prismaAdapter). */
72
+ prismaAdapter?: {
73
+ adapter: string;
74
+ export: string;
75
+ urlEnv: string;
76
+ };
77
+ /** The managed-infrastructure kind this pack is the World's backing for (architecture.md, "The descriptor"). */
78
+ managedService?: {
79
+ kind: 'redis' | 'mongodb';
80
+ scheme: string;
81
+ note: string;
82
+ };
83
+ /** The World's managed database the runtime binds the twin to (see TwinPack.managedDatabase). */
84
+ managedDatabase?: {
85
+ kind: 'postgres';
86
+ arg: string;
87
+ note: string;
88
+ };
89
+ /** Listeners the twin opens beside its main port (see TwinPack.sidePorts). */
90
+ sidePorts?: Array<{
91
+ name: string;
92
+ option: string;
93
+ flag: string;
94
+ path?: string;
95
+ note: string;
96
+ }>;
97
+ /** DERIVED, never declared: the pack's `create<Name>TwinServer` factory export — the
98
+ * `({port,root,readOnly}) => {port,stop}` contract the colocated host mounts
99
+ * (world-runtime/src/host.ts). Read off the module's own exports because the name is not
100
+ * mechanically derivable from the vendor string (createUpstashRedisTwinServer,
101
+ * createGoogleOAuthTwinServer); the export itself is the single source of truth. Consumed
102
+ * by `volter-world init` to emit `colocate:` service entries (runtime contract R2a). */
103
+ serveExport?: string;
104
+ };
105
+ export type PackFactsFile = {
106
+ '//': string;
107
+ /** Vendor → facts from its current descriptor or explicit vendor-server backing. */
108
+ packs: Record<string, PackFacts>;
109
+ /** vendor packs whose index.ts exports no `pack` descriptor yet — the migration worklist. */
110
+ undeclared: string[];
111
+ };
112
+ /** Compile descriptors at explicit package directories; no repository layout or publisher namespace is assumed. */
113
+ export declare function compilePackFacts(dirs: Array<{
114
+ vendor: string;
115
+ dir: string;
116
+ }>): Promise<PackFactsFile>;
117
+ export declare function renderPackFacts(file: PackFactsFile): string;
@@ -0,0 +1,242 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ // Published descriptor compiler. Pack locations are explicit; imports happen only when called by a publisher.
10
+ import { existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { PACK_ARCHETYPES, PROTOCOL_VERSION, protocolStanding } from '@volter/world-core/runtime';
13
+ const sortObject = (o) => Object.fromEntries(Object.entries(o).sort(([a], [b]) => a.localeCompare(b)));
14
+ /** Host rules are DATA with a closed grammar (packRegistry HostRule): exactly one selector, regex
15
+ * sources that compile, keys that are one pack's alone (and never another pack's vendor id),
16
+ * and an exclude only where the same key has something to exclude from. */
17
+ function validateHostRules(vendor, rules, vendors, claimedKeys, worldIds) {
18
+ const keys = new Set();
19
+ for (const rule of rules) {
20
+ const selectors = ['host', 'suffix', 'hostPattern'].filter((k) => rule[k] !== undefined);
21
+ if (selectors.length !== 1)
22
+ throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — exactly one of host | suffix | hostPattern`);
23
+ for (const k of ['hostPattern', 'pathPattern']) {
24
+ if (rule[k] !== undefined) {
25
+ try {
26
+ new RegExp(rule[k]);
27
+ }
28
+ catch (e) {
29
+ throw new Error(`pack-facts: ${vendor} host rule ${k} ${JSON.stringify(rule[k])} does not compile: ${e.message}`);
30
+ }
31
+ }
32
+ }
33
+ if (rule.demandPathPattern !== undefined && rule.demandPathPattern !== false) {
34
+ if (typeof rule.demandPathPattern !== 'string')
35
+ throw new Error(`pack-facts: ${vendor} demandPathPattern must be a regex source or false`);
36
+ try {
37
+ new RegExp(rule.demandPathPattern);
38
+ }
39
+ catch (e) {
40
+ throw new Error(`pack-facts: ${vendor} demandPathPattern does not compile: ${e.message}`);
41
+ }
42
+ }
43
+ if (rule.exclude !== undefined && rule.exclude !== true)
44
+ throw new Error(`pack-facts: ${vendor} host rule ${JSON.stringify(rule)} — exclude is \`true\` or absent, never a falsy flag`);
45
+ const key = rule.key ?? vendor;
46
+ // A foreign key is a WORLD identity too: `volter-world init` emits <KEY>_TWIN_URL only for the
47
+ // ids in adoption.worldIds (covers.ts injectorVendorKeysFor), so a key not listed there would
48
+ // route in the injector and never be wired by the world — and a pack claiming ONLY foreign
49
+ // keys would leave its own <VENDOR>_TWIN_URL inert while looking covered.
50
+ if (key !== vendor && !worldIds.includes(key))
51
+ throw new Error(`pack-facts: ${vendor} claims key "${key}" but does not list it in adoption.worldIds — the routing identity must also be a world identity`);
52
+ if (!/^[a-z0-9-]+$/.test(key))
53
+ throw new Error(`pack-facts: ${vendor} host rule key ${JSON.stringify(key)} — keys are [a-z0-9-]+ (the <KEY>_TWIN_URL stem)`);
54
+ if (key !== vendor && vendors.includes(key))
55
+ throw new Error(`pack-facts: ${vendor} claims key "${key}", which is another pack's vendor id — a pack's hosts are its own`);
56
+ const owner = claimedKeys.get(key);
57
+ if (owner !== undefined && owner !== vendor)
58
+ throw new Error(`pack-facts: key "${key}" is claimed by both ${owner} and ${vendor} — a key belongs to one pack`);
59
+ claimedKeys.set(key, vendor);
60
+ keys.add(key);
61
+ }
62
+ for (const key of keys) {
63
+ if (rules.filter((r) => (r.key ?? vendor) === key).every((r) => r.exclude === true))
64
+ throw new Error(`pack-facts: ${vendor} key "${key}" has only exclude rules — nothing to exclude from`);
65
+ }
66
+ // A pack may answer ONLY under foreign keys (aws: the consolidated twin has no `aws` host and
67
+ // AWS_TWIN_URL is null by design) — the worldIds rule above is what makes that explicit.
68
+ }
69
+ /** Compile descriptors at explicit package directories; no repository layout or publisher namespace is assumed. */
70
+ export async function compilePackFacts(dirs) {
71
+ const claimedKeys = new Map();
72
+ const claimedTools = new Map();
73
+ const packDirs = new Map(dirs.map((e) => [e.vendor, e.dir]));
74
+ const vendors = [...packDirs.keys()].sort();
75
+ const packs = {};
76
+ const undeclared = [];
77
+ for (const vendor of vendors) {
78
+ const entry = join(packDirs.get(vendor), 'src', 'index.ts');
79
+ if (!existsSync(entry)) {
80
+ undeclared.push(vendor);
81
+ continue;
82
+ }
83
+ const mod = await import(__rewriteRelativeImportExtension(entry));
84
+ const pack = mod.pack;
85
+ if (pack === undefined || typeof pack.vendor !== 'string') {
86
+ undeclared.push(vendor);
87
+ continue;
88
+ }
89
+ if (pack.vendor !== vendor) {
90
+ throw new Error(`pack-facts: ${entry} declares vendor "${pack.vendor}" but lives in ${vendor}/ of the pack repository — the directory IS the identity; rename one.`);
91
+ }
92
+ if (pack.hosts !== undefined && pack.hostsNone !== undefined) {
93
+ throw new Error(`pack-facts: ${vendor} declares BOTH hosts and hostsNone — a ruling is one or the other.`);
94
+ }
95
+ if (pack.hosts !== undefined)
96
+ validateHostRules(vendor, pack.hosts, vendors, claimedKeys, pack.adoption?.worldIds ?? []);
97
+ for (const tool of pack.adoption?.tools ?? []) {
98
+ if (!tool || typeof tool.package !== 'string' || tool.package === '' || (tool.usage !== 'build' && tool.usage !== 'deployment')) {
99
+ throw new Error(`pack-facts: ${vendor} adoption tool must be { package, usage: "build" | "deployment" }`);
100
+ }
101
+ const owner = claimedTools.get(tool.package);
102
+ if (owner !== undefined && owner !== vendor)
103
+ throw new Error(`pack-facts: tool package "${tool.package}" is claimed by both ${owner} and ${vendor}`);
104
+ claimedTools.set(tool.package, vendor);
105
+ }
106
+ // the World reads its name and templates (init.ts, served-world.ts): a bare string sets nothing, silently
107
+ const ee = pack.endpointEnv;
108
+ if (ee !== undefined && (typeof ee !== 'object' || ee === null || typeof ee.name !== 'string' || typeof ee.note !== 'string')) {
109
+ throw new Error(`pack-facts: ${vendor}'s endpointEnv is ${JSON.stringify(ee)}; it is { name, templates?, note } (the variable the vendor's SDK reads, and why)`);
110
+ }
111
+ if (pack.endpointEnv !== undefined && pack.endpointEnvNone !== undefined) {
112
+ throw new Error(`pack-facts: ${vendor} declares BOTH endpointEnv and endpointEnvNone — a ruling is one or the other.`);
113
+ }
114
+ if (pack.archetype === undefined) {
115
+ throw new Error(`pack-facts: ${vendor} declares no archetype — every pack ratifies its serve-family (closed enum: ${PACK_ARCHETYPES.join(' | ')}; see packRegistry PackArchetype)`);
116
+ }
117
+ if (!PACK_ARCHETYPES.includes(pack.archetype)) {
118
+ throw new Error(`pack-facts: ${vendor} declares unknown archetype "${String(pack.archetype)}" (closed enum: ${PACK_ARCHETYPES.join(' | ')})`);
119
+ }
120
+ // raw-tcp consistency: the one raw-protocol pack today serves CRUD state over its wire.
121
+ if (pack.archetype === 'proxy' && pack.transport === 'raw-tcp') {
122
+ throw new Error(`pack-facts: ${vendor} — proxy archetype over raw-tcp transport is undeclared territory; rule it in the contract first.`);
123
+ }
124
+ const facts = {};
125
+ if (pack.adoption !== undefined)
126
+ facts.adoption = pack.adoption;
127
+ if (pack.hosts !== undefined)
128
+ facts.hosts = pack.hosts;
129
+ if (pack.hostsNone !== undefined)
130
+ facts.hostsNone = pack.hostsNone;
131
+ if (pack.hostsClaimed !== undefined) {
132
+ if (!pack.hostsClaimed.door.startsWith('/') || !pack.hostsClaimed.note.trim())
133
+ throw new Error(`pack-facts: ${vendor} hostsClaimed needs a door path and a note`);
134
+ if (pack.hosts === undefined)
135
+ throw new Error(`pack-facts: ${vendor} claims hosts at runtime but declares none of its own: its door is reached on its own host`);
136
+ facts.hostsClaimed = pack.hostsClaimed;
137
+ }
138
+ facts.archetype = pack.archetype;
139
+ // R16 — the protocol the package targets, and where it stands against this kernel: the kernel serves its own
140
+ // major and no other, so any other declaration, or none, is refused here.
141
+ {
142
+ const standing = protocolStanding(pack.protocol);
143
+ if (standing.standing === 'refused')
144
+ throw new Error(`pack-facts: ${vendor} declares protocol ${JSON.stringify(pack.protocol)} — this kernel serves ${PROTOCOL_VERSION} and no other major (runtime contract R16)`);
145
+ facts.protocol = { ...(pack.protocol !== undefined ? { declared: pack.protocol } : {}), major: standing.major, standing: standing.standing };
146
+ }
147
+ facts.transport = pack.transport;
148
+ if (pack.maxRequestBodySize !== undefined) {
149
+ if (!Number.isSafeInteger(pack.maxRequestBodySize) || pack.maxRequestBodySize <= 0)
150
+ throw new Error(`pack-facts: ${vendor} maxRequestBodySize must be positive integer bytes`);
151
+ facts.maxRequestBodySize = pack.maxRequestBodySize;
152
+ }
153
+ if (pack.assets !== undefined)
154
+ facts.assets = pack.assets; // R20 — what the deploy step places behind the assets binding
155
+ if (pack.resourcesUnreachable !== undefined)
156
+ facts.resourcesUnreachable = pack.resourcesUnreachable; // R2 adopted-as-debt, by name and reason
157
+ facts.resources = pack.resources; // the declared subject types — R2's resource-level claim
158
+ if (pack.endpointEnv !== undefined)
159
+ facts.endpointEnv = pack.endpointEnv;
160
+ if (pack.endpointEnvNone !== undefined)
161
+ facts.endpointEnvNone = pack.endpointEnvNone;
162
+ if (pack.credentialDoor !== undefined)
163
+ facts.credentialDoor = pack.credentialDoor;
164
+ if (pack.credentialIssuer !== undefined) {
165
+ if (!/^[a-z][a-z0-9-]*$/.test(pack.credentialIssuer) || pack.credentialIssuer === vendor)
166
+ throw new Error(`pack-facts: ${vendor} credentialIssuer must name another pack`);
167
+ facts.credentialIssuer = pack.credentialIssuer;
168
+ }
169
+ if (pack.prismaAdapter !== undefined) {
170
+ if (pack.endpointEnv?.templates?.[pack.prismaAdapter.urlEnv] === undefined && pack.credentialDoor?.fill[pack.prismaAdapter.urlEnv] === undefined) {
171
+ throw new Error(`pack-facts: ${vendor} declares prismaAdapter.urlEnv "${pack.prismaAdapter.urlEnv}" but neither its endpointEnv templates nor its credentialDoor fill such a variable — the adapter's URL is one of theirs.`);
172
+ }
173
+ facts.prismaAdapter = pack.prismaAdapter;
174
+ }
175
+ if (pack.managedDatabase !== undefined) {
176
+ const md = pack.managedDatabase;
177
+ if (md.kind !== 'postgres')
178
+ throw new Error(`pack-facts: ${vendor} declares managedDatabase.kind "${String(md.kind)}"; the runtime binds only the World's managed postgres`);
179
+ if (typeof md.arg !== 'string' || !/^--[a-z][a-z-]*$/.test(md.arg))
180
+ throw new Error(`pack-facts: ${vendor} declares managedDatabase.arg "${String(md.arg)}"; it is the serve flag the URL follows (--name)`);
181
+ if (typeof md.note !== 'string' || md.note.trim() === '')
182
+ throw new Error(`pack-facts: ${vendor} declares managedDatabase with no note`);
183
+ facts.managedDatabase = pack.managedDatabase;
184
+ }
185
+ if (pack.managedService !== undefined) {
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`);
189
+ if (typeof ms.scheme !== 'string' || !/^[a-z][a-z0-9+.-]*$/.test(ms.scheme))
190
+ throw new Error(`pack-facts: ${vendor} declares managedService.scheme "${String(ms.scheme)}"; it is the published URL's scheme`);
191
+ if (typeof ms.note !== 'string' || ms.note.trim() === '')
192
+ throw new Error(`pack-facts: ${vendor} declares managedService with no note`);
193
+ if (pack.bin === undefined)
194
+ throw new Error(`pack-facts: ${vendor} declares managedService with no bin; the managed infrastructure starts the pack by its bin`);
195
+ const other = Object.entries(packs).find(([, f]) => f.managedService?.kind === ms.kind);
196
+ if (other)
197
+ throw new Error(`pack-facts: ${vendor} and ${other[0]} both declare managedService.kind "${ms.kind}"; one pack backs a kind`);
198
+ facts.managedService = pack.managedService;
199
+ }
200
+ if (pack.sidePorts !== undefined) {
201
+ for (const side of pack.sidePorts) {
202
+ if (typeof side.name !== 'string' || !/^[a-z][a-z0-9-]*$/.test(side.name))
203
+ throw new Error(`pack-facts: ${vendor} declares a side port named "${String(side.name)}"; a name is lowercase (inspect)`);
204
+ if (typeof side.option !== 'string' || !/^[a-z][A-Za-z0-9]*$/.test(side.option))
205
+ throw new Error(`pack-facts: ${vendor} side port ${side.name}: option "${String(side.option)}" is the factory's camelCase option`);
206
+ if (typeof side.flag !== 'string' || !/^--[a-z][a-z-]*$/.test(side.flag))
207
+ throw new Error(`pack-facts: ${vendor} side port ${side.name}: flag "${String(side.flag)}" is the serve flag (--name)`);
208
+ if (side.path !== undefined && (typeof side.path !== 'string' || !side.path.startsWith('/')))
209
+ throw new Error(`pack-facts: ${vendor} side port ${side.name}: path "${String(side.path)}" starts with /`);
210
+ if (typeof side.note !== 'string' || side.note.trim() === '')
211
+ throw new Error(`pack-facts: ${vendor} side port ${side.name} has no note`);
212
+ }
213
+ facts.sidePorts = pack.sidePorts;
214
+ }
215
+ // The serve factory is DERIVED from the module's exports (see PackFacts.serveExport);
216
+ // a pack exporting SEVERAL factories must declare which one `cli.ts serve` boots — the
217
+ // exports alone cannot reveal that judgment (linear defaults to its DERIVED server).
218
+ const serveExports = Object.keys(mod).filter((k) => /^create\w*TwinServer$/.test(k) && typeof mod[k] === 'function');
219
+ if (pack.serveExport !== undefined) {
220
+ if (!serveExports.includes(pack.serveExport)) {
221
+ throw new Error(`pack-facts: ${vendor} declares serveExport "${pack.serveExport}" but its index exports no such create*TwinServer factory (found: ${serveExports.join(', ') || 'none'}).`);
222
+ }
223
+ facts.serveExport = pack.serveExport;
224
+ }
225
+ else if (serveExports.length > 1) {
226
+ throw new Error(`pack-facts: ${vendor} exports ${serveExports.length} create*TwinServer factories (${serveExports.join(', ')}) — declare \`serveExport\` on its descriptor naming the one cli.ts serve boots.`);
227
+ }
228
+ else if (serveExports.length === 1) {
229
+ facts.serveExport = serveExports[0];
230
+ }
231
+ if (Object.keys(facts).length > 0)
232
+ packs[vendor] = facts;
233
+ }
234
+ return {
235
+ '//': 'GENERATED by scripts/pack-facts.ts — current pack descriptors and explicit world-runtime server backings; no prior-artifact entries are retained.',
236
+ packs: sortObject(packs),
237
+ undeclared,
238
+ };
239
+ }
240
+ export function renderPackFacts(file) {
241
+ return JSON.stringify(file, null, 2) + '\n';
242
+ }
@@ -31,6 +31,7 @@ export type StepHooks = {
31
31
  * its sockets are WebSockets to that port, and a line connection (`stream`) is a TCP connection to its protocol port. */
32
32
  export type ServedTwin = (root: string, options: {
33
33
  scenarioPath?: string;
34
+ application?: import('@volter/world-core').PackFetchOptions['application'];
34
35
  }) => Promise<{
35
36
  fetch: (request: Request) => Promise<Response>;
36
37
  stop: () => void;
@@ -11,10 +11,10 @@ import { attributionName } from "../verdict.js";
11
11
  import { redis } from '@volter/world-core';
12
12
  import { gzipSync } from 'node:zlib';
13
13
  import { parse, visit } from 'graphql';
14
- import { existsSync, readFileSync } from 'node:fs';
14
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
16
  import { databaseSession, getActiveWorldStore, MemoryBlobStore, MemoryWorldStore, setActiveBlobStore, setActiveWorldStore, setDeliveryObserver, setInProcessTwins } from '@volter/world-core/runtime';
17
- import { VENDOR_HOSTS } from '@volter/world-core/vendor-hosts';
17
+ import { createHostTable } from '@volter/world-core/host-routing';
18
18
  import packFactsDocument from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
19
19
  import { isResp, at, captureAt, echoFailures, expandFills, expectFailures, fill, fillJson, formBody, isGit, isLine, isSql, isSocket, isWait, jwtAuthorization, oauth1Authorization, presignedPath, sigv4Headers, waitMs } from "./journey-kit.js";
20
20
  import { lanesOf, lineShape, shapeChecker } from "./shape.js";
@@ -28,7 +28,22 @@ function socketAt(open, host) {
28
28
  const u = new URL(open);
29
29
  return { host: u.host, path: `${u.pathname}${u.search}` };
30
30
  }
31
- export function usePacks(dir) { packsRoot = dir; }
31
+ const baselineFacts = packFactsDocument.packs;
32
+ let walkFacts = baselineFacts;
33
+ let walkHosts = createHostTable(walkFacts).hosts;
34
+ export function usePacks(dir) {
35
+ packsRoot = dir;
36
+ const installed = {};
37
+ for (const name of readdirSync(dir)) {
38
+ const file = join(dir, name, 'generated/pack-facts.json');
39
+ if (existsSync(file))
40
+ Object.assign(installed, JSON.parse(readFileSync(file, 'utf8')).packs);
41
+ }
42
+ walkFacts = { ...baselineFacts, ...installed };
43
+ walkHosts = createHostTable(walkFacts, Object.keys(installed)).hosts;
44
+ shapes.clear();
45
+ fieldsByVendor.clear();
46
+ }
32
47
  const packsDir = () => { if (packsRoot === undefined)
33
48
  throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
34
49
  /** The walk's World: the twin's own code on its in-memory World and blob stores (the backends a serverless
@@ -87,15 +102,15 @@ const shapeOf = (vendor) => { if (!shapes.has(vendor))
87
102
  * name, as vendor-hosts.cjs compiles them. */
88
103
  function routingKeys(vendor) {
89
104
  const name = vendor.split('/')[0];
90
- const hosts = (packFactsDocument.packs[name]?.hosts) ?? [];
91
- return [...new Set(hosts.map((rule) => rule.key ?? name))].filter((key) => VENDOR_HOSTS[key] !== undefined);
105
+ const hosts = walkFacts[name]?.hosts ?? [];
106
+ return [...new Set(hosts.map((rule) => rule.key ?? name))].filter((key) => walkHosts[key] !== undefined);
92
107
  }
93
108
  /** Whether one of `keys`' host rules claims `url` (an absolute URL; a path is the life's own host). */
94
109
  const claims = (keys, url) => {
95
110
  if (!/^https?:\/\//.test(url) || !URL.canParse(url))
96
111
  return false;
97
112
  const u = new URL(url);
98
- return keys.some((key) => VENDOR_HOSTS[key](u.hostname, u.pathname));
113
+ return keys.some((key) => walkHosts[key](u.hostname, u.pathname));
99
114
  };
100
115
  /** What a stored row keeps for the twin that no schema of the vendor's spec names, by path, in anything the World shows a
101
116
  * client: the pack's `_`-prefixed bookkeeping (GitHub's `_links` is the vendor's), and, in what no answer's SHAPE
@@ -220,7 +235,7 @@ export async function walk(journey, root, collect, opts) {
220
235
  }
221
236
  return { status: a.status, ...(a.headers ? { headers: a.headers } : {}), ...(a.bodyError === undefined ? {} : { bodyError: a.bodyError }), body: typeof a.body === 'string' ? a.body : a.body === undefined ? '' : JSON.stringify(a.body) };
222
237
  };
223
- const served = opts.served ? await opts.served(root, scenarioPath ? { scenarioPath } : {}) : undefined;
238
+ const served = opts.served ? await opts.served(root, { ...(scenarioPath ? { scenarioPath } : {}), application }) : undefined;
224
239
  const twin = served ? served.fetch : factory(root, { ...(scenarioPath ? { scenarioPath } : {}), application });
225
240
  const shape = await shapeOf(journey.vendor);
226
241
  const host = journey.host ?? 'twin.local';
@@ -10,7 +10,6 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
10
10
  // index. What the catalog's walkers drive (behavior journeys, the spec census probe, the invariants)
11
11
  // since the worker render left with the v1 host.
12
12
  import { join } from 'node:path';
13
- import packFacts from '@volter/world-core/generated/pack-facts.json' with { type: 'json' };
14
13
  import { startPgliteHost } from "./walk-database.js";
15
14
  import { worldNow } from '@volter/world-core/runtime';
16
15
  export async function vendorFetch(twinDir, vendor) {
@@ -26,7 +25,7 @@ export async function vendorFetch(twinDir, vendor) {
26
25
  if (!name)
27
26
  return undefined;
28
27
  const factory = mod[name];
29
- if (!declaresManagedDatabase(vendor))
28
+ if (!mod.pack?.managedDatabase)
30
29
  return (root, options = {}) => factory({ root, ...options });
31
30
  // A pack whose data plane is the World's managed Postgres is walked over one, as a World binds it: each World (each
32
31
  // root) its own fresh database, started on its first request and stopped when the walk's process ends, its clock the
@@ -52,11 +51,6 @@ process.once('exit', () => { for (const host of hosts) {
52
51
  }
53
52
  catch { /* ended */ }
54
53
  } });
55
- /** Whether a pack declares the World's managed database (its descriptor's `managedDatabase`, in the pack facts: this
56
- * checkout's kernel's, as the walk's other readers of them import them). */
57
- function declaresManagedDatabase(vendor) {
58
- return packFacts.packs[vendor.split('/')[0]]?.managedDatabase !== undefined;
59
- }
60
54
  const jwtView = (field) => (body) => {
61
55
  const token = body?.[field];
62
56
  if (typeof token !== 'string')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
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",
@@ -70,12 +70,16 @@
70
70
  "yaml": "2.9.0"
71
71
  },
72
72
  "peerDependencies": {
73
- "@volter/world-core": "*",
74
- "@volter/world-runtime": "*"
73
+ "@volter/world-core": "3.0.42",
74
+ "@volter/world-runtime": "*",
75
+ "playwright": "^1.63.0"
75
76
  },
76
77
  "peerDependenciesMeta": {
77
78
  "@volter/world-runtime": {
78
79
  "optional": true
80
+ },
81
+ "playwright": {
82
+ "optional": true
79
83
  }
80
84
  },
81
85
  "devDependencies": {
@@ -0,0 +1,93 @@
1
+ // Published assessment API. The standard owns journeys and checks; catalog controllers own admission authority.
2
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { observeDerivedDispatch, type DerivedOwner } from '@volter/world-core/runtime';
6
+ import { p3Gate } from './gate.ts';
7
+ import { conformance } from './conformance.ts';
8
+ import { inMemoryWorld, walk, withPinnedWorldDraws, usePacks, type ServedTwin } from './walk/behavior-journey.ts';
9
+ import { compilePackFacts } from './pack-facts.ts';
10
+ import { expandFills, type Journey } from './walk/journey-kit.ts';
11
+
12
+ const canonical = (value: unknown): unknown => value && typeof value === 'object' ? Array.isArray(value) ? value.map(canonical) : Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonical(item)])) : value;
13
+ const json = (value: unknown) => JSON.stringify(canonical(value));
14
+ const hash = (value: unknown) => createHash('sha256').update(json(value)).digest('hex');
15
+ const read = (file: string) => JSON.parse(readFileSync(file, 'utf8'));
16
+ export type AssessmentOptions = { browser?: boolean; conformance?: boolean };
17
+
18
+ /** Surface coverage counts actual kernel dispatch, and retains every declared gap in the denominator. */
19
+ export async function assessPack(packDir: string, options: AssessmentOptions = {}) {
20
+ if (!process.env.VOLTER_WORLD) throw new Error('Assessment must run inside a World');
21
+ const facts = read(join(packDir, 'generated/pack-facts.json'));
22
+ const vendor = Object.keys(facts.packs ?? {})[0];
23
+ if (!vendor || Object.keys(facts.packs).length !== 1) throw new Error('Assessment needs one published vendor identity');
24
+ const manifest = read(join(packDir, 'package.json'));
25
+ const compiled = await compilePackFacts([{ vendor, dir: packDir }]);
26
+ if (json(compiled.packs) !== json(facts.packs) || compiled.undeclared.length) throw new Error('Published facts differ from the pack descriptor');
27
+ const lanes = readdirSync(packDir).filter((name) => existsSync(join(packDir, name, 'src/manifest.ts')) && !existsSync(join(packDir, name, 'package.json')));
28
+ const units = [packDir, ...lanes.map((name) => join(packDir, name))];
29
+ if (packDir.split(/[\\/]/).at(-1) !== vendor) throw new Error('Place the artifact in a directory named for its vendor before assessment');
30
+ usePacks(join(packDir, '..'));
31
+ const gates = await p3Gate(units);
32
+ const life = expandFills(read(join(packDir, 'journeys/customer-life.json'))) as Journey;
33
+ if (!Array.isArray(life.steps) || !life.steps.length) throw new Error('Assessment needs a nonempty customer journey');
34
+ const surfaceRows: Array<{ unit: string; signature: string; ids: string[] }> = [];
35
+ for (const unit of units) {
36
+ const file = join(unit, 'src/generated/surface.gen.json');
37
+ if (!existsSync(file)) continue;
38
+ const surface = read(file);
39
+ if (!Array.isArray(surface.operations) || !surface.operations.length) continue;
40
+ surfaceRows.push({ unit: unit === packDir ? vendor : `${vendor}/${unit.slice(packDir.length + 1)}`, signature: hash(surface.operations), ids: surface.operations.map((operation: { id: string }) => operation.id) });
41
+ }
42
+ const declaredHttp = [...new Set(surfaceRows.flatMap((row) => row.ids.map((id) => `${row.unit}:${id}`)))].sort();
43
+ const mod = await import(join(packDir, 'src/index.ts')) as Record<string, unknown>;
44
+ const factory = Object.entries(mod).find(([name, value]) => /^create\w+Fetch$/.test(name) && !/TwinFetch$/.test(name) && typeof value === 'function')?.[1] as ((options: { root: string }) => { ownersByUnit?: () => Record<string, Record<string, DerivedOwner>>; owners?: () => Record<string, DerivedOwner> }) | undefined;
45
+ const surfaceWorld = inMemoryWorld('catalog-surface');
46
+ let owners: Record<string, DerivedOwner> | null = null;
47
+ try {
48
+ const wire = factory?.({ root: surfaceWorld.root() });
49
+ if (wire?.ownersByUnit) owners = Object.fromEntries(Object.entries(wire.ownersByUnit()).flatMap(([unit, operations]) => Object.entries(operations).map(([id, owner]) => [`${vendor}${unit ? `/${unit}` : ''}:${id}`, owner])));
50
+ else if (wire?.owners) owners = Object.fromEntries(Object.entries(wire.owners()).map(([id, owner]) => [`${vendor}:${id}`, owner]));
51
+ } finally { surfaceWorld.restore(); }
52
+ const declared = owners ? Object.keys(owners).sort() : [];
53
+ const servedOperations = owners ? declared.filter((id) => owners![id] !== 'gap') : [];
54
+ const instrumented = declared.length > 0 && json(declared) === json(declaredHttp);
55
+ const coverageOf = (result: { exercised: string[]; ambiguous: boolean }) => !instrumented || result.ambiguous ? null : { total: declared.length, exercised: result.exercised.length, percent: 100 * result.exercised.length / declared.length, operations: result.exercised, missing: declared.filter((id) => !result.exercised.includes(id)) };
56
+ async function replay(served?: ServedTwin) {
57
+ const world = inMemoryWorld('catalog-replay');
58
+ const failures: Array<{ step: string; beat: string; detail: string }> = [], trace: unknown[] = [];
59
+ const exercised = new Set<string>();
60
+ const signatures = new WeakMap<object, string>();
61
+ let ambiguous = false;
62
+ const stop = observeDerivedDispatch(({ surface, operation }) => {
63
+ let signature = signatures.get(surface.operations);
64
+ if (!signature) { signature = hash(surface.operations); signatures.set(surface.operations, signature); }
65
+ const candidates = surfaceRows.filter((row) => row.signature === signature);
66
+ if (!candidates.length) return; // Companion dispatch is outside this pack's denominator.
67
+ if (candidates.length !== 1) { ambiguous = true; return; }
68
+ const id = `${candidates[0]!.unit}:${operation.id}`;
69
+ exercised.add(id);
70
+ });
71
+ try {
72
+ await withPinnedWorldDraws(() => walk(structuredClone(life), world.root(), failures, { beats: true, ...(served ? { served } : {}), hooks: { after: (step, path, answer) => trace.push({ name: step.name ?? '', path, status: answer.status, body: answer.body }) } }));
73
+ return { failures, trace, exercised: [...exercised].sort(), ambiguous };
74
+ } finally { stop(); world.restore(); }
75
+ }
76
+ const first = await replay(), second = await replay();
77
+ const replayEqual = first.trace.length > 0 && json(first.trace) === json(second.trace);
78
+ const coverage = coverageOf(first);
79
+ const quick = { scope: 'packaged-customer-journey; in-process', steps: life.steps.length, answered: first.trace.length, failures: [...first.failures, ...second.failures], replay: { equal: replayEqual, runs: 2, firstSha256: hash(first.trace), secondSha256: hash(second.trace) }, operationCoverage: coverage, servedOperations, surface: owners ? { total: declared.length, served: servedOperations.length, gap: declared.length - servedOperations.length } : null, codeCoverage: null, stateTransitionCoverage: null };
80
+ let browser: Record<string, unknown> = { measured: false, reason: options.browser ? 'Journey has a wire Chromium fetch cannot exercise' : 'Browser target not requested' };
81
+ const httpOnly = life.steps.every((step) => 'method' in step || 'wait' in step);
82
+ if (options.browser && httpOnly) {
83
+ const { chromiumServed } = await import('./browser-assessment.ts');
84
+ const transport = await chromiumServed(packDir, life.host ?? 'twin.local');
85
+ try {
86
+ const a = await replay(transport.served), b = await replay(transport.served);
87
+ browser = { measured: true, target: 'Chromium fetch, cookie jar and HTTP response behavior; no DOM interaction coverage', version: transport.version, answered: a.trace.length, failures: [...a.failures, ...b.failures], replay: { equal: a.trace.length > 0 && json(a.trace) === json(b.trace), firstSha256: hash(a.trace), secondSha256: hash(b.trace), runs: 2 }, operationCoverage: coverageOf(a), domCoverage: null, clock: 'World and page JavaScript clocks frozen; native cookie expiration uses Chromium wall time' };
88
+ } finally { await transport.stop(); }
89
+ }
90
+ const conform = options.conformance === false ? null : await conformance(packDir);
91
+ const ready = gates.length > 0 && gates.every((gate) => !gate.failures.length) && !quick.failures.length && replayEqual && conform !== null && conform.length > 0 && conform.every((result) => !result.failures.length) && (!options.browser || !httpOnly || browser.measured === true && (browser.failures as unknown[]).length === 0 && (browser.replay as { equal: boolean }).equal);
92
+ return { schemaVersion: 1, vendor, package: manifest.name, version: manifest.version, ready, gates: gates.map((gate) => ({ ...gate, unit: gate.unit === packDir ? vendor : `${vendor}/${gate.unit.slice(packDir.length + 1)}` })), quick, browser, conformance: conform };
93
+ }
@@ -0,0 +1,47 @@
1
+ // Browser transport for the existing journey engine. A container supplies network isolation in catalog execution.
2
+ import { join } from 'node:path';
3
+ import type { ServedTwin } from './walk/behavior-journey.ts';
4
+ export async function chromiumServed(packDir: string, host: string) {
5
+ const { chromium } = await import('playwright');
6
+ const browser = await chromium.launch({ args: ['--no-sandbox'] });
7
+ const mod = await import(join(packDir, 'src/index.ts')) as Record<string, unknown>;
8
+ const factory = Object.entries(mod).find(([name, value]) => /^create\w+Fetch$/.test(name) && !/TwinFetch$/.test(name) && typeof value === 'function')?.[1] as ((options: import('@volter/world-core').PackFetchOptions) => (request: Request) => Promise<Response>) | undefined;
9
+ if (!factory) { await browser.close(); throw new Error('Browser assessment needs the pack HTTP fetch factory'); }
10
+ const served: ServedTwin = async (root, options) => {
11
+ const fetchPack = factory({ root, ...options });
12
+ const context = await browser.newContext({ serviceWorkers: 'block', locale: 'en-US', timezoneId: 'UTC' });
13
+ const page = await context.newPage();
14
+ const harness = `https://${host}/__catalog_assessment__`;
15
+ await context.route('**/*', async (route) => {
16
+ const request = route.request();
17
+ if (request.url() === harness) { await route.fulfill({ status: 200, contentType: 'text/html', body: '<!doctype html><title>Catalog assessment</title>' }); return; }
18
+ if (!['xhr', 'fetch'].includes(request.resourceType())) { await route.abort('blockedbyclient'); return; }
19
+ try {
20
+ const response = await fetchPack(new Request(request.url(), { method: request.method(), headers: await request.allHeaders(), ...(request.postDataBuffer() ? { body: new Uint8Array(request.postDataBuffer()!).buffer } : {}) }));
21
+ const headers = Object.fromEntries(response.headers);
22
+ const cookies = response.headers.getSetCookie();
23
+ if (cookies.length) headers['set-cookie'] = cookies.join('\n');
24
+ await route.fulfill({ status: response.status, headers, body: Buffer.from(await response.arrayBuffer()) });
25
+ } catch { await route.abort('failed'); }
26
+ });
27
+ await page.goto(harness);
28
+ await page.clock.setFixedTime(new Date('2026-01-01T00:00:00.000Z'));
29
+ return {
30
+ fetch: async (request: Request) => {
31
+ const body = request.body ? Buffer.from(await request.arrayBuffer()).toString('base64') : null;
32
+ const result = await page.evaluate(async ({ url, method, headers, body }) => {
33
+ const bytes = body === null ? undefined : Uint8Array.from(atob(body), (char) => char.charCodeAt(0));
34
+ const response = await fetch(url, { method, headers, body: bytes, redirect: 'manual', credentials: 'include' });
35
+ const data = new Uint8Array(await response.arrayBuffer());
36
+ let text = '';
37
+ for (let i = 0; i < data.length; i += 32768) text += String.fromCharCode(...data.subarray(i, i + 32768));
38
+ return { status: response.status, headers: [...response.headers], body: btoa(text) };
39
+ }, { url: request.url, method: request.method, headers: Object.fromEntries(request.headers), body });
40
+ if (!result.status) throw new Error('Chromium returned an opaque response; this journey needs browser-specific navigation assertions');
41
+ return new Response([101, 204, 205, 304].includes(result.status) || request.method === 'HEAD' ? null : Buffer.from(result.body, 'base64'), { status: result.status, headers: result.headers });
42
+ },
43
+ stop: () => { void context.close(); },
44
+ };
45
+ };
46
+ return { served, version: browser.version(), stop: () => browser.close() };
47
+ }