@volter/twin-standard 1.0.59 → 1.0.61

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.
@@ -10,11 +10,22 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
10
10
  // vendor, the served port, the host, the issued credentials and the scenario file; each case's failures are printed as
11
11
  // one JSON line, `{ id, asks, failures }`.
12
12
  import { readFileSync } from 'node:fs';
13
+ import { createRequire } from 'node:module';
14
+ import { extname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
13
16
  import { clientContext, worldProxy } from "./harness.js";
14
17
  const job = JSON.parse(readFileSync(process.argv[2], 'utf8'));
15
- const { cases } = (await import(__rewriteRelativeImportExtension(`./${job.vendor}.ts`)));
18
+ const { cases } = (await import(__rewriteRelativeImportExtension(`./${job.vendor}${extname(fileURLToPath(import.meta.url))}`)));
16
19
  // every connection a case makes goes through the World's proxy, as an application's does in a World
17
- const context = clientContext(await worldProxy(job.port), job.host, job.env, job.scenarioPath);
20
+ const proxy = await worldProxy(job.port);
21
+ // Bind only this artifact's declared vendor hosts to the assessed server. The enclosing World's
22
+ // injector stays active, and untwinned destinations keep its refusal policy.
23
+ const require = createRequire(import.meta.url);
24
+ require('@volter/world-core/pack-facts').extendPackFacts([job.packFacts]);
25
+ const facts = JSON.parse(readFileSync(job.packFacts, 'utf8')).packs[job.vendor];
26
+ const keys = [...new Set((facts.hosts ?? []).map((rule) => rule.key ?? job.vendor))];
27
+ require('@volter/world-core/inject').install(Object.fromEntries(keys.map((key) => [key, `http://127.0.0.1:${proxy}`])));
28
+ const context = clientContext(proxy, job.host, job.env, job.scenarioPath);
18
29
  for (const c of cases) {
19
30
  const failures = await Promise.race([
20
31
  c.run(context).catch((e) => [`the case threw: ${e.message}`]),
@@ -23,8 +23,9 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSy
23
23
  import { createRequire } from 'node:module';
24
24
  import { connect } from 'node:net';
25
25
  import { tmpdir } from 'node:os';
26
- import { basename, dirname, join, matchesGlob, relative, resolve } from 'node:path';
26
+ import { basename, dirname, extname, join, matchesGlob, relative, resolve } from 'node:path';
27
27
  import { citations, sourceHeld } from "./check-sources.js";
28
+ import { fileURLToPath } from 'node:url';
28
29
  const pascal = (s) => s.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
29
30
  const readJson = (p) => (existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : undefined);
30
31
  /** The pack's units: the pack and each lane in it (a directory with its own src/manifest.ts and no package.json). */
@@ -331,7 +332,7 @@ function lineConnection(port) {
331
332
  return (sink) => {
332
333
  const socket = connect(port, '127.0.0.1');
333
334
  let heard = 0;
334
- socket.on('data', (bytes) => { heard = Date.now(); sink.write(bytes); });
335
+ socket.on('data', (bytes) => { heard = performance.now(); sink.write(bytes); });
335
336
  socket.on('close', () => sink.end());
336
337
  socket.on('error', () => socket.destroy());
337
338
  const opened = new Promise((resolveOpen) => { socket.once('connect', () => resolveOpen()); socket.once('error', () => resolveOpen()); });
@@ -339,13 +340,13 @@ function lineConnection(port) {
339
340
  data: (chunk) => { void opened.then(() => socket.write(chunk)); },
340
341
  settled: async () => {
341
342
  await opened;
342
- const start = Date.now();
343
+ const start = performance.now();
343
344
  const since = heard;
344
- while (Date.now() - start < 10_000) {
345
+ while (performance.now() - start < 10_000) {
345
346
  await new Promise((r) => setTimeout(r, 10));
346
347
  if (socket.destroyed)
347
348
  return;
348
- if (heard > since ? Date.now() - heard >= 50 : Date.now() - start >= 500)
349
+ if (heard > since ? performance.now() - heard >= 50 : performance.now() - start >= 500)
349
350
  return;
350
351
  }
351
352
  },
@@ -411,7 +412,9 @@ async function lifeServed(pack) {
411
412
  * descriptor's clientCasesNone says why none is owed. */
412
413
  async function clientCases(pack, scratch) {
413
414
  const vendor = (await manifestOf(pack)).vendor ?? basename(pack);
414
- if (!existsSync(join(import.meta.dir, 'clients', `${vendor}.ts`))) {
415
+ const clients = join(dirname(fileURLToPath(import.meta.url)), 'clients');
416
+ const extension = extname(fileURLToPath(import.meta.url));
417
+ if (!existsSync(join(clients, `${vendor}${extension}`))) {
415
418
  // a vendor whose descriptor names npm clients (adoption.sdks) is held to them: no case is no proof the SDK works,
416
419
  // unless the descriptor says why none is owed (clientCasesNone: no official SDK, or none the demand's applications
417
420
  // use); one an application calls with no SDK of the vendor's (a raw HTTP catalog) has no client to drive
@@ -455,16 +458,16 @@ async function clientCases(pack, scratch) {
455
458
  issued[name] = String(answer[field]);
456
459
  }
457
460
  const job = join(scratch, 'client-job.json');
458
- writeFileSync(job, JSON.stringify({ vendor, port: server.port, host, env: issued, scenarioPath }));
461
+ writeFileSync(job, JSON.stringify({ vendor, port: server.port, host, env: issued, scenarioPath, packFacts: join(pack, 'generated', 'pack-facts.json') }));
459
462
  // Node, as an application runs its SDK (asynchronous: this process serves the pack meanwhile)
460
463
  const out = await new Promise((resolve) => {
461
- const child = spawn('node', ['--experimental-strip-types', '--no-warnings', join(import.meta.dir, 'clients', 'run.ts'), job], { stdio: ['ignore', 'pipe', 'pipe'] });
464
+ const child = spawn('node', ['--experimental-strip-types', '--no-warnings', join(clients, `run${extension}`), job], { stdio: ['ignore', 'pipe', 'pipe'] });
462
465
  let text = '';
463
466
  let errors = '';
464
467
  child.stdout.on('data', (d) => { text += d; });
465
468
  child.stderr.on('data', (d) => { errors += d; });
466
469
  const timer = setTimeout(() => child.kill(), 300_000);
467
- child.on('close', (code) => { clearTimeout(timer); resolve(code === 0 ? text : `${text}\n${JSON.stringify({ id: 'runner', asks: 'the cases run', failures: [`node exited ${code}: ${errors.trim().split('\n').slice(-2).join(' ')}`] })}`); });
470
+ child.on('close', (code) => { clearTimeout(timer); resolve(code === 0 ? text : `${text}\n${JSON.stringify({ id: 'runner', asks: 'the cases run', failures: [`node exited ${code}: ${errors.trim().split('\n').slice(0, 12).join(' ')}`] })}`); });
468
471
  });
469
472
  const results = out.split('\n').filter((l) => l.startsWith('{')).map((l) => JSON.parse(l));
470
473
  return { failures: results.flatMap((r) => r.failures.map((f) => `${r.id} (${r.asks}): ${f}`)), note: `${results.length} case(s) through the vendor's client` };
@@ -645,9 +645,9 @@ export async function walk(journey, root, collect, opts) {
645
645
  // they arrive (the steps' settle waits until none has come for a moment)
646
646
  const wire = new Map();
647
647
  const settleWire = async (c, step) => {
648
- const deadline = Date.now() + 10_000;
648
+ const deadline = performance.now() + 10_000;
649
649
  let seen = -1;
650
- while (Date.now() < deadline) {
650
+ while (performance.now() < deadline) {
651
651
  const body = { frames: c.frames, last: c.frames.at(-1) ?? null, closed: c.closed };
652
652
  let expected = true;
653
653
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.67",
78
+ "@volter/world-core": "3.0.69",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
@@ -2,12 +2,23 @@
2
2
  // vendor, the served port, the host, the issued credentials and the scenario file; each case's failures are printed as
3
3
  // one JSON line, `{ id, asks, failures }`.
4
4
  import { readFileSync } from 'node:fs';
5
+ import { createRequire } from 'node:module';
6
+ import { extname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
5
8
  import { clientContext, worldProxy, type ClientCase } from './harness.ts';
6
9
 
7
- const job = JSON.parse(readFileSync(process.argv[2]!, 'utf8')) as { vendor: string; port: number; host: string; env: Record<string, string>; scenarioPath: string };
8
- const { cases } = (await import(`./${job.vendor}.ts`)) as { cases: ClientCase[] };
10
+ const job = JSON.parse(readFileSync(process.argv[2]!, 'utf8')) as { vendor: string; port: number; host: string; env: Record<string, string>; scenarioPath: string; packFacts: string };
11
+ const { cases } = (await import(`./${job.vendor}${extname(fileURLToPath(import.meta.url))}`)) as { cases: ClientCase[] };
9
12
  // every connection a case makes goes through the World's proxy, as an application's does in a World
10
- const context = clientContext(await worldProxy(job.port), job.host, job.env, job.scenarioPath);
13
+ const proxy = await worldProxy(job.port);
14
+ // Bind only this artifact's declared vendor hosts to the assessed server. The enclosing World's
15
+ // injector stays active, and untwinned destinations keep its refusal policy.
16
+ const require = createRequire(import.meta.url);
17
+ require('@volter/world-core/pack-facts').extendPackFacts([job.packFacts]);
18
+ const facts = JSON.parse(readFileSync(job.packFacts, 'utf8')).packs[job.vendor];
19
+ const keys: string[] = [...new Set<string>((facts.hosts ?? []).map((rule: { key?: string }) => rule.key ?? job.vendor))];
20
+ require('@volter/world-core/inject').install(Object.fromEntries(keys.map((key) => [key, `http://127.0.0.1:${proxy}`])));
21
+ const context = clientContext(proxy, job.host, job.env, job.scenarioPath);
11
22
  for (const c of cases) {
12
23
  const failures = await Promise.race([
13
24
  c.run(context).catch((e: Error) => [`the case threw: ${e.message}`]),
@@ -15,9 +15,10 @@ import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSy
15
15
  import { createRequire } from 'node:module';
16
16
  import { connect } from 'node:net';
17
17
  import { tmpdir } from 'node:os';
18
- import { basename, dirname, join, matchesGlob, relative, resolve } from 'node:path';
18
+ import { basename, dirname, extname, join, matchesGlob, relative, resolve } from 'node:path';
19
19
  import type { TwinStream } from '@volter/world-core';
20
20
  import { citations, sourceHeld, type SourceRecord } from './check-sources.ts';
21
+ import { fileURLToPath } from 'node:url';
21
22
 
22
23
  export type ConformanceResult = { id: string; asks: string; failures: string[]; note?: string };
23
24
 
@@ -297,7 +298,7 @@ function lineConnection(port: number): TwinStream {
297
298
  return (sink) => {
298
299
  const socket = connect(port, '127.0.0.1');
299
300
  let heard = 0;
300
- socket.on('data', (bytes: Uint8Array) => { heard = Date.now(); sink.write(bytes); });
301
+ socket.on('data', (bytes: Uint8Array) => { heard = performance.now(); sink.write(bytes); });
301
302
  socket.on('close', () => sink.end());
302
303
  socket.on('error', () => socket.destroy());
303
304
  const opened = new Promise<void>((resolveOpen) => { socket.once('connect', () => resolveOpen()); socket.once('error', () => resolveOpen()); });
@@ -305,11 +306,11 @@ function lineConnection(port: number): TwinStream {
305
306
  data: (chunk) => { void opened.then(() => socket.write(chunk)); },
306
307
  settled: async () => {
307
308
  await opened;
308
- const start = Date.now(); const since = heard;
309
- while (Date.now() - start < 10_000) {
309
+ const start = performance.now(); const since = heard;
310
+ while (performance.now() - start < 10_000) {
310
311
  await new Promise((r) => setTimeout(r, 10));
311
312
  if (socket.destroyed) return;
312
- if (heard > since ? Date.now() - heard >= 50 : Date.now() - start >= 500) return;
313
+ if (heard > since ? performance.now() - heard >= 50 : performance.now() - start >= 500) return;
313
314
  }
314
315
  },
315
316
  close: () => socket.destroy(),
@@ -364,7 +365,9 @@ async function lifeServed(pack: string): Promise<string[]> {
364
365
  * descriptor's clientCasesNone says why none is owed. */
365
366
  async function clientCases(pack: string, scratch: string): Promise<{ failures: string[]; note?: string }> {
366
367
  const vendor = (await manifestOf(pack)).vendor ?? basename(pack);
367
- if (!existsSync(join(import.meta.dir, 'clients', `${vendor}.ts`))) {
368
+ const clients = join(dirname(fileURLToPath(import.meta.url)), 'clients');
369
+ const extension = extname(fileURLToPath(import.meta.url));
370
+ if (!existsSync(join(clients, `${vendor}${extension}`))) {
368
371
  // a vendor whose descriptor names npm clients (adoption.sdks) is held to them: no case is no proof the SDK works,
369
372
  // unless the descriptor says why none is owed (clientCasesNone: no official SDK, or none the demand's applications
370
373
  // use); one an application calls with no SDK of the vendor's (a raw HTTP catalog) has no client to drive
@@ -396,16 +399,16 @@ async function clientCases(pack: string, scratch: string): Promise<{ failures: s
396
399
  for (const [name, field] of Object.entries(door.fill)) if (answer[field] !== undefined) issued[name] = String(answer[field]);
397
400
  }
398
401
  const job = join(scratch, 'client-job.json');
399
- writeFileSync(job, JSON.stringify({ vendor, port: server.port, host, env: issued, scenarioPath }));
402
+ writeFileSync(job, JSON.stringify({ vendor, port: server.port, host, env: issued, scenarioPath, packFacts: join(pack, 'generated', 'pack-facts.json') }));
400
403
  // Node, as an application runs its SDK (asynchronous: this process serves the pack meanwhile)
401
404
  const out = await new Promise<string>((resolve) => {
402
- const child = spawn('node', ['--experimental-strip-types', '--no-warnings', join(import.meta.dir, 'clients', 'run.ts'), job], { stdio: ['ignore', 'pipe', 'pipe'] });
405
+ const child = spawn('node', ['--experimental-strip-types', '--no-warnings', join(clients, `run${extension}`), job], { stdio: ['ignore', 'pipe', 'pipe'] });
403
406
  let text = '';
404
407
  let errors = '';
405
408
  child.stdout.on('data', (d: Buffer) => { text += d; });
406
409
  child.stderr.on('data', (d: Buffer) => { errors += d; });
407
410
  const timer = setTimeout(() => child.kill(), 300_000);
408
- child.on('close', (code) => { clearTimeout(timer); resolve(code === 0 ? text : `${text}\n${JSON.stringify({ id: 'runner', asks: 'the cases run', failures: [`node exited ${code}: ${errors.trim().split('\n').slice(-2).join(' ')}`] })}`); });
411
+ child.on('close', (code) => { clearTimeout(timer); resolve(code === 0 ? text : `${text}\n${JSON.stringify({ id: 'runner', asks: 'the cases run', failures: [`node exited ${code}: ${errors.trim().split('\n').slice(0, 12).join(' ')}`] })}`); });
409
412
  });
410
413
  const results = out.split('\n').filter((l) => l.startsWith('{')).map((l) => JSON.parse(l) as { id: string; asks: string; failures: string[] });
411
414
  return { failures: results.flatMap((r) => r.failures.map((f) => `${r.id} (${r.asks}): ${f}`)), note: `${results.length} case(s) through the vendor's client` };
@@ -483,9 +483,9 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
483
483
  // they arrive (the steps' settle waits until none has come for a moment)
484
484
  const wire = new Map<string, WebSocket>();
485
485
  const settleWire = async (c: Socket, step?: SocketStep): Promise<void> => {
486
- const deadline = Date.now() + 10_000;
486
+ const deadline = performance.now() + 10_000;
487
487
  let seen = -1;
488
- while (Date.now() < deadline) {
488
+ while (performance.now() < deadline) {
489
489
  const body = { frames: c.frames, last: c.frames.at(-1) ?? null, closed: c.closed };
490
490
  let expected = true;
491
491
  try {