@volter/twin-standard 1.0.158 → 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/README.md +3 -2
- package/client-sdks/package.json +1 -0
- package/client-sdks/pnpm-lock.yaml +210 -0
- package/dist/client-sdks/package.json +1 -0
- package/dist/client-sdks/pnpm-lock.yaml +210 -0
- package/dist/src/assessment.d.ts +13 -3
- package/dist/src/assessment.js +10 -6
- package/dist/src/browser-assessment.js +16 -13
- package/dist/src/check-sources.js +2 -2
- package/dist/src/clients/harness.d.ts +25 -4
- package/dist/src/clients/harness.js +184 -22
- package/dist/src/clients/jira.js +11 -2
- package/dist/src/clients/resend.first-use.json +2 -2
- package/dist/src/clients/run.js +19 -14
- package/dist/src/conformance.d.ts +3 -4
- package/dist/src/conformance.js +396 -203
- package/dist/src/consumer.d.ts +22 -1
- package/dist/src/consumer.js +91 -26
- package/dist/src/create-pack.d.ts +1 -1
- package/dist/src/create-pack.js +13 -12
- package/dist/src/credential-issuer-import.d.ts +3 -0
- package/dist/src/credential-issuer-import.js +42 -0
- package/dist/src/derive.js +6 -3
- package/dist/src/gate.js +4 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/lanes.d.ts +2 -0
- package/dist/src/lanes.js +7 -0
- package/dist/src/pack-facts.d.ts +3 -0
- package/dist/src/pack-facts.js +36 -1
- package/dist/src/protocol-3.js +13 -8
- package/dist/src/spec-documents.d.ts +4 -0
- package/dist/src/spec-documents.js +110 -3
- 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-walk.js +38 -26
- 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 +7 -7
- package/dist/src/tools/vendor-examples.js +95 -85
- package/dist/src/verdict.js +1 -1
- package/dist/src/walk/behavior-journey.d.ts +22 -14
- package/dist/src/walk/behavior-journey.js +199 -133
- package/dist/src/walk/journey-kit.d.ts +5 -4
- package/dist/src/walk/journey-kit.js +6 -2
- package/dist/src/walk/replay.d.ts +23 -0
- package/dist/src/walk/replay.js +118 -0
- package/dist/src/walk/serve-pack.d.ts +2 -2
- package/dist/src/walk/serve-pack.js +44 -2
- package/dist/src/walk/shape.d.ts +26 -5
- package/dist/src/walk/shape.js +167 -57
- package/dist/src/walk/vendor-fetch.d.ts +7 -2
- package/dist/src/walk/vendor-fetch.js +13 -2
- package/dist/src/walk/walk-database.d.ts +9 -1
- package/dist/src/walk/walk-database.js +35 -13
- package/dist/src/walk/xml-view.d.ts +3 -2
- package/dist/src/walk/xml-view.js +5 -4
- package/package.json +6 -4
- package/src/assessment.ts +12 -8
- package/src/browser-assessment.ts +12 -5
- package/src/check-sources.ts +2 -2
- package/src/clients/harness.ts +142 -19
- package/src/clients/jira.ts +4 -2
- package/src/clients/resend.first-use.json +2 -2
- package/src/clients/run.ts +17 -15
- package/src/conformance.ts +317 -147
- package/src/consumer.ts +83 -21
- package/src/create-pack.ts +13 -12
- package/src/credential-issuer-import.ts +38 -0
- package/src/derive.ts +6 -4
- package/src/gate.ts +3 -0
- package/src/index.ts +1 -1
- package/src/lanes.ts +7 -0
- package/src/pack-facts.ts +25 -2
- package/src/protocol-3.ts +12 -8
- package/src/spec-documents.ts +92 -3
- 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-walk.ts +32 -22
- package/src/tools/life-coverage.ts +2 -2
- package/src/tools/packs-root.ts +3 -3
- package/src/tools/score-pack.ts +8 -8
- package/src/tools/vendor-examples.ts +92 -80
- package/src/verdict.ts +1 -1
- package/src/walk/behavior-journey.ts +158 -109
- package/src/walk/journey-kit.ts +9 -7
- package/src/walk/replay.ts +103 -0
- package/src/walk/serve-pack.ts +30 -5
- package/src/walk/shape.ts +157 -55
- package/src/walk/vendor-fetch.ts +16 -4
- package/src/walk/walk-database.ts +41 -15
- package/src/walk/xml-view.ts +5 -4
|
@@ -11,16 +11,38 @@ import { writeFileSync } from 'node:fs';
|
|
|
11
11
|
/** The World's proxy in front of a pack served on `served`: it ends the caller's TLS (here, none was begun) and forwards
|
|
12
12
|
* each request and socket upgrade to the pack with `x-forwarded-proto: https`, the vendor's Host unchanged, as a World's
|
|
13
13
|
* proxy forwards a caller of the vendor's https host (twin-fetch.ts, twinPublicBase: the links and socket URLs the pack
|
|
14
|
-
* answers are then the vendor's secure ones).
|
|
15
|
-
export async function worldProxy(served) {
|
|
14
|
+
* answers are then the vendor's secure ones). Its stop settles every connection it opened. */
|
|
15
|
+
export async function worldProxy(served, observe) {
|
|
16
|
+
const sockets = new Set();
|
|
17
|
+
const requests = new Set();
|
|
18
|
+
const track = (socket) => { if (sockets.has(socket))
|
|
19
|
+
return; sockets.add(socket); socket.once('close', () => sockets.delete(socket)); };
|
|
16
20
|
const server = createServer((req, res) => {
|
|
17
21
|
const forward = httpRequest({ host: '127.0.0.1', port: served, method: req.method, path: req.url, headers: { ...req.headers, 'x-forwarded-proto': 'https' } }, (answer) => {
|
|
22
|
+
if (observe) {
|
|
23
|
+
answer.on('end', () => {
|
|
24
|
+
const headers = new Headers();
|
|
25
|
+
for (const [name, value] of Object.entries(req.headers))
|
|
26
|
+
if (value !== undefined)
|
|
27
|
+
for (const one of Array.isArray(value) ? value : [value])
|
|
28
|
+
headers.append(name, one);
|
|
29
|
+
observe({ url: `https://${headers.get('x-volter-twin-original-host') ?? headers.get('host')}${req.url}`, headers }, { status: answer.statusCode ?? 502 });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
answer.on('error', () => res.destroy());
|
|
18
33
|
res.writeHead(answer.statusCode ?? 502, answer.headers);
|
|
19
34
|
answer.pipe(res);
|
|
20
35
|
});
|
|
21
|
-
|
|
36
|
+
requests.add(forward);
|
|
37
|
+
forward.once('close', () => requests.delete(forward));
|
|
38
|
+
forward.on('error', () => { if (!res.headersSent)
|
|
39
|
+
res.writeHead(502); res.end(); });
|
|
40
|
+
forward.on('socket', track);
|
|
41
|
+
req.on('aborted', () => forward.destroy());
|
|
42
|
+
res.on('close', () => forward.destroy());
|
|
22
43
|
req.pipe(forward);
|
|
23
44
|
});
|
|
45
|
+
server.on('connection', track);
|
|
24
46
|
server.on('upgrade', (req, socket, head) => {
|
|
25
47
|
const upstream = connect(served, '127.0.0.1', () => {
|
|
26
48
|
const lines = [`${req.method} ${req.url} HTTP/1.1`, ...Object.entries({ ...req.headers, 'x-forwarded-proto': 'https' }).flatMap(([k, v]) => (Array.isArray(v) ? v : [v]).map((x) => `${k}: ${x}`)), '', ''];
|
|
@@ -30,15 +52,115 @@ export async function worldProxy(served) {
|
|
|
30
52
|
upstream.pipe(socket);
|
|
31
53
|
socket.pipe(upstream);
|
|
32
54
|
});
|
|
55
|
+
track(upstream);
|
|
33
56
|
upstream.on('error', () => socket.destroy());
|
|
34
57
|
socket.on('error', () => upstream.destroy());
|
|
58
|
+
socket.on('close', () => upstream.destroy());
|
|
59
|
+
});
|
|
60
|
+
await new Promise((done, reject) => {
|
|
61
|
+
server.once('error', reject);
|
|
62
|
+
server.listen(0, '127.0.0.1', () => { server.off('error', reject); done(); });
|
|
35
63
|
});
|
|
36
|
-
await new Promise((r) => server.listen(0, '127.0.0.1', () => r()));
|
|
37
64
|
server.unref();
|
|
38
|
-
|
|
65
|
+
let stopping;
|
|
66
|
+
return {
|
|
67
|
+
port: server.address().port,
|
|
68
|
+
stop: () => stopping ??= (async () => {
|
|
69
|
+
const closed = new Promise((done, reject) => server.close((error) => error ? reject(error) : done()));
|
|
70
|
+
const pending = [...requests].map((request) => new Promise((done) => { request.once('close', () => done()); request.destroy(); }));
|
|
71
|
+
const connections = [...sockets].map((socket) => new Promise((done) => { socket.once('close', () => done()); socket.destroy(); }));
|
|
72
|
+
await Promise.all([closed, ...pending, ...connections]);
|
|
73
|
+
})(),
|
|
74
|
+
};
|
|
39
75
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
76
|
+
/** A served socket either opens within its five-second handshake or closes under the same owner. */
|
|
77
|
+
export function openWebSocket(socket) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
let settled = false;
|
|
80
|
+
const finish = (opened) => {
|
|
81
|
+
if (settled)
|
|
82
|
+
return;
|
|
83
|
+
settled = true;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
socket.removeEventListener('open', openedSocket);
|
|
86
|
+
socket.removeEventListener('error', failed);
|
|
87
|
+
socket.removeEventListener('close', failed);
|
|
88
|
+
try {
|
|
89
|
+
if (!opened)
|
|
90
|
+
socket.close();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
reject(error);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
resolve(opened);
|
|
97
|
+
};
|
|
98
|
+
const openedSocket = () => finish(true);
|
|
99
|
+
const failed = () => finish(false);
|
|
100
|
+
const timer = setTimeout(failed, 5000);
|
|
101
|
+
socket.addEventListener('open', openedSocket, { once: true });
|
|
102
|
+
socket.addEventListener('error', failed, { once: true });
|
|
103
|
+
socket.addEventListener('close', failed, { once: true });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** One request and its complete body share the check's ten-second lifetime, including caller cancellation. */
|
|
107
|
+
export async function readHttpResponse(fetcher, request) {
|
|
108
|
+
request.signal.throwIfAborted();
|
|
109
|
+
const abort = new AbortController();
|
|
110
|
+
const signal = AbortSignal.any([request.signal, abort.signal]);
|
|
111
|
+
const timer = setTimeout(() => abort.abort(new DOMException('request exceeded 10 s', 'TimeoutError')), 10_000);
|
|
112
|
+
let cancel;
|
|
113
|
+
const interrupted = new Promise((_, reject) => {
|
|
114
|
+
cancel = () => reject(signal.reason);
|
|
115
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
116
|
+
if (signal.aborted)
|
|
117
|
+
cancel();
|
|
118
|
+
});
|
|
119
|
+
let reader;
|
|
120
|
+
try {
|
|
121
|
+
const arriving = fetcher(new Request(request, { signal })).then((response) => {
|
|
122
|
+
if (signal.aborted)
|
|
123
|
+
void response.body?.cancel(signal.reason).catch(() => undefined);
|
|
124
|
+
return response;
|
|
125
|
+
});
|
|
126
|
+
const response = await Promise.race([arriving, interrupted]);
|
|
127
|
+
reader = response.body?.getReader();
|
|
128
|
+
const chunks = [];
|
|
129
|
+
let size = 0;
|
|
130
|
+
let bodyError;
|
|
131
|
+
try {
|
|
132
|
+
if (reader)
|
|
133
|
+
for (;;) {
|
|
134
|
+
const chunk = await Promise.race([reader.read(), interrupted]);
|
|
135
|
+
if (chunk.done)
|
|
136
|
+
break;
|
|
137
|
+
chunks.push(chunk.value);
|
|
138
|
+
size += chunk.value.byteLength;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
bodyError = { message: String(error), cancelled: signal.aborted || error instanceof Error && ['AbortError', 'TimeoutError'].includes(error.name) };
|
|
143
|
+
void reader?.cancel(error).catch(() => undefined);
|
|
144
|
+
}
|
|
145
|
+
const bodyBytes = new Uint8Array(size);
|
|
146
|
+
let offset = 0;
|
|
147
|
+
for (const chunk of chunks) {
|
|
148
|
+
bodyBytes.set(chunk, offset);
|
|
149
|
+
offset += chunk.byteLength;
|
|
150
|
+
}
|
|
151
|
+
const cookies = response.headers.getSetCookie();
|
|
152
|
+
return { status: response.status, headers: { ...Object.fromEntries(response.headers), ...(cookies.length ? { 'set-cookie': cookies.join(', ') } : {}) },
|
|
153
|
+
text: new TextDecoder().decode(bodyBytes), bodyBytes, ...(bodyError ? { bodyError } : {}) };
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
if (cancel)
|
|
158
|
+
signal.removeEventListener('abort', cancel);
|
|
159
|
+
reader?.releaseLock();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** HTTP to a served pack, with the vendor Host and manual redirects. Headers resolve before the streamed body;
|
|
163
|
+
* its caller's abort signal owns the fresh connection until the body ends or is cancelled. */
|
|
42
164
|
export function servedHttpFetch(port) {
|
|
43
165
|
return (async (input, init) => {
|
|
44
166
|
const request = new Request(input, init);
|
|
@@ -49,28 +171,68 @@ export function servedHttpFetch(port) {
|
|
|
49
171
|
const signal = init?.signal ?? (input instanceof Request ? input.signal : request.signal);
|
|
50
172
|
return new Promise((resolve, reject) => {
|
|
51
173
|
const cleanup = () => signal.removeEventListener('abort', abort);
|
|
52
|
-
|
|
174
|
+
let responseFailed;
|
|
175
|
+
const failed = (error) => { cleanup(); if (responseFailed)
|
|
176
|
+
responseFailed(error);
|
|
177
|
+
else
|
|
178
|
+
reject(error); };
|
|
53
179
|
const sent = httpRequest({ host: '127.0.0.1', port, method: request.method, path: `${url.pathname}${url.search}`, headers, agent: false }, (res) => {
|
|
180
|
+
res.pause();
|
|
181
|
+
const answer = new Headers();
|
|
182
|
+
for (const [k, v] of Object.entries(res.headers))
|
|
183
|
+
if (v !== undefined)
|
|
184
|
+
for (const one of Array.isArray(v) ? v : [v])
|
|
185
|
+
answer.append(k, one);
|
|
54
186
|
const chunks = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
187
|
+
let controller;
|
|
188
|
+
let ended = false, cancelled = false, bodyError;
|
|
189
|
+
const pump = () => {
|
|
190
|
+
if (cancelled)
|
|
191
|
+
return;
|
|
192
|
+
while (chunks.length && (controller.desiredSize ?? 0) > 0)
|
|
193
|
+
controller.enqueue(chunks.shift());
|
|
194
|
+
if (chunks.length || (controller.desiredSize ?? 0) <= 0) {
|
|
195
|
+
res.pause();
|
|
196
|
+
return;
|
|
65
197
|
}
|
|
66
|
-
|
|
67
|
-
|
|
198
|
+
// Do not error until the reader has taken queued bytes: an errored web stream discards its queue.
|
|
199
|
+
if (bodyError) {
|
|
200
|
+
cancelled = true;
|
|
201
|
+
controller.error(bodyError);
|
|
68
202
|
}
|
|
203
|
+
else if (ended) {
|
|
204
|
+
cancelled = true;
|
|
205
|
+
controller.close();
|
|
206
|
+
}
|
|
207
|
+
else
|
|
208
|
+
res.resume();
|
|
209
|
+
};
|
|
210
|
+
const body = new ReadableStream({
|
|
211
|
+
start(value) { controller = value; },
|
|
212
|
+
pull() { pump(); },
|
|
213
|
+
cancel() { cancelled = true; chunks.length = 0; cleanup(); res.destroy(); sent.destroy(); },
|
|
69
214
|
});
|
|
70
|
-
|
|
215
|
+
responseFailed = (error) => { bodyError ??= error; cleanup(); pump(); };
|
|
216
|
+
res.on('data', (chunk) => { chunks.push(chunk); pump(); });
|
|
217
|
+
res.on('end', () => { ended = true; cleanup(); pump(); });
|
|
218
|
+
res.on('error', responseFailed);
|
|
219
|
+
res.on('close', () => { if (!res.complete && !cancelled)
|
|
220
|
+
responseFailed(new Error('HTTP response closed before completion')); });
|
|
221
|
+
const noBody = request.method === 'HEAD' || res.statusCode === 204 || res.statusCode === 205 || res.statusCode === 304;
|
|
222
|
+
try {
|
|
223
|
+
resolve(new Response(noBody ? null : body, { status: res.statusCode ?? 0, headers: answer }));
|
|
224
|
+
if (noBody)
|
|
225
|
+
res.resume();
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
cancelled = true;
|
|
229
|
+
cleanup();
|
|
230
|
+
res.destroy();
|
|
231
|
+
reject(error);
|
|
232
|
+
}
|
|
71
233
|
});
|
|
72
234
|
const abort = () => {
|
|
73
|
-
const error = new Error('served HTTP request aborted');
|
|
235
|
+
const error = signal.reason instanceof Error ? signal.reason : new Error('served HTTP request aborted');
|
|
74
236
|
sent.destroy(error);
|
|
75
237
|
failed(error);
|
|
76
238
|
};
|
package/dist/src/clients/jira.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
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
|
+
import { standardPackageFile } from "../package-file.js";
|
|
10
|
+
const { importSdk } = await import(__rewriteRelativeImportExtension(new URL('import.mjs', standardPackageFile('client-sdks/')).href));
|
|
11
|
+
const { Version3Client, HttpException } = await importSdk('jira.js');
|
|
1
12
|
const README = 'https://www.npmjs.com/package/jira.js/v/5.4.0';
|
|
2
13
|
const CREATE = 'https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-post';
|
|
3
14
|
const ADF = 'https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/';
|
|
@@ -12,7 +23,6 @@ async function site(c, slug, apiToken) {
|
|
|
12
23
|
const email = `admin@${slug}.example`;
|
|
13
24
|
const made = await door('/_twin/sites', { name: slug, admin: { name: 'Admin', email, password: 'sdk-case-2026' } });
|
|
14
25
|
const token = apiToken ?? (await door('/_twin/api-token', { site: slug, email, password: 'sdk-case-2026' })).apiToken;
|
|
15
|
-
const { Version3Client } = await import('jira.js');
|
|
16
26
|
const jira = new Version3Client({ host: `https://${slug}.atlassian.net`, authentication: { basic: { email, apiToken: token } }, baseRequestConfig: { httpsAgent: c.agent } });
|
|
17
27
|
return { jira, admin: made.admin };
|
|
18
28
|
}
|
|
@@ -55,7 +65,6 @@ export const cases = [
|
|
|
55
65
|
asks: 'a request with an API token the account never made is refused, and the client throws its HttpException carrying the status',
|
|
56
66
|
source: `${README} "\`HttpException\`: Server responded with an error (includes parsed error details)"`,
|
|
57
67
|
async run(c) {
|
|
58
|
-
const { HttpException } = await import('jira.js');
|
|
59
68
|
const { jira } = await site(c, 'sdk-refused', 'ATATT-not-a-token-this-account-made');
|
|
60
69
|
try {
|
|
61
70
|
await jira.projects.searchProjects({ maxResults: 50 });
|
|
@@ -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/dist/src/clients/run.js
CHANGED
|
@@ -20,19 +20,24 @@ if (!Array.isArray(cases) || cases.length === 0)
|
|
|
20
20
|
throw new Error(`${job.vendor} client module declares no cases; there is no SDK execution evidence`);
|
|
21
21
|
// every connection a case makes goes through the World's proxy, as an application's does in a World
|
|
22
22
|
const proxy = await worldProxy(job.port);
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
require(
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
try {
|
|
24
|
+
// Bind only this artifact's declared vendor hosts to the assessed server. The enclosing World's
|
|
25
|
+
// injector stays active, and untwinned destinations keep its refusal policy.
|
|
26
|
+
const require = createRequire(import.meta.url);
|
|
27
|
+
require('@volter/world-core/pack-facts').extendPackFacts([job.packFacts]);
|
|
28
|
+
const facts = JSON.parse(readFileSync(job.packFacts, 'utf8')).packs[job.vendor];
|
|
29
|
+
const keys = [...new Set((facts.hosts ?? []).map((rule) => rule.key ?? job.vendor))];
|
|
30
|
+
require('@volter/world-core/inject').install(Object.fromEntries(keys.map((key) => [key, `http://127.0.0.1:${proxy.port}`])));
|
|
31
|
+
const context = clientContext(proxy.port, job.host, job.env, job.scenarioPath, job.raw ? job.port : undefined);
|
|
32
|
+
for (const c of cases) {
|
|
33
|
+
const failures = await Promise.race([
|
|
34
|
+
c.run(context).catch((e) => [`the case threw: ${e.message}`]),
|
|
35
|
+
new Promise((r) => setTimeout(() => r(['no outcome in 30 s']), 30_000)),
|
|
36
|
+
]);
|
|
37
|
+
process.stdout.write(`${JSON.stringify({ id: c.id, asks: c.asks, failures })}\n`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await proxy.stop();
|
|
37
42
|
}
|
|
38
43
|
process.exit(0);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ConsumerResult } from './consumer.js';
|
|
1
2
|
export type ConformanceResult = {
|
|
2
3
|
id: string;
|
|
3
4
|
asks: string;
|
|
@@ -5,13 +6,10 @@ export type ConformanceResult = {
|
|
|
5
6
|
note?: string;
|
|
6
7
|
};
|
|
7
8
|
/** The files conformance reads from a unit: its manifest, its spec's record, its vendor's life and its decision table
|
|
8
|
-
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published())
|
|
9
|
-
* pack's standing counts it in the form only when every unit holds them (twin-world's scripts/pack-standing.ts, by
|
|
10
|
-
* unitFilesMissing): the one list of both. */
|
|
9
|
+
* (a lane's journeys are its own `journeys/` or else its vendor's). A release publishes each (published()). */
|
|
11
10
|
export declare const UNIT_FILES: readonly ["src/manifest.ts", "spec/SOURCE.md", "journeys/customer-life.json", "journeys/decisions.json"];
|
|
12
11
|
/** The published package must carry every conformance input its units hold, including lanes. */
|
|
13
12
|
export declare function unpublishedUnitFiles(unit: string): string[];
|
|
14
|
-
export declare function unitFilesMissing(unit: string): string[];
|
|
15
13
|
type Owners = Record<string, string>;
|
|
16
14
|
type Decision = {
|
|
17
15
|
operation: string;
|
|
@@ -33,5 +31,6 @@ export declare function decisionTableProblems(unit: string, byUnit: Record<strin
|
|
|
33
31
|
/** `onCheck` hears each check's id as it starts, so a caller can show where a long run stands. */
|
|
34
32
|
export declare function conformance(dir: string, options?: {
|
|
35
33
|
onCheck?: (id: string) => void;
|
|
34
|
+
consumer?: ConsumerResult;
|
|
36
35
|
}): Promise<ConformanceResult[]>;
|
|
37
36
|
export {};
|