@volter/twin-standard 1.0.148 → 1.0.150
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 +12 -3
- package/client-sdks/bun.lock +0 -32
- package/client-sdks/package.json +0 -2
- package/dist/client-sdks/bun.lock +0 -32
- package/dist/client-sdks/package.json +0 -2
- package/dist/src/assessment.d.ts +6 -4
- package/dist/src/assessment.js +2 -1
- package/dist/src/cli.js +21 -6
- package/dist/src/clients/harness.d.ts +3 -1
- package/dist/src/clients/harness.js +4 -2
- package/dist/src/clients/jira.d.ts +2 -0
- package/dist/src/clients/jira.js +71 -0
- package/dist/src/clients/polar.d.ts +2 -0
- package/dist/src/clients/polar.js +73 -0
- package/dist/src/clients/resend.js +38 -0
- package/dist/src/clients/run.js +1 -1
- package/dist/src/clients/sentry.js +1 -26
- package/dist/src/clients/smtp.d.ts +2 -0
- package/dist/src/clients/smtp.js +54 -0
- package/dist/src/clients/stripe.js +1 -1
- package/dist/src/clients/volteridentity.js +4 -3
- package/dist/src/clients/x.d.ts +2 -0
- package/dist/src/clients/x.js +64 -0
- package/dist/src/conformance.d.ts +4 -3
- package/dist/src/conformance.js +293 -8
- package/dist/src/consumer.js +5 -1
- package/dist/src/create-pack.d.ts +5 -0
- package/dist/src/create-pack.js +38 -4
- package/dist/src/derive.js +21 -13
- package/dist/src/gate.d.ts +1 -0
- package/dist/src/gate.js +47 -4
- package/dist/src/grade.d.ts +1 -0
- package/dist/src/grade.js +1 -1
- package/dist/src/pack-facts.d.ts +9 -1
- package/dist/src/pack-facts.js +11 -2
- package/dist/src/pack-files.js +2 -1
- package/dist/src/protocol-3.js +40 -6
- package/dist/src/spec-ir-client.js +3 -2
- package/dist/src/spec-ir-commands.d.ts +73 -0
- package/dist/src/spec-ir-commands.js +63 -0
- package/dist/src/spec-ir.d.ts +2 -0
- package/dist/src/spec-ir.js +43 -0
- package/dist/src/tools/judge-brief.d.ts +1 -0
- package/dist/src/tools/judge-brief.js +67 -0
- package/dist/src/tools/life-coverage-walk.d.ts +2 -0
- package/dist/src/tools/life-coverage-walk.js +220 -0
- package/dist/src/tools/life-coverage.d.ts +2 -0
- package/dist/src/tools/life-coverage.js +271 -0
- package/dist/src/tools/packs-root.d.ts +9 -0
- package/dist/src/tools/packs-root.js +17 -0
- package/dist/src/tools/run-tool.d.ts +1 -0
- package/dist/src/tools/run-tool.js +18 -0
- package/dist/src/tools/score-pack.d.ts +2 -0
- package/dist/src/tools/score-pack.js +264 -0
- package/dist/src/tools/vendor-examples.d.ts +2 -0
- package/dist/src/tools/vendor-examples.js +1493 -0
- package/dist/src/types.d.ts +2 -0
- package/dist/src/verdict.js +1 -1
- package/dist/src/walk/behavior-journey.d.ts +6 -0
- package/dist/src/walk/behavior-journey.js +108 -8
- package/dist/src/walk/journey-kit.d.ts +23 -1
- package/dist/src/walk/journey-kit.js +7 -2
- package/dist/src/walk/rendered-scalar.d.ts +3 -0
- package/dist/src/walk/rendered-scalar.js +23 -0
- package/dist/src/walk/sse-example.js +45 -0
- package/package.json +2 -2
- package/src/assessment.ts +2 -1
- package/src/cli.ts +15 -2
- package/src/clients/harness.ts +4 -2
- package/src/clients/jira.ts +73 -0
- package/src/clients/polar.ts +73 -0
- package/src/clients/resend.ts +32 -0
- package/src/clients/run.ts +2 -2
- package/src/clients/sentry.ts +1 -25
- package/src/clients/smtp.ts +49 -0
- package/src/clients/stripe.ts +1 -1
- package/src/clients/volteridentity.ts +4 -3
- package/src/clients/x.ts +66 -0
- package/src/conformance.ts +226 -10
- package/src/consumer.ts +5 -1
- package/src/create-pack.ts +35 -5
- package/src/derive.ts +21 -14
- package/src/gate.ts +40 -6
- package/src/grade.ts +2 -2
- package/src/pack-facts.ts +10 -3
- package/src/pack-files.ts +2 -1
- package/src/protocol-3.ts +32 -6
- package/src/spec-ir-client.ts +3 -2
- package/src/spec-ir-commands.ts +81 -0
- package/src/spec-ir.ts +31 -0
- package/src/tools/judge-brief.ts +63 -0
- package/src/tools/life-coverage-walk.ts +173 -0
- package/src/tools/life-coverage.ts +237 -0
- package/src/tools/packs-root.ts +21 -0
- package/src/tools/run-tool.ts +10 -0
- package/src/tools/score-pack.ts +244 -0
- package/src/tools/vendor-examples.ts +1250 -0
- package/src/types.ts +2 -0
- package/src/verdict.ts +1 -1
- package/src/walk/behavior-journey.ts +65 -4
- package/src/walk/journey-kit.ts +21 -3
- package/src/walk/rendered-scalar.ts +17 -0
- package/src/walk/sse-example.ts +37 -0
|
@@ -58,4 +58,42 @@ export const cases = [
|
|
|
58
58
|
return answer.data === null && answer.error?.statusCode === 422 && answer.error?.name === 'missing_required_field' ? [] : [`missing sender returned ${JSON.stringify(answer)}`];
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: 'unverified-domain-validation-error',
|
|
63
|
+
asks: 'a send from a domain the team never verified comes back as the SDK error validation_error (403), with no data',
|
|
64
|
+
source: `${ERRORS} "To send emails to other recipients, please verify a domain at resend.com/domains, and change the \`from\` address to an email using this domain."`,
|
|
65
|
+
async run(c) {
|
|
66
|
+
const resend = client(c);
|
|
67
|
+
const sent = await resend.emails.send({ from: 'Someone <someone@never-verified.example>', to: ['client@example.org'], subject: 'Not from a verified domain', text: 'Refused.' });
|
|
68
|
+
const failures = [];
|
|
69
|
+
if (sent.data !== null)
|
|
70
|
+
failures.push(`the send answered data ${JSON.stringify(sent.data)}`);
|
|
71
|
+
const error = sent.error;
|
|
72
|
+
if (error?.name !== 'validation_error')
|
|
73
|
+
failures.push(`the error is ${JSON.stringify(error)}, not validation_error`);
|
|
74
|
+
else if (error.statusCode !== 403)
|
|
75
|
+
failures.push(`the validation_error's status is ${String(error.statusCode)}, not 403`);
|
|
76
|
+
return failures;
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'scheduled-send-waits',
|
|
81
|
+
asks: "a send scheduled for later (the SDK's scheduledAt, sent as scheduled_at) waits: it reads back scheduled, at its time",
|
|
82
|
+
source: `${SEND} "Schedule email to be sent later."`,
|
|
83
|
+
async run(c) {
|
|
84
|
+
const resend = client(c);
|
|
85
|
+
const at = '2030-01-01T08:00:00.000Z';
|
|
86
|
+
const sent = await resend.emails.send({ from: 'Acme <onboarding@resend.dev>', to: ['owner@world.test'], subject: 'Later', text: 'In good time.', scheduledAt: at });
|
|
87
|
+
if (sent.error || !sent.data?.id)
|
|
88
|
+
return [`the send answered ${JSON.stringify(sent).slice(0, 200)}`];
|
|
89
|
+
const read = await resend.emails.get(sent.data.id);
|
|
90
|
+
const email = read.data;
|
|
91
|
+
const failures = [];
|
|
92
|
+
if (email?.last_event !== 'scheduled')
|
|
93
|
+
failures.push(`the scheduled email's last_event is ${String(email?.last_event)}`);
|
|
94
|
+
if (email?.scheduled_at === undefined || email.scheduled_at === null || Date.parse(email.scheduled_at) !== Date.parse(at))
|
|
95
|
+
failures.push(`it reads back scheduled_at ${String(email?.scheduled_at)}`);
|
|
96
|
+
return failures;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
61
99
|
];
|
package/dist/src/clients/run.js
CHANGED
|
@@ -27,7 +27,7 @@ require('@volter/world-core/pack-facts').extendPackFacts([job.packFacts]);
|
|
|
27
27
|
const facts = JSON.parse(readFileSync(job.packFacts, 'utf8')).packs[job.vendor];
|
|
28
28
|
const keys = [...new Set((facts.hosts ?? []).map((rule) => rule.key ?? job.vendor))];
|
|
29
29
|
require('@volter/world-core/inject').install(Object.fromEntries(keys.map((key) => [key, `http://127.0.0.1:${proxy}`])));
|
|
30
|
-
const context = clientContext(proxy, job.host, job.env, job.scenarioPath);
|
|
30
|
+
const context = clientContext(proxy, job.host, job.env, job.scenarioPath, job.raw ? job.port : undefined);
|
|
31
31
|
for (const c of cases) {
|
|
32
32
|
const failures = await Promise.race([
|
|
33
33
|
c.run(context).catch((e) => [`the case threw: ${e.message}`]),
|
|
@@ -28,7 +28,6 @@ async function sdkOf(name) {
|
|
|
28
28
|
const pins = [
|
|
29
29
|
['rallly', '@sentry/node', '10.38.0'],
|
|
30
30
|
['twenty', 'sentry-node-twenty', '10.73.0'],
|
|
31
|
-
['postiz', 'sentry-node-postiz', '10.56.0'],
|
|
32
31
|
['cal-api', 'sentry-node-cal', '9.46.0'],
|
|
33
32
|
['cal-web', 'sentry-node-cal-web', '10.33.0'],
|
|
34
33
|
];
|
|
@@ -76,7 +75,7 @@ export const cases = pins.map(([application, name, version]) => ({
|
|
|
76
75
|
return failures;
|
|
77
76
|
},
|
|
78
77
|
}));
|
|
79
|
-
for (const [name, version] of [['@sentry/browser', '10.38.0'], ['sentry-browser-twenty', '10.59.0']
|
|
78
|
+
for (const [name, version] of [['@sentry/browser', '10.38.0'], ['sentry-browser-twenty', '10.59.0']])
|
|
80
79
|
cases.push({
|
|
81
80
|
id: 'browser-' + version + '-dsn-query-auth-and-session-envelope',
|
|
82
81
|
asks: 'the browser fetch transport uses DSN query authentication and flushes event and session envelopes',
|
|
@@ -142,27 +141,3 @@ cases.push({
|
|
|
142
141
|
return drained && attempts === 2 && dropped.includes('ratelimit_backoff') ? [] : ['transport retried 503, failed to drain, or sent a rate-limited event'];
|
|
143
142
|
},
|
|
144
143
|
});
|
|
145
|
-
cases.push({
|
|
146
|
-
id: 'postiz-feedback-attribution',
|
|
147
|
-
asks: 'captureFeedback preserves the widget message, explicit name/email, associated event and scoped user',
|
|
148
|
-
source: 'https://unpkg.com/@sentry/core@10.56.0/build/esm/feedback.js "associated_event_id: associatedEventId"',
|
|
149
|
-
async run(c) {
|
|
150
|
-
const sdk = await sdkOf('sentry-browser-postiz');
|
|
151
|
-
const client = new sdk.BrowserClient({ dsn: c.env.SENTRY_DSN, integrations: [], stackParser: sdk.defaultStackParser,
|
|
152
|
-
transport: (options) => sdk.createTransport(options, async (request) => {
|
|
153
|
-
const answer = await c.fetch(options.url, { method: 'POST', body: request.body });
|
|
154
|
-
return { statusCode: answer.status, headers: {} };
|
|
155
|
-
}) });
|
|
156
|
-
let id = '';
|
|
157
|
-
sdk.withScope((scope) => {
|
|
158
|
-
scope.setClient(client);
|
|
159
|
-
scope.setUser({ id: 'feedback-author' });
|
|
160
|
-
id = sdk.captureFeedback({ message: 'The scheduling button is hard to find', name: 'Priya', email: 'priya@harbourline.test', associatedEventId: '00000000000000000000000000000002' }, {}, scope);
|
|
161
|
-
});
|
|
162
|
-
const drained = await client.flush(10_000);
|
|
163
|
-
const event = await read(c, id, await token(c));
|
|
164
|
-
await client.close(10_000);
|
|
165
|
-
const feedback = event.contexts?.feedback;
|
|
166
|
-
return drained && event.type === 'feedback' && event.user?.id === 'feedback-author' && feedback?.name === 'Priya' && feedback?.contact_email === 'priya@harbourline.test' && feedback?.associated_event_id === '00000000000000000000000000000002' ? [] : ['feedback attribution or associated event was lost'];
|
|
167
|
-
},
|
|
168
|
-
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const RFC5321 = 'https://www.rfc-editor.org/rfc/rfc5321';
|
|
2
|
+
async function transport(c) {
|
|
3
|
+
const nodemailer = (await import('nodemailer')).default;
|
|
4
|
+
return nodemailer.createTransport({
|
|
5
|
+
host: c.host, port: 25, secure: false, ignoreTLS: true,
|
|
6
|
+
getSocket: (_options, callback) => callback(null, { connection: c.connect() }),
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export const cases = [
|
|
10
|
+
{
|
|
11
|
+
id: 'verify-greets',
|
|
12
|
+
asks: "nodemailer's verify opens the session, greets with EHLO and quits: the relay answers it as an SMTP server",
|
|
13
|
+
source: `${RFC5321} "These commands are used to identify the SMTP client to the SMTP server."`,
|
|
14
|
+
async run(c) {
|
|
15
|
+
const t = await transport(c);
|
|
16
|
+
try {
|
|
17
|
+
return (await t.verify()) === true ? [] : ['verify resolved without true'];
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return [`verify failed: ${String(error).slice(0, 200)}`];
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
t.close();
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'transaction-to-two-recipients',
|
|
29
|
+
asks: 'a message with text and HTML to two recipients runs MAIL, a RCPT for each, then DATA: both accepted, none rejected, the data answered 250',
|
|
30
|
+
source: `${RFC5321} "Once started, a mail transaction consists of a transaction beginning command, one or more RCPT commands, and a DATA command, in that order."`,
|
|
31
|
+
async run(c) {
|
|
32
|
+
const t = await transport(c);
|
|
33
|
+
try {
|
|
34
|
+
const info = await t.sendMail({ from: 'Polls <polls@case.example>', to: ['an@case.example', 'bo@case.example'], subject: 'Pick a date', text: 'Please pick a date.', html: '<p>Please pick a date.</p>' });
|
|
35
|
+
const failures = [];
|
|
36
|
+
if (JSON.stringify([...info.accepted].sort()) !== JSON.stringify(['an@case.example', 'bo@case.example']))
|
|
37
|
+
failures.push(`accepted ${JSON.stringify(info.accepted)}`);
|
|
38
|
+
if (info.rejected.length)
|
|
39
|
+
failures.push(`rejected ${JSON.stringify(info.rejected)}`);
|
|
40
|
+
if (!info.response.startsWith('250'))
|
|
41
|
+
failures.push(`the data was answered ${info.response}`);
|
|
42
|
+
if (!info.messageId)
|
|
43
|
+
failures.push('no message id');
|
|
44
|
+
return failures;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return [`the send failed: ${String(error).slice(0, 200)}`];
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
t.close();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Stripe's own client (stripe-node), driving the stripe pack as its applications do (Cal.com, Dub,
|
|
1
|
+
// Stripe's own client (stripe-node), driving the stripe pack as its applications do (Cal.com, Dub, Rallly and
|
|
2
2
|
// Twenty call it): an endpoint registered through the API and the event Stripe sends it verified by constructEvent, as an
|
|
3
3
|
// application's webhook handler verifies it; a customer list read page by page by the client's auto-pagination; a retry
|
|
4
4
|
// sent with its idempotency key; the error a missing object raises; and the customer portal a billing page opens.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @volter/identity 0.4.1, as Volter Harness uses it; discovery and token verification are also the APIs Open Autonomy
|
|
2
|
-
// uses in 0.1.0. These cases are owned by conformance and are not run during pack authoring.
|
|
2
|
+
// uses in 0.1.0. These cases are owned by conformance and are not run during pack authoring. They import nothing of the
|
|
3
|
+
// kernel: the runner is Node's own type stripping, which the kernel's parameter properties do not pass.
|
|
3
4
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
5
|
import { tmpdir } from 'node:os';
|
|
5
6
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
7
8
|
import { resolveIdentity, verifyIdentityToken, tokenFor, readSharedSignIn, SHARED_CLIENTS } from '@volter/identity';
|
|
8
9
|
const INDEX = 'https://unpkg.com/@volter/identity@0.4.1/dist/index.js';
|
|
9
10
|
const SHARED = 'https://unpkg.com/@volter/identity@0.4.1/dist/shared.js';
|
|
@@ -34,7 +35,7 @@ async function setup(c, name, shared = false) {
|
|
|
34
35
|
await send(c, `/admin/clients/${registered.client_id}/resources`, { resource: 'https://teams.sdk.example' }, operator);
|
|
35
36
|
}
|
|
36
37
|
const verifier = 'official-sdk-case-verifier-with-enough-entropy-for-s256';
|
|
37
|
-
const query = new URLSearchParams({ client_id: registered.client_id, redirect_uri: callback, response_type: 'code', scope: 'openid profile email offline_access', code_challenge:
|
|
38
|
+
const query = new URLSearchParams({ client_id: registered.client_id, redirect_uri: callback, response_type: 'code', scope: 'openid profile email offline_access', code_challenge: createHash('sha256').update(verifier).digest('base64url'), code_challenge_method: 'S256' });
|
|
38
39
|
const authorized = await send(c, `/api/auth/oauth2/authorize?${query}`, undefined, { cookie });
|
|
39
40
|
const code = new URL(authorized.headers.get('location')).searchParams.get('code');
|
|
40
41
|
const form = new URLSearchParams({ client_id: registered.client_id, redirect_uri: callback, grant_type: 'authorization_code', code, code_verifier: verifier, ...(registered.client_secret ? { client_secret: registered.client_secret } : {}) });
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const CREATE = 'https://docs.x.com/x-api/posts/create-post';
|
|
2
|
+
const ME = 'https://docs.x.com/x-api/users/get-my-user';
|
|
3
|
+
const README = 'https://www.npmjs.com/package/twitter-api-v2/v/1.29.1';
|
|
4
|
+
/** An account named `username` and an access token for it with `scopes`; the client on that token. */
|
|
5
|
+
async function signedIn(c, username, scopes, token = `case-${username}-token`) {
|
|
6
|
+
const door = async (path, body) => {
|
|
7
|
+
const answer = await c.fetch(`https://${c.host}${path}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
8
|
+
if (answer.status >= 300)
|
|
9
|
+
throw new Error(`${path} answered ${answer.status}`);
|
|
10
|
+
return (await answer.json()).data;
|
|
11
|
+
};
|
|
12
|
+
const account = await door('/_twin/accounts', { username, name: 'Case Account' });
|
|
13
|
+
await door('/_twin/tokens', { token, account_id: account.id, scopes });
|
|
14
|
+
const { TwitterApi } = await import('twitter-api-v2');
|
|
15
|
+
return { client: new TwitterApi(token, { httpAgent: c.agent }), account };
|
|
16
|
+
}
|
|
17
|
+
export const cases = [
|
|
18
|
+
{
|
|
19
|
+
id: 'post-and-read-back',
|
|
20
|
+
asks: "a post made through the SDK answers its id and text, and reads back by id with that text, its author the signed-in account",
|
|
21
|
+
source: `${CREATE} "Create Posts"`,
|
|
22
|
+
async run(c) {
|
|
23
|
+
const { client, account } = await signedIn(c, 'case_poster', ['tweet.read', 'tweet.write', 'users.read']);
|
|
24
|
+
const made = await client.v2.tweet('Shipping the case today');
|
|
25
|
+
const failures = [];
|
|
26
|
+
if (!made.data?.id || made.data.text !== 'Shipping the case today')
|
|
27
|
+
failures.push(`tweet answered ${JSON.stringify(made)}`);
|
|
28
|
+
const read = await client.v2.singleTweet(made.data.id, { 'tweet.fields': ['author_id'] });
|
|
29
|
+
if (read.data?.text !== 'Shipping the case today')
|
|
30
|
+
failures.push(`the post reads back text ${JSON.stringify(read.data?.text)}`);
|
|
31
|
+
if (read.data?.author_id !== account.id)
|
|
32
|
+
failures.push(`the post reads back author ${String(read.data?.author_id)}, not ${account.id}`);
|
|
33
|
+
return failures;
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'me-is-the-signed-in-account',
|
|
38
|
+
asks: "v2.me answers the account the token was issued to, by its id and username (an app's account check after sign-in)",
|
|
39
|
+
source: `${ME} "Get Users Me"`,
|
|
40
|
+
async run(c) {
|
|
41
|
+
const { client, account } = await signedIn(c, 'case_me', ['tweet.read', 'users.read']);
|
|
42
|
+
const me = await client.v2.me();
|
|
43
|
+
return me.data?.id === account.id && me.data.username === 'case_me' ? [] : [`me answered ${JSON.stringify(me)}`];
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'unknown-token-api-response-error',
|
|
48
|
+
asks: 'a token X never issued is refused, and the client throws its ApiResponseError carrying the 401',
|
|
49
|
+
source: `${README} "Typed errors, meaningful error messages, error enumerations for both v1.1 and v2"`,
|
|
50
|
+
async run(c) {
|
|
51
|
+
const { TwitterApi, ApiResponseError } = await import('twitter-api-v2');
|
|
52
|
+
const client = new TwitterApi('case-never-issued-token', { httpAgent: c.agent });
|
|
53
|
+
try {
|
|
54
|
+
await client.v2.me();
|
|
55
|
+
return ['the never-issued token was accepted'];
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (!(error instanceof ApiResponseError))
|
|
59
|
+
return [`the refusal is not an ApiResponseError: ${String(error).slice(0, 160)}`];
|
|
60
|
+
return error.code === 401 ? [] : [`the refusal's code is ${error.code}, not 401`];
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
@@ -11,8 +11,6 @@ export type ConformanceResult = {
|
|
|
11
11
|
export declare const UNIT_FILES: readonly ["src/manifest.ts", "spec/SOURCE.md", "journeys/customer-life.json", "journeys/decisions.json"];
|
|
12
12
|
/** The published package must carry every conformance input its units hold, including lanes. */
|
|
13
13
|
export declare function unpublishedUnitFiles(unit: string): string[];
|
|
14
|
-
/** Each of UNIT_FILES a unit lacks where conformance reads it: the manifest of every unit, the life at its vendor's root
|
|
15
|
-
* (lifeServed), the spec's record and the decision table of a unit with a spec (decided()). */
|
|
16
14
|
export declare function unitFilesMissing(unit: string): string[];
|
|
17
15
|
type Owners = Record<string, string>;
|
|
18
16
|
type Decision = {
|
|
@@ -32,5 +30,8 @@ export declare function decisionTableProblems(unit: string, byUnit: Record<strin
|
|
|
32
30
|
building?: boolean;
|
|
33
31
|
}): string[];
|
|
34
32
|
/** Every check over the pack at `dir`, in order. */
|
|
35
|
-
|
|
33
|
+
/** `onCheck` hears each check's id as it starts, so a caller can show where a long run stands. */
|
|
34
|
+
export declare function conformance(dir: string, options?: {
|
|
35
|
+
onCheck?: (id: string) => void;
|
|
36
|
+
}): Promise<ConformanceResult[]>;
|
|
36
37
|
export {};
|