@volter/twin-standard 1.0.30 → 1.0.32

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.
Files changed (149) hide show
  1. package/dist/src/archive-source.d.ts +13 -0
  2. package/dist/src/archive-source.js +176 -0
  3. package/dist/src/check-sources.d.ts +19 -2
  4. package/dist/src/check-sources.js +95 -37
  5. package/dist/src/cli.js +52 -9
  6. package/dist/src/clients/ai-gateway.d.ts +1 -0
  7. package/dist/src/clients/ai-gateway.js +3 -0
  8. package/dist/src/clients/anthropic-messages.d.ts +10 -0
  9. package/dist/src/clients/anthropic-messages.js +52 -0
  10. package/dist/src/clients/anthropic.d.ts +1 -0
  11. package/dist/src/clients/anthropic.js +3 -0
  12. package/dist/src/clients/aws.d.ts +2 -0
  13. package/dist/src/clients/aws.js +137 -0
  14. package/dist/src/clients/cerebras.d.ts +1 -0
  15. package/dist/src/clients/cerebras.js +3 -0
  16. package/dist/src/clients/clerk.d.ts +2 -0
  17. package/dist/src/clients/clerk.js +99 -0
  18. package/dist/src/clients/cloudflare.d.ts +2 -0
  19. package/dist/src/clients/cloudflare.js +86 -0
  20. package/dist/src/clients/elevenlabs.d.ts +2 -0
  21. package/dist/src/clients/elevenlabs.js +39 -0
  22. package/dist/src/clients/gemini.d.ts +2 -0
  23. package/dist/src/clients/gemini.js +92 -0
  24. package/dist/src/clients/github.d.ts +2 -0
  25. package/dist/src/clients/github.js +71 -0
  26. package/dist/src/clients/groq.d.ts +1 -0
  27. package/dist/src/clients/groq.js +3 -0
  28. package/dist/src/clients/harness.d.ts +36 -0
  29. package/dist/src/clients/harness.js +74 -0
  30. package/dist/src/clients/merge.d.ts +1 -0
  31. package/dist/src/clients/merge.js +8 -0
  32. package/dist/src/clients/openai-compatible.d.ts +9 -0
  33. package/dist/src/clients/openai-compatible.js +51 -0
  34. package/dist/src/clients/openai.d.ts +2 -0
  35. package/dist/src/clients/openai.js +65 -0
  36. package/dist/src/clients/openrouter.d.ts +1 -0
  37. package/dist/src/clients/openrouter.js +3 -0
  38. package/dist/src/clients/perplexity.d.ts +1 -0
  39. package/dist/src/clients/perplexity.js +3 -0
  40. package/dist/src/clients/planetscale.d.ts +2 -0
  41. package/dist/src/clients/planetscale.js +109 -0
  42. package/dist/src/clients/posthog.d.ts +2 -0
  43. package/dist/src/clients/posthog.js +145 -0
  44. package/dist/src/clients/run.d.ts +1 -0
  45. package/dist/src/clients/run.js +25 -0
  46. package/dist/src/clients/sentry.d.ts +2 -0
  47. package/dist/src/clients/sentry.js +167 -0
  48. package/dist/src/clients/slack.d.ts +2 -0
  49. package/dist/src/clients/slack.js +107 -0
  50. package/dist/src/clients/stripe.d.ts +2 -0
  51. package/dist/src/clients/stripe.js +161 -0
  52. package/dist/src/clients/tinybird.d.ts +2 -0
  53. package/dist/src/clients/tinybird.js +71 -0
  54. package/dist/src/clients/tunnel.d.ts +2 -0
  55. package/dist/src/clients/tunnel.js +99 -0
  56. package/dist/src/clients/upstash.d.ts +2 -0
  57. package/dist/src/clients/upstash.js +143 -0
  58. package/dist/src/clients/volteridentity.d.ts +2 -0
  59. package/dist/src/clients/volteridentity.js +128 -0
  60. package/dist/src/clients/xai.d.ts +2 -0
  61. package/dist/src/clients/xai.js +23 -0
  62. package/dist/src/conformance.d.ts +33 -0
  63. package/dist/src/conformance.js +520 -0
  64. package/dist/src/derive.js +113 -22
  65. package/dist/src/gate.js +22 -16
  66. package/dist/src/index.d.ts +5 -3
  67. package/dist/src/index.js +5 -3
  68. package/dist/src/lanes.d.ts +2 -0
  69. package/dist/src/lanes.js +7 -0
  70. package/dist/src/p3-rules.js +0 -2
  71. package/dist/src/protocol-3.d.ts +1 -0
  72. package/dist/src/protocol-3.js +76 -30
  73. package/dist/src/published.d.ts +6 -0
  74. package/dist/src/published.js +65 -11
  75. package/dist/src/spec-documents.js +2 -1
  76. package/dist/src/spec-ir-client.d.ts +5 -1
  77. package/dist/src/spec-ir-client.js +12 -2
  78. package/dist/src/spec-ir.d.ts +7 -2
  79. package/dist/src/spec-ir.js +42 -10
  80. package/dist/src/verdict.d.ts +14 -0
  81. package/dist/src/verdict.js +30 -0
  82. package/dist/src/walk/behavior-journey.d.ts +47 -0
  83. package/dist/src/walk/behavior-journey.js +1138 -0
  84. package/dist/src/walk/git-client.d.ts +26 -0
  85. package/dist/src/walk/git-client.js +139 -0
  86. package/dist/src/walk/journey-kit.d.ts +445 -0
  87. package/dist/src/walk/journey-kit.js +743 -0
  88. package/dist/src/walk/shape.d.ts +54 -0
  89. package/dist/src/walk/shape.js +210 -0
  90. package/dist/src/walk/sse-example.d.ts +2 -0
  91. package/dist/src/walk/sse-example.js +32 -0
  92. package/dist/src/walk/vendor-fetch.d.ts +20 -0
  93. package/dist/src/walk/vendor-fetch.js +92 -0
  94. package/dist/src/walk/walk-database.d.ts +12 -0
  95. package/dist/src/walk/walk-database.js +96 -0
  96. package/dist/src/walk/xml-view.d.ts +2 -0
  97. package/dist/src/walk/xml-view.js +55 -0
  98. package/package.json +39 -3
  99. package/src/archive-source.ts +141 -0
  100. package/src/check-sources.ts +83 -33
  101. package/src/cli.ts +40 -8
  102. package/src/clients/ai-gateway.ts +4 -0
  103. package/src/clients/anthropic-messages.ts +60 -0
  104. package/src/clients/anthropic.ts +4 -0
  105. package/src/clients/aws.ts +119 -0
  106. package/src/clients/cerebras.ts +4 -0
  107. package/src/clients/clerk.ts +97 -0
  108. package/src/clients/cloudflare.ts +80 -0
  109. package/src/clients/elevenlabs.ts +38 -0
  110. package/src/clients/gemini.ts +89 -0
  111. package/src/clients/github.ts +67 -0
  112. package/src/clients/groq.ts +4 -0
  113. package/src/clients/harness.ts +101 -0
  114. package/src/clients/merge.ts +9 -0
  115. package/src/clients/openai-compatible.ts +59 -0
  116. package/src/clients/openai.ts +66 -0
  117. package/src/clients/openrouter.ts +4 -0
  118. package/src/clients/perplexity.ts +4 -0
  119. package/src/clients/planetscale.ts +98 -0
  120. package/src/clients/posthog.ts +132 -0
  121. package/src/clients/run.ts +18 -0
  122. package/src/clients/sentry.ts +157 -0
  123. package/src/clients/slack.ts +98 -0
  124. package/src/clients/stripe.ts +129 -0
  125. package/src/clients/tinybird.ts +69 -0
  126. package/src/clients/tunnel.ts +79 -0
  127. package/src/clients/upstash.ts +137 -0
  128. package/src/clients/volteridentity.ts +108 -0
  129. package/src/clients/xai.ts +25 -0
  130. package/src/conformance.ts +446 -0
  131. package/src/derive.ts +87 -16
  132. package/src/gate.ts +20 -14
  133. package/src/index.ts +5 -3
  134. package/src/lanes.ts +7 -0
  135. package/src/p3-rules.ts +0 -2
  136. package/src/protocol-3.ts +71 -27
  137. package/src/published.ts +59 -11
  138. package/src/spec-documents.ts +2 -1
  139. package/src/spec-ir-client.ts +15 -4
  140. package/src/spec-ir.ts +43 -5
  141. package/src/verdict.ts +28 -0
  142. package/src/walk/behavior-journey.ts +806 -0
  143. package/src/walk/git-client.ts +138 -0
  144. package/src/walk/journey-kit.ts +858 -0
  145. package/src/walk/shape.ts +208 -0
  146. package/src/walk/sse-example.ts +25 -0
  147. package/src/walk/vendor-fetch.ts +80 -0
  148. package/src/walk/walk-database.ts +97 -0
  149. package/src/walk/xml-view.ts +44 -0
