@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.
package/dist/src/clients/run.js
CHANGED
|
@@ -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}.
|
|
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
|
|
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}`]),
|
package/dist/src/conformance.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
343
|
+
const start = performance.now();
|
|
343
344
|
const since = heard;
|
|
344
|
-
while (
|
|
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 ?
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
648
|
+
const deadline = performance.now() + 10_000;
|
|
649
649
|
let seen = -1;
|
|
650
|
-
while (
|
|
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.
|
|
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.
|
|
78
|
+
"@volter/world-core": "3.0.69",
|
|
79
79
|
"@volter/world-runtime": "*",
|
|
80
80
|
"playwright": "^1.63.0"
|
|
81
81
|
},
|
package/src/clients/run.ts
CHANGED
|
@@ -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}.
|
|
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
|
|
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}`]),
|
package/src/conformance.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
309
|
-
while (
|
|
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 ?
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
486
|
+
const deadline = performance.now() + 10_000;
|
|
487
487
|
let seen = -1;
|
|
488
|
-
while (
|
|
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 {
|