@@ -0,0 +1,143 @@
1
+ const AUTO_PIPELINE = 'https://upstash.com/docs/redis/sdks/ts/pipelining/auto-pipeline';
2
+ const ALGORITHMS = 'https://upstash.com/docs/redis/sdks/ratelimit-ts/algorithms';
3
+ const SIGNATURE = 'https://upstash.com/docs/qstash/howto/signature';
4
+ const DEDUPLICATION = 'https://upstash.com/docs/qstash/features/deduplication';
5
+ /** The QStash the credential door opens is in eu-central-1, whose URL is qstash.upstash.io (the pack's api lane). */
6
+ const QSTASH_URL = 'https://qstash.upstash.io';
7
+ async function routed(c, run) {
8
+ const held = globalThis.fetch;
9
+ globalThis.fetch = c.fetch;
10
+ try {
11
+ return await run();
12
+ }
13
+ finally {
14
+ globalThis.fetch = held;
15
+ }
16
+ }
17
+ /** A client as Dub's lib/upstash/redis.ts makes one: `new Redis({ url, token })`. */
18
+ async function redis(c) {
19
+ const { Redis } = (await import('@upstash/redis'));
20
+ return new Redis({ url: c.env.UPSTASH_REDIS_REST_URL, token: c.env.UPSTASH_REDIS_REST_TOKEN });
21
+ }
22
+ export const cases = [
23
+ {
24
+ id: 'redis-auto-pipeline',
25
+ asks: 'commands given at once are sent as one pipeline and each answers its own value, decoded from base64 into what was given: an object set is read back as that object, a counter as a number, a hash as an object',
26
+ source: `${AUTO_PIPELINE} "Auto pipelining allows you to use the Redis client as usual"; ${AUTO_PIPELINE} "the client will accumulate commands in a pipeline"`,
27
+ async run(c) {
28
+ if (!c.env.UPSTASH_REDIS_REST_URL || !c.env.UPSTASH_REDIS_REST_TOKEN)
29
+ return ['the credential door issued no UPSTASH_REDIS_REST_URL and token'];
30
+ try {
31
+ return await routed(c, async () => {
32
+ const r = await redis(c);
33
+ const link = { url: 'https://trailmark.io/pricing', clicks: 0, tags: ['launch'] };
34
+ const [set, count, fields] = await Promise.all([r.set('link:go.trailmark.io:pricing', link, { ex: 3600 }), r.incr('clicks:pricing'), r.hset('checkout:cs_1', { amount: 4900, currency: 'usd' })]);
35
+ const out = [];
36
+ if (set !== 'OK')
37
+ out.push(`SET answered ${JSON.stringify(set)}, not OK`);
38
+ if (count !== 1)
39
+ out.push(`INCR answered ${JSON.stringify(count)}, not 1`);
40
+ if (fields !== 2)
41
+ out.push(`HSET answered ${JSON.stringify(fields)}, not 2`);
42
+ const [read, hash] = await Promise.all([r.get('link:go.trailmark.io:pricing'), r.hgetall('checkout:cs_1')]);
43
+ if (JSON.stringify(read) !== JSON.stringify(link))
44
+ out.push(`the link read back as ${JSON.stringify(read)}`);
45
+ if (JSON.stringify(hash) !== JSON.stringify({ amount: 4900, currency: 'usd' }))
46
+ out.push(`the hash read back as ${JSON.stringify(hash)}`);
47
+ return out;
48
+ });
49
+ }
50
+ catch (e) {
51
+ return [`the client's calls failed: ${e.name}: ${e.message}`];
52
+ }
53
+ },
54
+ },
55
+ {
56
+ id: 'ratelimit-sliding-window',
57
+ asks: "a sliding window of 3 requests per 10 seconds, as Dub's ratelimit() makes one (prefix dub, analytics on), allows three requests and rejects the fourth; its script is run by SHA, and by EVAL when the database does not hold it",
58
+ source: `${ALGORITHMS} "the request is rejected"; ${ALGORITHMS} "Create a new ratelimiter, that allows 10 requests per 10 seconds."`,
59
+ async run(c) {
60
+ if (!c.env.UPSTASH_REDIS_REST_URL || !c.env.UPSTASH_REDIS_REST_TOKEN)
61
+ return ['the credential door issued no UPSTASH_REDIS_REST_URL and token'];
62
+ try {
63
+ return await routed(c, async () => {
64
+ const { Ratelimit } = (await import('@upstash/ratelimit'));
65
+ const limiter = new Ratelimit({ redis: await redis(c), limiter: Ratelimit.slidingWindow(3, '10 s'), analytics: true, prefix: 'dub', timeout: 1000 });
66
+ const seen = [];
67
+ for (let i = 0; i < 4; i += 1) {
68
+ const res = await limiter.limit('unsplash:203.0.113.7');
69
+ await res.pending;
70
+ seen.push(res.success);
71
+ }
72
+ return JSON.stringify(seen) === JSON.stringify([true, true, true, false]) ? [] : [`four requests in the window answered ${JSON.stringify(seen)}, not three allowed and the fourth rejected`];
73
+ });
74
+ }
75
+ catch (e) {
76
+ return [`the limiter's calls failed: ${e.name}: ${e.message}`];
77
+ }
78
+ },
79
+ },
80
+ {
81
+ id: 'qstash-deduplicated-publish',
82
+ asks: 'a message published again with the same deduplication id is accepted and answers the existing message\'s id',
83
+ source: `${DEDUPLICATION} "we will accept the request and return the messageID of the existing message"`,
84
+ async run(c) {
85
+ if (!c.env.QSTASH_TOKEN)
86
+ return ['the credential door issued no QSTASH_TOKEN'];
87
+ try {
88
+ return await routed(c, async () => {
89
+ const { Client } = (await import('@upstash/qstash'));
90
+ const client = new Client({ baseUrl: QSTASH_URL, token: c.env.QSTASH_TOKEN });
91
+ const request = { url: 'https://app.trailmark.io/api/cron/payouts/send-stripe-payout', body: { payoutId: 'po_1' }, deduplicationId: 'po_1', delay: 3600 };
92
+ const first = await client.publishJSON(request);
93
+ const again = await client.publishJSON(request);
94
+ const out = [];
95
+ if (!first.messageId)
96
+ out.push('the first publish answered no messageId');
97
+ if (again.messageId !== first.messageId)
98
+ out.push(`the duplicate answered ${JSON.stringify(again.messageId)}, not the first message's ${JSON.stringify(first.messageId)}`);
99
+ return out;
100
+ });
101
+ }
102
+ catch (e) {
103
+ return [`the client's calls failed: ${e.name}: ${e.message}`];
104
+ }
105
+ },
106
+ },
107
+ {
108
+ id: 'qstash-signed-delivery',
109
+ asks: "a published message's delivery carries Upstash-Signature, a JWT the Receiver verifies with the account's current and next signing keys against the body and the destination",
110
+ source: `${SIGNATURE} "We send a JWT with each request. This JWT is signed by your individual secret signing key and sent in the Upstash-Signature HTTP header."`,
111
+ async run(c) {
112
+ if (!c.env.QSTASH_TOKEN || !c.env.QSTASH_CURRENT_SIGNING_KEY || !c.env.QSTASH_NEXT_SIGNING_KEY)
113
+ return ['the credential door issued no QSTASH_TOKEN and signing keys'];
114
+ try {
115
+ return await routed(c, async () => {
116
+ const { Client, Receiver } = (await import('@upstash/qstash'));
117
+ const client = new Client({ baseUrl: QSTASH_URL, token: c.env.QSTASH_TOKEN });
118
+ const url = 'https://app.trailmark.io/api/jobs/process/welcome-user-job';
119
+ await client.publishJSON({ url, body: { name: 'welcome-user-job', userId: 'user_1' } });
120
+ // the World's stand-in for the destination: what QStash sent there (the pack's deliveries door)
121
+ let delivery;
122
+ for (let i = 0; i < 20 && !delivery; i += 1) {
123
+ const seen = (await (await c.fetch(`${QSTASH_URL}/_twin/deliveries?to=${encodeURIComponent(url)}`)).json());
124
+ delivery = seen.deliveries?.[0];
125
+ if (!delivery)
126
+ await new Promise((r) => setTimeout(r, 100));
127
+ }
128
+ if (!delivery)
129
+ return ['QStash made no delivery of the message'];
130
+ const signature = delivery.headers['upstash-signature'];
131
+ if (!signature)
132
+ return ['the delivery carries no Upstash-Signature'];
133
+ const receiver = new Receiver({ currentSigningKey: c.env.QSTASH_CURRENT_SIGNING_KEY, nextSigningKey: c.env.QSTASH_NEXT_SIGNING_KEY });
134
+ const valid = await receiver.verify({ signature, body: delivery.body, url }).catch((e) => e);
135
+ return valid === true ? [] : [`the Receiver refused the delivery's signature: ${valid instanceof Error ? valid.message : String(valid)}`];
136
+ });
137
+ }
138
+ catch (e) {
139
+ return [`the client's calls failed: ${e.name}: ${e.message}`];
140
+ }
141
+ },
142
+ },
143
+ ];
@@ -0,0 +1,2 @@
1
+ import type { ClientCase } from './harness.js';
2
+ export declare const cases: ClientCase[];
@@ -0,0 +1,128 @@
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.
3
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { digest } from '@volter/world-core';
7
+ import { resolveIdentity, verifyIdentityToken, tokenFor, readSharedSignIn, SHARED_CLIENTS } from '@volter/identity';
8
+ const INDEX = 'https://unpkg.com/@volter/identity@0.4.1/dist/index.js';
9
+ const SHARED = 'https://unpkg.com/@volter/identity@0.4.1/dist/shared.js';
10
+ const issuer = (c) => `https://${c.host}`;
11
+ async function send(c, path, body, headers = {}) {
12
+ const response = await c.fetch(`${issuer(c)}${path}`, {
13
+ method: body === undefined ? 'GET' : 'POST', redirect: 'manual',
14
+ headers: { 'content-type': typeof body === 'string' ? 'application/x-www-form-urlencoded' : 'application/json', ...headers },
15
+ ...(body === undefined ? {} : { body: typeof body === 'string' ? body : JSON.stringify(body) }),
16
+ });
17
+ if (response.status >= 400)
18
+ throw new Error(`${path}: ${response.status} ${await response.text()}`);
19
+ return response;
20
+ }
21
+ async function setup(c, name, shared = false) {
22
+ const email = `${name}@sdk.example.test`;
23
+ const person = await (await send(c, '/_twin/users', { email, name })).json();
24
+ const login = await send(c, '/login', `email=${encodeURIComponent(email)}&authorize=`);
25
+ const cookie = login.headers.get('set-cookie').split(';')[0];
26
+ const admin = await (await send(c, '/_twin/app-credentials', {})).json();
27
+ const operator = { cookie, authorization: `Bearer ${admin.admin_token}` };
28
+ const callback = 'http://127.0.0.1:53716/callback';
29
+ const registered = shared
30
+ ? await (await send(c, '/_twin/shared-client', {})).json()
31
+ : await (await send(c, '/admin/clients', { client_name: name, redirect_uris: [callback], application_type: 'native', token_endpoint_auth_method: 'client_secret_post', grant_types: ['authorization_code', 'refresh_token'], skip_consent: true }, operator)).json();
32
+ if (shared) {
33
+ await send(c, '/admin/resources', { identifier: 'https://teams.sdk.example', name: 'SDK Teams server' }, operator);
34
+ await send(c, `/admin/clients/${registered.client_id}/resources`, { resource: 'https://teams.sdk.example' }, operator);
35
+ }
36
+ 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: digest('sha256', verifier, 'base64url'), code_challenge_method: 'S256' });
38
+ const authorized = await send(c, `/api/auth/oauth2/authorize?${query}`, undefined, { cookie });
39
+ const code = new URL(authorized.headers.get('location')).searchParams.get('code');
40
+ 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 } : {}) });
41
+ const tokens = await (await send(c, '/api/auth/oauth2/token', form.toString())).json();
42
+ return { tokens, client: registered.client_id, subject: person.id };
43
+ }
44
+ async function withTransport(c, run) {
45
+ const previous = globalThis.fetch;
46
+ globalThis.fetch = c.fetch;
47
+ try {
48
+ return await run();
49
+ }
50
+ finally {
51
+ globalThis.fetch = previous;
52
+ }
53
+ }
54
+ export const cases = [
55
+ {
56
+ id: 'discovery-cache-and-fresh',
57
+ asks: 'resolveIdentity normalizes the issuer, caches discovery and refreshes when fresh is requested',
58
+ source: `${INDEX} "The answer is cached per issuer"`,
59
+ async run(c) {
60
+ let requests = 0;
61
+ const transport = (async (...args) => { requests++; return c.fetch(...args); });
62
+ const first = await resolveIdentity(`${issuer(c)}/`, { fresh: true, fetch: transport });
63
+ const cached = await resolveIdentity(issuer(c), { fetch: transport });
64
+ const failures = [];
65
+ if (!first.available || !cached.available)
66
+ failures.push('the SDK did not discover the twin');
67
+ if (first.available && first.metadata.issuer !== issuer(c))
68
+ failures.push('the SDK discovered another issuer');
69
+ if (requests !== 1)
70
+ failures.push('a cached discovery made another request');
71
+ await resolveIdentity(issuer(c), { fresh: true, fetch: transport });
72
+ if (Number(requests) !== 2)
73
+ failures.push('fresh discovery did not request metadata again');
74
+ return failures;
75
+ },
76
+ },
77
+ {
78
+ id: 'verify-signed-identity-and-audience',
79
+ asks: 'verifyIdentityToken reads the issuer keys, names the real subject and rejects another audience',
80
+ source: `${INDEX} "the signature, the issuer, the audience (this product's client id, or the resource it serves) and the expiry. Throws on anything else."`,
81
+ run(c) {
82
+ return withTransport(c, async () => {
83
+ const issued = await setup(c, 'verified-person');
84
+ const who = await verifyIdentityToken(issued.tokens.id_token, { issuer: issuer(c), audience: issued.client, fetch: c.fetch });
85
+ const failures = who.subject === issued.subject && who.issuer === issuer(c) ? [] : ['the verified token names another subject or issuer'];
86
+ const wrong = await verifyIdentityToken(issued.tokens.id_token, { issuer: issuer(c), audience: 'another-client', fetch: c.fetch }).then(() => false, () => true);
87
+ if (!wrong)
88
+ failures.push('the SDK accepted a token for another audience');
89
+ const tampered = await verifyIdentityToken(`${issued.tokens.id_token.slice(0, -4)}AAAA`, { issuer: issuer(c), audience: issued.client, fetch: c.fetch }).then(() => false, () => true);
90
+ if (!tampered)
91
+ failures.push('the SDK accepted a changed signature');
92
+ return failures;
93
+ });
94
+ },
95
+ },
96
+ {
97
+ id: 'shared-resource-refresh-rotation',
98
+ asks: 'tokenFor exchanges the shared refresh for the resource, saves the rotated refresh and uses it next time',
99
+ source: `${SHARED} "The token is the product's alone"`,
100
+ run(c) {
101
+ return withTransport(c, async () => {
102
+ const issued = await setup(c, 'shared-person', true);
103
+ const directory = mkdtempSync(join(tmpdir(), 'identity-sdk-case-'));
104
+ const path = join(directory, 'identity.json');
105
+ try {
106
+ writeFileSync(path, JSON.stringify({ issuer: issuer(c), clientId: SHARED_CLIENTS[issuer(c)], refreshToken: issued.tokens.refresh_token, savedAt: Date.now() }), { mode: 0o600 });
107
+ const resource = 'https://teams.sdk.example';
108
+ const token = await tokenFor(resource, { path });
109
+ if (!token)
110
+ return ['the SDK obtained no resource token'];
111
+ const who = await verifyIdentityToken(token, { issuer: issuer(c), audience: resource, fetch: c.fetch });
112
+ const saved = readSharedSignIn(path);
113
+ const failures = who.subject === issued.subject ? [] : ['the resource token names another subject'];
114
+ if (saved.refreshToken === issued.tokens.refresh_token)
115
+ failures.push('the SDK did not save the rotated refresh');
116
+ if (!await tokenFor(resource, { path }))
117
+ failures.push('the saved refresh did not obtain the next token');
118
+ if (JSON.parse(readFileSync(path, 'utf8')).refreshToken === saved.refreshToken)
119
+ failures.push('the second refresh did not rotate again');
120
+ return failures;
121
+ }
122
+ finally {
123
+ rmSync(directory, { recursive: true, force: true });
124
+ }
125
+ });
126
+ },
127
+ },
128
+ ];
@@ -0,0 +1,2 @@
1
+ import type { ClientCase } from './harness.js';
2
+ export declare const cases: ClientCase[];
@@ -0,0 +1,23 @@
1
+ import { chatCases, openaiClient } from "./openai-compatible.js";
2
+ const XAI = { key: 'XAI_API_KEY', base: (host) => `https://${host}/v1`, model: 'grok-4.7' };
3
+ export const cases = [
4
+ ...chatCases(XAI),
5
+ {
6
+ id: 'responses-reasoning-stream',
7
+ asks: "a reasoning turn streamed through the SDK's Responses stream completes with its reasoning item and its message",
8
+ source: 'https://developers.openai.com/api/reference/resources/responses/streaming-events "response.reasoning_summary_part.added"',
9
+ async run(c) {
10
+ c.scenario({ handlers: [{ id: 'reasons', on: { userTextIncludes: 'hello' }, respond: { text: 'scripted words', reasoning: 'thinking it through' } }] });
11
+ const client = await openaiClient(c, XAI);
12
+ try {
13
+ const stream = client.responses.stream({ model: XAI.model, input: 'hello', reasoning: { summary: 'auto' } });
14
+ for await (const _event of stream) { /* the accumulator reads each */ }
15
+ const types = (await stream.finalResponse()).output.map((o) => o.type);
16
+ return types.includes('reasoning') && types.includes('message') ? [] : [`the streamed response's output is ${JSON.stringify(types)}`];
17
+ }
18
+ catch (e) {
19
+ return [`the SDK's Responses stream failed: ${e.message}`];
20
+ }
21
+ },
22
+ },
23
+ ];
@@ -0,0 +1,33 @@
1
+ export type ConformanceResult = {
2
+ id: string;
3
+ asks: string;
4
+ failures: string[];
5
+ note?: string;
6
+ };
7
+ /** 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()), and a
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. */
11
+ export declare const UNIT_FILES: readonly ["src/manifest.ts", "spec/SOURCE.md", "journeys/customer-life.json", "journeys/decisions.json"];
12
+ /** The published package must carry every conformance input its units hold, including lanes. */
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
+ export declare function unitFilesMissing(unit: string): string[];
17
+ type Owners = Record<string, string>;
18
+ type Decision = {
19
+ operation: string;
20
+ mechanism: string;
21
+ demand?: string;
22
+ lane?: string;
23
+ };
24
+ /** The decision table read against one unit of a vendor (its own API, `''`, or a lane by name), each unit against its
25
+ * own owners (a fetch's `ownersByUnit()`): a row decides the operation of the lane it names; a row naming none decides
26
+ * the vendor's own API's operation of that id, or, when its own API has none, the one lane's that has it. So an id two
27
+ * units share is two decisions, and a lane's operation its root serves stays the lane's gap (Clerk's Backend and
28
+ * Frontend APIs both name `ListOrganizationMemberships`). A demanded operation (journeys/demand.json, the vendor's
29
+ * backend calls) is the unit's a row naming no lane would decide. */
30
+ export declare function decisionTableProblems(unit: string, byUnit: Record<string, Owners>, decisions: Decision[], demanded?: string[], doors?: string[]): string[];
31
+ /** Every check over the pack at `dir`, in order. */
32
+ export declare function conformance(dir: string): Promise<ConformanceResult[]>;
33
+ export {};