@sema-agent/client-core 0.111.0 → 0.112.1

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 (115) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +16 -7
  3. package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
  4. package/dist/checkpointRowVerdict.d.ts +3 -0
  5. package/dist/checkpointRowVerdict.js +14 -1
  6. package/dist/consoleCaPin.d.ts +136 -0
  7. package/dist/consoleCaPin.js +827 -0
  8. package/dist/decideReceipt.d.ts +2 -1
  9. package/dist/deviceRebind.js +3 -3
  10. package/dist/effectiveFacts.d.ts +9 -0
  11. package/dist/effectiveFacts.js +33 -0
  12. package/dist/engineAddress.d.ts +1 -0
  13. package/dist/engineAddress.js +4 -1
  14. package/dist/engineCredentialRelease.d.ts +54 -0
  15. package/dist/engineCredentialRelease.js +146 -0
  16. package/dist/engineErrorCodes.d.ts +1 -0
  17. package/dist/engineErrorCodes.js +1 -0
  18. package/dist/engineHealthFacts.d.ts +0 -11
  19. package/dist/engineHealthFacts.js +0 -21
  20. package/dist/engineHealthProbeVerdict.d.ts +1 -0
  21. package/dist/engineHealthProbeVerdict.js +21 -13
  22. package/dist/engineUnavailable.d.ts +3 -2
  23. package/dist/engineUnavailable.js +16 -12
  24. package/dist/engineUrl.js +4 -2
  25. package/dist/gateFailureCodes.js +2 -2
  26. package/dist/generated/sdkFactTables.d.ts +1 -0
  27. package/dist/generated/sdkFactTables.js +13 -0
  28. package/dist/generated/settingsSchemaFns.d.ts +1 -0
  29. package/dist/generated/settingsSchemaFns.js +3 -0
  30. package/dist/generated/settingsSchemaHooksClassifier.js +38 -9
  31. package/dist/generated/settingsSchemaTables.d.ts +52 -0
  32. package/dist/generated/settingsSchemaTables.js +18 -0
  33. package/dist/generated/settingsSchemaTypes.d.ts +23 -13
  34. package/dist/generated/settingsSchemaUrlShape.d.ts +1 -0
  35. package/dist/generated/settingsSchemaUrlShape.js +17 -0
  36. package/dist/hitl/decisionRefusal.d.ts +4 -0
  37. package/dist/hitl/decisionRefusal.js +12 -2
  38. package/dist/hitl/hitlBridge.d.ts +1 -0
  39. package/dist/hitl/hitlBridge.js +17 -4
  40. package/dist/hitl/localAllowRedemption.d.ts +9 -6
  41. package/dist/hitl/localAllowRedemption.js +20 -0
  42. package/dist/hitl/parkOwnership.js +2 -1
  43. package/dist/hitl/parkResolver.js +7 -0
  44. package/dist/hitl/planReviewCard.d.ts +3 -2
  45. package/dist/hitl/planReviewCard.js +2 -0
  46. package/dist/hitl/planReviewWire.d.ts +4 -2
  47. package/dist/hitl/planReviewWire.js +134 -97
  48. package/dist/hitl/screenDecisionCards.js +3 -2
  49. package/dist/hitl/sessionPolicyWire.js +2 -2
  50. package/dist/hitl/standingRows.d.ts +4 -2
  51. package/dist/hitl/standingRows.js +10 -3
  52. package/dist/hooksReaderGenerations.js +1 -1
  53. package/dist/i18n/messages.en.d.ts +165 -0
  54. package/dist/i18n/messages.en.js +166 -1
  55. package/dist/i18n/messages.zh.js +177 -1
  56. package/dist/index.d.ts +30 -5
  57. package/dist/index.js +15 -2
  58. package/dist/liveModelCatalog.d.ts +4 -0
  59. package/dist/liveModelCatalog.js +10 -1
  60. package/dist/localConfig/documents.d.ts +61 -0
  61. package/dist/localConfig/documents.js +51 -0
  62. package/dist/localConfig/kernel.d.ts +149 -0
  63. package/dist/localConfig/kernel.js +597 -0
  64. package/dist/localConfig/mcp/edit.d.ts +161 -0
  65. package/dist/localConfig/mcp/edit.js +846 -0
  66. package/dist/localConfig/mcp/servers.d.ts +165 -0
  67. package/dist/localConfig/mcp/servers.js +776 -0
  68. package/dist/localConfig/mcp/views.d.ts +170 -0
  69. package/dist/localConfig/mcp/views.js +429 -0
  70. package/dist/localConfig/modelPool.d.ts +262 -0
  71. package/dist/localConfig/modelPool.js +1489 -0
  72. package/dist/localConfig/modelPoolWords.d.ts +13 -0
  73. package/dist/localConfig/modelPoolWords.js +20 -0
  74. package/dist/localConfig/receiptShape.d.ts +7 -0
  75. package/dist/localConfig/receiptShape.js +135 -0
  76. package/dist/localConfig/settingsWrite.d.ts +127 -0
  77. package/dist/localConfig/settingsWrite.js +237 -0
  78. package/dist/localConfig/skills.d.ts +124 -0
  79. package/dist/localConfig/skills.js +397 -0
  80. package/dist/localConfig/skillsWords.d.ts +10 -0
  81. package/dist/localConfig/skillsWords.js +5 -0
  82. package/dist/mcpProbeWire.js +2 -1
  83. package/dist/mcpWireCaps.js +1 -1
  84. package/dist/memoryVerbsWire.js +3 -2
  85. package/dist/model/onboardJudgement.d.ts +14 -4
  86. package/dist/model/onboardJudgement.js +91 -139
  87. package/dist/modelConfigGapNotice.d.ts +20 -0
  88. package/dist/modelConfigGapNotice.js +102 -0
  89. package/dist/modsKernelIdentity.js +2 -47
  90. package/dist/notifications.js +14 -12
  91. package/dist/ownKey.d.ts +1 -0
  92. package/dist/ownKey.js +40 -8
  93. package/dist/permissionsDeclaration.d.ts +2 -1
  94. package/dist/permissionsDeclaration.js +31 -17
  95. package/dist/planReviewOutcomeCause.d.ts +2 -0
  96. package/dist/planReviewOutcomeCause.js +12 -0
  97. package/dist/promptBlockedCopy.js +2 -2
  98. package/dist/resumeRefusalCopy.d.ts +3 -1
  99. package/dist/resumeRefusalCopy.js +12 -4
  100. package/dist/runLedgerSnapshot.js +3 -3
  101. package/dist/runPermissionModeSwitch.js +2 -3
  102. package/dist/seatContract.d.ts +154 -4
  103. package/dist/seatContract.js +228 -2
  104. package/dist/sessionIdSegment.d.ts +1 -0
  105. package/dist/sessionIdSegment.js +4 -1
  106. package/dist/sessionMemoryErase.js +2 -1
  107. package/dist/sha256Bytes.d.ts +1 -0
  108. package/dist/sha256Bytes.js +46 -0
  109. package/dist/skillOverrideChain.d.ts +83 -0
  110. package/dist/skillOverrideChain.js +378 -0
  111. package/dist/skillsWireCaps.d.ts +3 -0
  112. package/dist/skillsWireCaps.js +16 -1
  113. package/dist/turnFailurePresentation.js +4 -10
  114. package/docs/INTEGRATION-CLIENTS.md +680 -14
  115. package/package.json +3 -3
@@ -0,0 +1,827 @@
1
+ import { hasUnsafeAddressCharacter, isCenterRelativePath } from './engineAddress.js';
2
+ import { isLoopbackWireUrl } from './engineWireSdk.js';
3
+ import { usableClockMs } from './clockReading.js';
4
+ import { capForDisplay } from './fleetTaskDesc.js';
5
+ import { SETTINGS_HTTP_URL_FLAWS, SETTINGS_STARTS_WITH_HTTP_SCHEME } from './generated/settingsSchemaFns.js';
6
+ import { SETTINGS_SHA256_DIGEST_RE } from './generated/settingsSchemaTables.js';
7
+ import { msg, renderMessage } from './i18n/translate.js';
8
+ import { engineNamespaceKeyFor } from './sessionMap.js';
9
+ import { sha256BytesHex } from './sha256Bytes.js';
10
+ import { slotReadOf } from './wireFailureShape.js';
11
+ export const CA_PIN_LIMITS = Object.freeze({
12
+ addressMaxChars: 2048,
13
+ authorityMaxChars: 300,
14
+ fingerprintsMax: 16,
15
+ fingerprintMaxChars: 128,
16
+ bodyMaxBytes: 1_048_576,
17
+ servedMax: 16,
18
+ pemMaxBytes: 16_384,
19
+ budgetMs: 120_000,
20
+ roundMs: 10_000,
21
+ retriesMax: 3,
22
+ waitCapMs: 60_000,
23
+ jitterMaxMs: 2_000,
24
+ });
25
+ const isCanonicalFingerprint = (v) => typeof v === 'string' && SETTINGS_SHA256_DIGEST_RE.test(v);
26
+ function refuseAddress(reason) {
27
+ return Object.freeze({ ok: false, reason });
28
+ }
29
+ function refuseOrigin(reason) {
30
+ return Object.freeze({ ok: false, reason });
31
+ }
32
+ function rawAuthorityOf(s) {
33
+ const from = s.indexOf('//') + 2;
34
+ let end = s.length;
35
+ for (let i = from; i < s.length; i++) {
36
+ const c = s.charCodeAt(i);
37
+ if (c === 0x2f || c === 0x3f || c === 0x23) {
38
+ end = i;
39
+ break;
40
+ }
41
+ }
42
+ return s.slice(from, end);
43
+ }
44
+ function rawHostOf(authority) {
45
+ const lower = authority.toLowerCase();
46
+ if (lower.startsWith('[')) {
47
+ const close = lower.indexOf(']');
48
+ return close < 0 ? lower : lower.slice(0, close + 1);
49
+ }
50
+ const colon = lower.lastIndexOf(':');
51
+ return colon < 0 ? lower : lower.slice(0, colon);
52
+ }
53
+ function hasTrailingDot(rawHost, parsed) {
54
+ return rawHost.endsWith('.') || parsed.hostname.endsWith('.');
55
+ }
56
+ function ipNotCanonical(rawHost, parsed) {
57
+ const h = parsed.hostname;
58
+ const isIp = h.startsWith('[') || /^[\d.]+$/.test(h);
59
+ return isIp && rawHost !== h;
60
+ }
61
+ export function readCenterAddress(raw, seat) {
62
+ try {
63
+ const seatWord = seat;
64
+ if (seatWord !== 'invite' && seatWord !== 'address')
65
+ return refuseAddress('bad_input');
66
+ const rawValue = raw;
67
+ if (typeof rawValue !== 'string')
68
+ return refuseAddress('empty');
69
+ const s = rawValue.trim();
70
+ if (s.length === 0)
71
+ return refuseAddress('empty');
72
+ if (s.length > CA_PIN_LIMITS.addressMaxChars)
73
+ return refuseAddress('too_long');
74
+ if (hasUnsafeAddressCharacter(s))
75
+ return refuseAddress('unsafe_character');
76
+ const schemeOk = seat === 'invite' ? /^https:\/\//i.test(s) : SETTINGS_STARTS_WITH_HTTP_SCHEME(s);
77
+ if (!schemeOk)
78
+ return refuseAddress('scheme');
79
+ const authority = rawAuthorityOf(s);
80
+ if (authority.length === 0)
81
+ return refuseAddress('scheme');
82
+ if (authority.includes('@'))
83
+ return refuseAddress('userinfo');
84
+ if (authority.includes('%'))
85
+ return refuseAddress('percent_in_host');
86
+ const flaw = SETTINGS_HTTP_URL_FLAWS(s, seat === 'address' ? ['query', 'fragment'] : ['query'])[0];
87
+ if (flaw === 'not-http-url')
88
+ return refuseAddress('unparseable');
89
+ if (flaw === 'query' || flaw === 'fragment')
90
+ return refuseAddress(flaw);
91
+ const u = new URL(s);
92
+ const rawHost = rawHostOf(authority);
93
+ if (hasTrailingDot(rawHost, u))
94
+ return refuseAddress('trailing_dot');
95
+ if (ipNotCanonical(rawHost, u))
96
+ return refuseAddress('ip_not_canonical');
97
+ if (seat === 'address' && u.protocol === 'http:' && !isLoopbackWireUrl(u.href))
98
+ return refuseAddress('plaintext_remote');
99
+ const hashAt = s.indexOf('#');
100
+ const afterAuthority = s.indexOf('//') + 2 + authority.length;
101
+ const rawPath = s.slice(afterAuthority, hashAt < 0 ? s.length : hashAt);
102
+ const pathOk = seat === 'invite' ? rawPath === '' || rawPath === '/' : rawPath === '' || isCenterRelativePath(rawPath);
103
+ if (!pathOk)
104
+ return refuseAddress('path');
105
+ const fingerprints = [];
106
+ if (seat === 'invite' && hashAt >= 0) {
107
+ const fragment = s.slice(hashAt + 1);
108
+ if (!fragment.startsWith('ca='))
109
+ return refuseAddress('fragment');
110
+ for (const item of fragment.slice(3).split(',')) {
111
+ if (!isCanonicalFingerprint(item))
112
+ return refuseAddress('fingerprint');
113
+ if (!fingerprints.includes(item))
114
+ fingerprints.push(item);
115
+ }
116
+ if (fingerprints.length > CA_PIN_LIMITS.fingerprintsMax)
117
+ return refuseAddress('too_many_fingerprints');
118
+ }
119
+ return Object.freeze({
120
+ ok: true,
121
+ seat,
122
+ origin: u.origin,
123
+ centerKey: engineNamespaceKeyFor(u.href),
124
+ fingerprints: Object.freeze(fingerprints),
125
+ });
126
+ }
127
+ catch {
128
+ return refuseAddress('bad_input');
129
+ }
130
+ }
131
+ export function trustOriginOf(url) {
132
+ try {
133
+ const urlValue = url;
134
+ if (typeof urlValue !== 'string')
135
+ return refuseOrigin('empty');
136
+ const s = urlValue.trim();
137
+ if (s.length === 0)
138
+ return refuseOrigin('empty');
139
+ const scheme = /^(https?|wss?):\/\//i.exec(s);
140
+ if (scheme === null)
141
+ return refuseOrigin('scheme');
142
+ const authority = rawAuthorityOf(s);
143
+ if (authority.length === 0)
144
+ return refuseOrigin('scheme');
145
+ if (authority.length > CA_PIN_LIMITS.authorityMaxChars)
146
+ return refuseOrigin('too_long');
147
+ if (hasUnsafeAddressCharacter(authority))
148
+ return refuseOrigin('unsafe_character');
149
+ if (authority.includes('@'))
150
+ return refuseOrigin('userinfo');
151
+ if (authority.includes('%'))
152
+ return refuseOrigin('percent_in_host');
153
+ const word = (scheme[1] ?? '').toLowerCase();
154
+ const httpScheme = word === 'wss' || word === 'https' ? 'https' : 'http';
155
+ let u;
156
+ try {
157
+ u = new URL(`${httpScheme}://${authority}/`);
158
+ }
159
+ catch {
160
+ return refuseOrigin('unparseable');
161
+ }
162
+ const rawHost = rawHostOf(authority);
163
+ if (hasTrailingDot(rawHost, u))
164
+ return refuseOrigin('trailing_dot');
165
+ if (ipNotCanonical(rawHost, u))
166
+ return refuseOrigin('ip_not_canonical');
167
+ return Object.freeze({ ok: true, origin: u.origin });
168
+ }
169
+ catch {
170
+ return refuseOrigin('unparseable');
171
+ }
172
+ }
173
+ function refuseFingerprints(reason, index) {
174
+ return Object.freeze(index === undefined ? { ok: false, reason } : { ok: false, reason, index });
175
+ }
176
+ function ownArrayItems(v) {
177
+ if (!Array.isArray(v))
178
+ return undefined;
179
+ const len = slotReadOf(v, 'length');
180
+ if (len.presence !== 'present' || typeof len.value !== 'number')
181
+ return undefined;
182
+ const out = [];
183
+ for (let i = 0; i < len.value; i++) {
184
+ const item = slotReadOf(v, String(i));
185
+ if (item.presence !== 'present')
186
+ return undefined;
187
+ out.push(item.value);
188
+ }
189
+ return out;
190
+ }
191
+ export function caFingerprintsOf(texts) {
192
+ try {
193
+ const items = ownArrayItems(texts);
194
+ if (items === undefined)
195
+ return refuseFingerprints('bad_input');
196
+ const out = [];
197
+ for (let i = 0; i < items.length; i++) {
198
+ const t = items[i];
199
+ if (typeof t !== 'string')
200
+ return refuseFingerprints('bad_input');
201
+ if (t.length > CA_PIN_LIMITS.fingerprintMaxChars)
202
+ return refuseFingerprints('fingerprint', i);
203
+ if (!t.startsWith('sha256:'))
204
+ return refuseFingerprints('prefix', i);
205
+ const body = t.slice('sha256:'.length);
206
+ let hex;
207
+ if (/^[0-9a-fA-F]{64}$/.test(body))
208
+ hex = body.toLowerCase();
209
+ else if (/^[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){31}$/.test(body))
210
+ hex = body.replace(/:/g, '').toLowerCase();
211
+ else
212
+ return refuseFingerprints('fingerprint', i);
213
+ const fp = `sha256:${hex}`;
214
+ if (!out.includes(fp))
215
+ out.push(fp);
216
+ }
217
+ if (out.length > CA_PIN_LIMITS.fingerprintsMax)
218
+ return refuseFingerprints('too_many_fingerprints');
219
+ return Object.freeze({ ok: true, fingerprints: Object.freeze(out) });
220
+ }
221
+ catch {
222
+ return refuseFingerprints('bad_input');
223
+ }
224
+ }
225
+ export function caFingerprintOfDer(der) {
226
+ return `sha256:${sha256BytesHex(der)}`;
227
+ }
228
+ export function caFingerprintDisplay(fp) {
229
+ if (!isCanonicalFingerprint(fp))
230
+ return undefined;
231
+ return (fp.slice('sha256:'.length).toUpperCase().match(/../g) ?? []).join(':');
232
+ }
233
+ function refuseBody(reason, index) {
234
+ return Object.freeze(index === undefined ? { kind: 'refused', reason } : { kind: 'refused', reason, index });
235
+ }
236
+ const TYPED_ARRAY_PROTO = Object.getPrototypeOf(Uint8Array.prototype);
237
+ const typedArrayTag = Object.getOwnPropertyDescriptor(TYPED_ARRAY_PROTO, Symbol.toStringTag)?.get;
238
+ const typedArrayLength = Object.getOwnPropertyDescriptor(TYPED_ARRAY_PROTO, 'length')?.get;
239
+ const uint8Set = Uint8Array.prototype.set;
240
+ function byteLengthOf(v) {
241
+ if (typeof v !== 'object' || v === null || typedArrayTag === undefined || typedArrayLength === undefined)
242
+ return undefined;
243
+ try {
244
+ if (typedArrayTag.call(v) !== 'Uint8Array')
245
+ return undefined;
246
+ const n = typedArrayLength.call(v);
247
+ return typeof n === 'number' ? n : undefined;
248
+ }
249
+ catch {
250
+ return undefined;
251
+ }
252
+ }
253
+ function copyBytes(v, n) {
254
+ try {
255
+ const copy = new Uint8Array(n);
256
+ uint8Set.call(copy, v);
257
+ return copy;
258
+ }
259
+ catch {
260
+ return undefined;
261
+ }
262
+ }
263
+ function bytesEqual(a, b) {
264
+ if (a.length !== b.length)
265
+ return false;
266
+ for (let i = 0; i < a.length; i++)
267
+ if (a[i] !== b[i])
268
+ return false;
269
+ return true;
270
+ }
271
+ const PEM_HEAD = '-----BEGIN CERTIFICATE-----\n';
272
+ const PEM_TAIL = '-----END CERTIFICATE-----\n';
273
+ function pemOfDer(der) {
274
+ let bin = '';
275
+ for (let i = 0; i < der.length; i++)
276
+ bin += String.fromCharCode(der[i] ?? 0);
277
+ const b64 = btoa(bin);
278
+ let body = '';
279
+ for (let i = 0; i < b64.length; i += 64)
280
+ body += `${b64.slice(i, i + 64)}\n`;
281
+ return PEM_HEAD + body + PEM_TAIL;
282
+ }
283
+ function canonicalPemDer(pem) {
284
+ if (!pem.startsWith(PEM_HEAD) || !pem.endsWith(PEM_TAIL) || pem.length <= PEM_HEAD.length + PEM_TAIL.length)
285
+ return undefined;
286
+ const b64 = pem.slice(PEM_HEAD.length, pem.length - PEM_TAIL.length).replace(/\n/g, '');
287
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(b64))
288
+ return undefined;
289
+ let bin;
290
+ try {
291
+ bin = atob(b64);
292
+ }
293
+ catch {
294
+ return undefined;
295
+ }
296
+ const der = new Uint8Array(bin.length);
297
+ for (let i = 0; i < bin.length; i++)
298
+ der[i] = bin.charCodeAt(i);
299
+ return pemOfDer(der) === pem ? der : undefined;
300
+ }
301
+ function derElementEnd(der, off, limit, depth) {
302
+ if (depth > 32 || off >= limit)
303
+ return -1;
304
+ const tag = der[off] ?? 0;
305
+ let p = off + 1;
306
+ if ((tag & 0x1f) === 0x1f) {
307
+ let n = 0;
308
+ while (p < limit && ((der[p] ?? 0) & 0x80) !== 0) {
309
+ if (n === 0 && der[p] === 0x80)
310
+ return -1;
311
+ p++;
312
+ if (++n > 4)
313
+ return -1;
314
+ }
315
+ if (p >= limit)
316
+ return -1;
317
+ p++;
318
+ }
319
+ if (p >= limit)
320
+ return -1;
321
+ const first = der[p] ?? 0;
322
+ p++;
323
+ let len;
324
+ if (first < 0x80)
325
+ len = first;
326
+ else {
327
+ const n = first & 0x7f;
328
+ if (n === 0 || n > 4 || p + n > limit || der[p] === 0)
329
+ return -1;
330
+ len = 0;
331
+ for (let i = 0; i < n; i++)
332
+ len = len * 256 + (der[p + i] ?? 0);
333
+ if (len < 0x80)
334
+ return -1;
335
+ p += n;
336
+ }
337
+ const end = p + len;
338
+ if (end > limit)
339
+ return -1;
340
+ if ((tag & 0x20) !== 0) {
341
+ let q = p;
342
+ while (q < end) {
343
+ q = derElementEnd(der, q, end, depth + 1);
344
+ if (q < 0)
345
+ return -1;
346
+ }
347
+ }
348
+ return end;
349
+ }
350
+ function derShapeCanonical(der) {
351
+ return der.length > 0 && derElementEnd(der, 0, der.length, 0) === der.length;
352
+ }
353
+ function isoOfOpensslTime(t) {
354
+ const m = /^([A-Z][a-z]{2}) ([ 1-3]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4}) GMT$/.exec(t);
355
+ if (m === null)
356
+ return undefined;
357
+ const month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].indexOf(m[1] ?? '');
358
+ const day = Number((m[2] ?? '').trim());
359
+ const [hh, mm, ss, yyyy] = [Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6])];
360
+ if (month < 0 || day < 1 || hh > 23 || mm > 59 || ss > 60)
361
+ return undefined;
362
+ const ms = Date.UTC(yyyy, month, day, hh, mm, ss);
363
+ const d = new Date(ms);
364
+ if (d.getUTCDate() !== day || d.getUTCMonth() !== month)
365
+ return undefined;
366
+ const pad = (x, w = 2) => String(x).padStart(w, '0');
367
+ return `${pad(yyyy, 4)}-${pad(month + 1)}-${pad(day)}T${pad(hh)}:${pad(mm)}:${pad(ss)}Z`;
368
+ }
369
+ function readFacts(v) {
370
+ if (typeof v !== 'object' || v === null)
371
+ return undefined;
372
+ const raw = slotReadOf(v, 'raw');
373
+ const subject = slotReadOf(v, 'subject');
374
+ const notAfter = slotReadOf(v, 'notAfter');
375
+ const selfSigned = slotReadOf(v, 'selfSigned');
376
+ if (raw.presence !== 'present' || subject.presence !== 'present' || notAfter.presence !== 'present' || selfSigned.presence !== 'present')
377
+ return undefined;
378
+ if (typeof subject.value !== 'string' || typeof notAfter.value !== 'string' || typeof selfSigned.value !== 'boolean')
379
+ return undefined;
380
+ const n = byteLengthOf(raw.value);
381
+ if (n === undefined)
382
+ return undefined;
383
+ const bytes = copyBytes(raw.value, n);
384
+ if (bytes === undefined)
385
+ return undefined;
386
+ return { raw: bytes, subject: subject.value, notAfter: notAfter.value, selfSigned: selfSigned.value };
387
+ }
388
+ function servedOf(fingerprint, pem, facts) {
389
+ const subject = capForDisplay(facts.subject.split('\n').join(', '), 256);
390
+ const notAfter = isoOfOpensslTime(facts.notAfter);
391
+ return Object.freeze({
392
+ fingerprint,
393
+ pem,
394
+ selfSigned: facts.selfSigned,
395
+ ...(subject.length > 0 ? { subject } : {}),
396
+ ...(notAfter !== undefined ? { notAfter } : {}),
397
+ });
398
+ }
399
+ function namedOf(request) {
400
+ const slot = slotReadOf(request, 'named');
401
+ if (slot.presence === 'absent')
402
+ return [];
403
+ if (slot.presence !== 'present')
404
+ return undefined;
405
+ const items = ownArrayItems(slot.value);
406
+ if (items === undefined)
407
+ return undefined;
408
+ const out = [];
409
+ for (const item of items) {
410
+ if (!isCanonicalFingerprint(item))
411
+ return undefined;
412
+ if (!out.includes(item))
413
+ out.push(item);
414
+ }
415
+ return out.length <= CA_PIN_LIMITS.fingerprintsMax ? out : undefined;
416
+ }
417
+ export function readCaBootstrapBody(body, request) {
418
+ try {
419
+ const req = request;
420
+ if (typeof req !== 'object' || req === null)
421
+ return refuseBody('bad_input');
422
+ const named = namedOf(req);
423
+ if (named === undefined)
424
+ return refuseBody('bad_input');
425
+ const parserSlot = slotReadOf(req, 'parseCertificate');
426
+ if (parserSlot.presence === 'absent')
427
+ return refuseBody('parser_unavailable');
428
+ if (parserSlot.presence !== 'present' || typeof parserSlot.value !== 'function')
429
+ return refuseBody('bad_input');
430
+ const parse = parserSlot.value;
431
+ const n = byteLengthOf(body);
432
+ if (n === undefined)
433
+ return refuseBody('bad_input');
434
+ if (n > CA_PIN_LIMITS.bodyMaxBytes)
435
+ return refuseBody('body_too_large');
436
+ const bytes = copyBytes(body, n);
437
+ if (bytes === undefined)
438
+ return refuseBody('bad_input');
439
+ let doc;
440
+ try {
441
+ doc = JSON.parse(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes));
442
+ }
443
+ catch {
444
+ return refuseBody('body_not_json');
445
+ }
446
+ if (typeof doc !== 'object' || doc === null || Array.isArray(doc))
447
+ return refuseBody('body_shape');
448
+ const configured = slotReadOf(doc, 'configured');
449
+ if (configured.presence !== 'present' || typeof configured.value !== 'boolean')
450
+ return refuseBody('body_shape');
451
+ if (configured.value === false)
452
+ return named.length > 0 ? refuseBody('unconfigured_with_fingerprints') : Object.freeze({ kind: 'unconfigured' });
453
+ const certsSlot = slotReadOf(doc, 'certificates');
454
+ if (certsSlot.presence !== 'present' || !Array.isArray(certsSlot.value))
455
+ return refuseBody('body_shape');
456
+ const items = ownArrayItems(certsSlot.value);
457
+ if (items === undefined)
458
+ return refuseBody('body_shape');
459
+ if (items.length === 0 || items.length > CA_PIN_LIMITS.servedMax)
460
+ return refuseBody('served_count');
461
+ const served = [];
462
+ for (let i = 0; i < items.length; i++) {
463
+ const item = items[i];
464
+ if (typeof item !== 'object' || item === null || Array.isArray(item))
465
+ return refuseBody('body_shape', i);
466
+ const pemSlot = slotReadOf(item, 'pem');
467
+ if (pemSlot.presence !== 'present' || typeof pemSlot.value !== 'string')
468
+ return refuseBody('body_shape', i);
469
+ const pem = pemSlot.value;
470
+ if (pem.length > CA_PIN_LIMITS.pemMaxBytes)
471
+ return refuseBody('certificate_too_large', i);
472
+ const der = canonicalPemDer(pem);
473
+ if (der === undefined || !derShapeCanonical(der))
474
+ return refuseBody('certificate_not_canonical', i);
475
+ const fingerprint = caFingerprintOfDer(der);
476
+ if (served.some((c) => c.fingerprint === fingerprint))
477
+ return refuseBody('served_duplicate', i);
478
+ let factsRaw;
479
+ try {
480
+ factsRaw = parse(der.slice());
481
+ }
482
+ catch {
483
+ return refuseBody('certificate_unparseable', i);
484
+ }
485
+ const facts = readFacts(factsRaw);
486
+ if (facts === undefined)
487
+ return refuseBody('certificate_unparseable', i);
488
+ if (!bytesEqual(facts.raw, der))
489
+ return refuseBody('certificate_not_canonical', i);
490
+ served.push(servedOf(fingerprint, pem, facts));
491
+ }
492
+ const anchors = [];
493
+ for (let k = 0; k < named.length; k++) {
494
+ const at = served.findIndex((c) => c.fingerprint === named[k]);
495
+ if (at < 0)
496
+ return refuseBody('named_not_served', k);
497
+ const hit = served[at];
498
+ if (hit === undefined || hit.selfSigned !== true)
499
+ return refuseBody('anchor_not_self_signed', at);
500
+ anchors.push(hit);
501
+ }
502
+ return Object.freeze({ kind: 'served', certificates: Object.freeze(served), anchors: Object.freeze(anchors) });
503
+ }
504
+ catch {
505
+ return refuseBody('bad_input');
506
+ }
507
+ }
508
+ function stopReasons() {
509
+ const all = {
510
+ bad_input: true, cancelled: true, timed_out: true, transport_failed: true, content_type: true, ca_unavailable: true, endpoint_missing: true,
511
+ unexpected_status: true, retry_after_unusable: true, retry_after_too_long: true, retries_exhausted: true, budget_exhausted: true,
512
+ };
513
+ return Object.keys(all);
514
+ }
515
+ function stopOf(reason, extra = {}) {
516
+ return Object.freeze({
517
+ action: 'stop',
518
+ reason,
519
+ ...(extra.status !== undefined ? { status: extra.status } : {}),
520
+ ...(extra.retryAfterSec !== undefined ? { retryAfterSec: extra.retryAfterSec } : {}),
521
+ ...(extra.remainingSec !== undefined ? { remainingSec: extra.remainingSec } : {}),
522
+ });
523
+ }
524
+ function stateOf(f) {
525
+ return Object.freeze({
526
+ phase: f.phase,
527
+ deadlineMs: f.deadlineMs,
528
+ lastNowMs: f.lastNowMs,
529
+ attempts: f.attempts,
530
+ ...(f.roundDeadlineMs !== undefined ? { roundDeadlineMs: f.roundDeadlineMs } : {}),
531
+ ...(f.notBeforeMs !== undefined ? { notBeforeMs: f.notBeforeMs } : {}),
532
+ ...(f.retryAfterSec !== undefined ? { retryAfterSec: f.retryAfterSec } : {}),
533
+ ...(f.stop !== undefined ? { stop: f.stop } : {}),
534
+ });
535
+ }
536
+ function stopped(base, stop) {
537
+ return Object.freeze({ state: stateOf({ phase: 'stopped', deadlineMs: base.deadlineMs, lastNowMs: base.lastNowMs, attempts: base.attempts, stop }), decision: stop });
538
+ }
539
+ export function caBootstrapOpen(nowMs) {
540
+ const t = usableClockMs(nowMs);
541
+ if (t === null)
542
+ return stateOf({ phase: 'stopped', deadlineMs: 0, lastNowMs: 0, attempts: 0, stop: stopOf('bad_input') });
543
+ return stateOf({ phase: 'idle', deadlineMs: t + CA_PIN_LIMITS.budgetMs, lastNowMs: t, attempts: 0 });
544
+ }
545
+ function intSlotOf(o, key, min) {
546
+ const r = slotReadOf(o, key);
547
+ if (r.presence === 'absent')
548
+ return { value: undefined };
549
+ if (r.presence !== 'present' || typeof r.value !== 'number' || !Number.isInteger(r.value) || r.value < min)
550
+ return undefined;
551
+ return { value: r.value };
552
+ }
553
+ function stopFrom(v) {
554
+ if (typeof v !== 'object' || v === null)
555
+ return undefined;
556
+ const action = slotReadOf(v, 'action');
557
+ if (action.presence !== 'present' || action.value !== 'stop')
558
+ return undefined;
559
+ const reasonSlot = slotReadOf(v, 'reason');
560
+ if (reasonSlot.presence !== 'present' || typeof reasonSlot.value !== 'string')
561
+ return undefined;
562
+ const word = stopReasons().find((w) => w === reasonSlot.value);
563
+ if (word === undefined)
564
+ return undefined;
565
+ const status = intSlotOf(v, 'status', Number.NEGATIVE_INFINITY);
566
+ const retry = intSlotOf(v, 'retryAfterSec', 1);
567
+ const remaining = intSlotOf(v, 'remainingSec', 0);
568
+ if (status === undefined || retry === undefined || remaining === undefined)
569
+ return undefined;
570
+ const retryHome = word === 'retry_after_too_long' || word === 'retries_exhausted';
571
+ if ((status.value !== undefined) !== (word === 'unexpected_status'))
572
+ return undefined;
573
+ if (retry.value === undefined ? retryHome : !(retryHome || word === 'budget_exhausted'))
574
+ return undefined;
575
+ if ((remaining.value !== undefined) !== (word === 'budget_exhausted'))
576
+ return undefined;
577
+ return stopOf(word, {
578
+ ...(status.value !== undefined ? { status: status.value } : {}),
579
+ ...(retry.value !== undefined ? { retryAfterSec: retry.value } : {}),
580
+ ...(remaining.value !== undefined ? { remainingSec: remaining.value } : {}),
581
+ });
582
+ }
583
+ function readState(v) {
584
+ if (typeof v !== 'object' || v === null)
585
+ return undefined;
586
+ const o = v;
587
+ const read = (k) => {
588
+ const r = slotReadOf(o, k);
589
+ if (r.presence === 'unreadable')
590
+ return undefined;
591
+ return { present: r.presence === 'present', value: r.value };
592
+ };
593
+ const phase = read('phase');
594
+ const deadline = read('deadlineMs');
595
+ const last = read('lastNowMs');
596
+ const attempts = read('attempts');
597
+ const round = read('roundDeadlineMs');
598
+ const notBefore = read('notBeforeMs');
599
+ const ra = read('retryAfterSec');
600
+ const stop = read('stop');
601
+ if (!phase || !deadline || !last || !attempts || !round || !notBefore || !ra || !stop)
602
+ return undefined;
603
+ const p = phase.value;
604
+ if (p !== 'idle' && p !== 'in-flight' && p !== 'reading' && p !== 'waiting' && p !== 'stopped')
605
+ return undefined;
606
+ const d = usableClockMs(deadline.value);
607
+ const l = usableClockMs(last.value);
608
+ const a = attempts.value;
609
+ if (d === null || l === null || typeof a !== 'number' || !Number.isInteger(a) || a < 0 || a > CA_PIN_LIMITS.retriesMax + 1)
610
+ return undefined;
611
+ const out = { phase: p, deadlineMs: d, lastNowMs: l, attempts: a };
612
+ const wantRound = p === 'in-flight' || p === 'reading';
613
+ const wantWait = p === 'waiting';
614
+ const wantStop = p === 'stopped';
615
+ if (round.present !== wantRound || notBefore.present !== wantWait || ra.present !== wantWait || stop.present !== wantStop)
616
+ return undefined;
617
+ if (p === 'idle' && a !== 0)
618
+ return undefined;
619
+ if (wantRound) {
620
+ const r = usableClockMs(round.value);
621
+ if (r === null || r > d || a < 1)
622
+ return undefined;
623
+ out.roundDeadlineMs = r;
624
+ }
625
+ if (wantWait) {
626
+ const nb = usableClockMs(notBefore.value);
627
+ const sec = ra.value;
628
+ if (nb === null || typeof sec !== 'number' || !Number.isInteger(sec) || sec < 1 || sec * 1000 > CA_PIN_LIMITS.waitCapMs)
629
+ return undefined;
630
+ if (nb + CA_PIN_LIMITS.roundMs > d || a < 1 || a > CA_PIN_LIMITS.retriesMax)
631
+ return undefined;
632
+ out.notBeforeMs = nb;
633
+ out.retryAfterSec = sec;
634
+ }
635
+ if (wantStop) {
636
+ const minted = stopFrom(stop.value);
637
+ if (minted === undefined)
638
+ return undefined;
639
+ out.stop = minted;
640
+ }
641
+ return out;
642
+ }
643
+ function retryAfterSecOf(v) {
644
+ if (typeof v !== 'string')
645
+ return undefined;
646
+ const t = v.replace(/^[ \t]+|[ \t]+$/g, '');
647
+ return /^[1-9][0-9]{0,8}$/.test(t) ? Number(t) : undefined;
648
+ }
649
+ function isJsonMediaType(v) {
650
+ if (typeof v !== 'string')
651
+ return false;
652
+ const semi = v.indexOf(';');
653
+ return (semi < 0 ? v : v.slice(0, semi)).replace(/^[ \t]+|[ \t]+$/g, '').toLowerCase() === 'application/json';
654
+ }
655
+ export function caBootstrapStep(state, event, nowMs) {
656
+ let s;
657
+ try {
658
+ s = readState(state);
659
+ if (s === undefined)
660
+ return stopped({ deadlineMs: 0, lastNowMs: 0, attempts: 0 }, stopOf('bad_input'));
661
+ if (s.phase === 'stopped' && s.stop !== undefined)
662
+ return Object.freeze({ state, decision: s.stop });
663
+ const base = { deadlineMs: s.deadlineMs, lastNowMs: s.lastNowMs, attempts: s.attempts };
664
+ if (typeof event !== 'object' || event === null)
665
+ return stopped(base, stopOf('bad_input'));
666
+ const typeSlot = slotReadOf(event, 'type');
667
+ if (typeSlot.presence !== 'present')
668
+ return stopped(base, stopOf('bad_input'));
669
+ const type = typeSlot.value;
670
+ if (type === 'cancelled')
671
+ return stopped(base, stopOf('cancelled'));
672
+ const now = usableClockMs(nowMs);
673
+ if (now === null || now < s.lastNowMs)
674
+ return stopped(base, stopOf('bad_input'));
675
+ const at = { deadlineMs: s.deadlineMs, lastNowMs: now, attempts: s.attempts };
676
+ const D = s.deadlineMs;
677
+ const remainingSec = Math.max(0, Math.floor((D - now) / 1000));
678
+ const attempts = s.attempts;
679
+ const admit = () => {
680
+ const decision = Object.freeze({ action: 'attempt', timeoutMs: CA_PIN_LIMITS.roundMs });
681
+ return Object.freeze({ state: stateOf({ phase: 'in-flight', deadlineMs: D, lastNowMs: now, attempts: attempts + 1, roundDeadlineMs: now + CA_PIN_LIMITS.roundMs }), decision });
682
+ };
683
+ switch (s.phase) {
684
+ case 'idle':
685
+ if (type !== 'begin')
686
+ return stopped(at, stopOf('bad_input'));
687
+ if (now + CA_PIN_LIMITS.roundMs > D)
688
+ return stopped(at, stopOf('budget_exhausted', { remainingSec }));
689
+ return admit();
690
+ case 'waiting': {
691
+ if (type !== 'begin')
692
+ return stopped(at, stopOf('bad_input'));
693
+ const notBefore = s.notBeforeMs ?? Number.POSITIVE_INFINITY;
694
+ const ra = s.retryAfterSec ?? 0;
695
+ if (now < notBefore) {
696
+ const decision = Object.freeze({ action: 'wait', afterMs: notBefore - now, retryAfterSec: ra });
697
+ return Object.freeze({ state: stateOf({ ...s, lastNowMs: now }), decision });
698
+ }
699
+ if (now + CA_PIN_LIMITS.roundMs > D)
700
+ return stopped(at, stopOf('budget_exhausted', { retryAfterSec: ra, remainingSec }));
701
+ return admit();
702
+ }
703
+ case 'in-flight':
704
+ case 'reading': {
705
+ const roundDeadline = s.roundDeadlineMs ?? Number.NEGATIVE_INFINITY;
706
+ if (type === 'timed-out')
707
+ return stopped(at, stopOf('timed_out'));
708
+ if (type === 'failed')
709
+ return stopped(at, stopOf('transport_failed'));
710
+ if (s.phase === 'reading') {
711
+ if (type !== 'checkpoint')
712
+ return stopped(at, stopOf('bad_input'));
713
+ if (now > roundDeadline)
714
+ return stopped(at, stopOf('timed_out'));
715
+ return Object.freeze({ state: stateOf({ ...s, lastNowMs: now }), decision: Object.freeze({ action: 'proceed' }) });
716
+ }
717
+ if (type !== 'response')
718
+ return stopped(at, stopOf('bad_input'));
719
+ if (now > roundDeadline)
720
+ return stopped(at, stopOf('timed_out'));
721
+ const status = slotReadOf(event, 'status');
722
+ if (status.presence !== 'present' || typeof status.value !== 'number' || !Number.isInteger(status.value))
723
+ return stopped(at, stopOf('bad_input'));
724
+ const code = status.value;
725
+ if (code === 200) {
726
+ const ct = slotReadOf(event, 'contentType');
727
+ if (ct.presence === 'present' && isJsonMediaType(ct.value)) {
728
+ return Object.freeze({ state: stateOf({ ...s, phase: 'reading', lastNowMs: now }), decision: Object.freeze({ action: 'read-body' }) });
729
+ }
730
+ return stopped(at, stopOf('content_type'));
731
+ }
732
+ if (code === 503)
733
+ return stopped(at, stopOf('ca_unavailable'));
734
+ if (code === 404)
735
+ return stopped(at, stopOf('endpoint_missing'));
736
+ if (code !== 429)
737
+ return stopped(at, stopOf('unexpected_status', { status: code }));
738
+ const raSlot = slotReadOf(event, 'retryAfter');
739
+ const r = raSlot.presence === 'present' ? retryAfterSecOf(raSlot.value) : undefined;
740
+ if (r === undefined)
741
+ return stopped(at, stopOf('retry_after_unusable'));
742
+ if (s.attempts >= CA_PIN_LIMITS.retriesMax + 1)
743
+ return stopped(at, stopOf('retries_exhausted', { retryAfterSec: r }));
744
+ if (r * 1000 > CA_PIN_LIMITS.waitCapMs)
745
+ return stopped(at, stopOf('retry_after_too_long', { retryAfterSec: r }));
746
+ if (now + r * 1000 + CA_PIN_LIMITS.roundMs > D)
747
+ return stopped(at, stopOf('budget_exhausted', { retryAfterSec: r, remainingSec }));
748
+ const jSlot = slotReadOf(event, 'jitter');
749
+ const u = jSlot.presence === 'present' && typeof jSlot.value === 'number' && jSlot.value >= 0 && jSlot.value < 1 ? jSlot.value : 0;
750
+ const room = Math.min(CA_PIN_LIMITS.jitterMaxMs, CA_PIN_LIMITS.waitCapMs - r * 1000, D - now - r * 1000 - CA_PIN_LIMITS.roundMs);
751
+ const j = Math.floor(u * Math.max(0, room));
752
+ const afterMs = r * 1000 + j;
753
+ const decision = Object.freeze({ action: 'wait', afterMs, retryAfterSec: r });
754
+ return Object.freeze({ state: stateOf({ phase: 'waiting', deadlineMs: D, lastNowMs: now, attempts: s.attempts, notBeforeMs: now + afterMs, retryAfterSec: r }), decision });
755
+ }
756
+ default:
757
+ return stopped(at, stopOf('bad_input'));
758
+ }
759
+ }
760
+ catch {
761
+ const base = s ? { deadlineMs: s.deadlineMs, lastNowMs: s.lastNowMs, attempts: s.attempts } : { deadlineMs: 0, lastNowMs: 0, attempts: 0 };
762
+ return stopped(base, stopOf('bad_input'));
763
+ }
764
+ }
765
+ function refusalKeys() {
766
+ return {
767
+ bad_input: 'centerTrust.refusal.badInput',
768
+ empty: 'centerTrust.refusal.empty',
769
+ too_long: 'centerTrust.refusal.tooLong',
770
+ unsafe_character: 'centerTrust.refusal.unsafeCharacter',
771
+ scheme: 'centerTrust.refusal.scheme',
772
+ userinfo: 'centerTrust.refusal.userinfo',
773
+ percent_in_host: 'centerTrust.refusal.percentInHost',
774
+ unparseable: 'centerTrust.refusal.unparseable',
775
+ query: 'centerTrust.refusal.query',
776
+ fragment: 'centerTrust.refusal.fragment',
777
+ trailing_dot: 'centerTrust.refusal.trailingDot',
778
+ ip_not_canonical: 'centerTrust.refusal.ipNotCanonical',
779
+ plaintext_remote: 'centerTrust.refusal.plaintextRemote',
780
+ path: 'centerTrust.refusal.path',
781
+ fingerprint: 'centerTrust.refusal.fingerprint',
782
+ too_many_fingerprints: 'centerTrust.refusal.tooManyFingerprints',
783
+ prefix: 'centerTrust.refusal.prefix',
784
+ parser_unavailable: 'centerTrust.refusal.parserUnavailable',
785
+ body_too_large: 'centerTrust.refusal.bodyTooLarge',
786
+ body_not_json: 'centerTrust.refusal.bodyNotJson',
787
+ body_shape: 'centerTrust.refusal.bodyShape',
788
+ served_count: 'centerTrust.refusal.servedCount',
789
+ certificate_too_large: 'centerTrust.refusal.certificateTooLarge',
790
+ certificate_not_canonical: 'centerTrust.refusal.certificateNotCanonical',
791
+ certificate_unparseable: 'centerTrust.refusal.certificateUnparseable',
792
+ served_duplicate: 'centerTrust.refusal.servedDuplicate',
793
+ named_not_served: 'centerTrust.refusal.namedNotServed',
794
+ anchor_not_self_signed: 'centerTrust.refusal.anchorNotSelfSigned',
795
+ unconfigured_with_fingerprints: 'centerTrust.refusal.unconfiguredWithFingerprints',
796
+ cancelled: 'centerTrust.refusal.cancelled',
797
+ timed_out: 'centerTrust.refusal.timedOut',
798
+ transport_failed: 'centerTrust.refusal.transportFailed',
799
+ content_type: 'centerTrust.refusal.contentType',
800
+ ca_unavailable: 'centerTrust.refusal.caUnavailable',
801
+ endpoint_missing: 'centerTrust.refusal.endpointMissing',
802
+ unexpected_status: 'centerTrust.refusal.unexpectedStatus',
803
+ retry_after_unusable: 'centerTrust.refusal.retryAfterUnusable',
804
+ retry_after_too_long: 'centerTrust.refusal.retryAfterTooLong',
805
+ retries_exhausted: 'centerTrust.refusal.retriesExhausted',
806
+ budget_exhausted: 'centerTrust.refusal.budgetExhausted',
807
+ };
808
+ }
809
+ export function caPinRefusalSentence(reason, lang) {
810
+ const L = CA_PIN_LIMITS;
811
+ const params = {
812
+ addressMax: L.addressMaxChars,
813
+ authorityMax: L.authorityMaxChars,
814
+ fingerprintsMax: L.fingerprintsMax,
815
+ bodyMax: L.bodyMaxBytes,
816
+ servedMax: L.servedMax,
817
+ pemMax: L.pemMaxBytes,
818
+ roundSec: L.roundMs / 1000,
819
+ waitCapSec: L.waitCapMs / 1000,
820
+ attemptsMax: L.retriesMax + 1,
821
+ budgetSec: L.budgetMs / 1000,
822
+ };
823
+ const keys = refusalKeys();
824
+ const word = reason;
825
+ const key = typeof word === 'string' && Object.hasOwn(keys, word) ? keys[word] : 'centerTrust.refusal.unknown';
826
+ return renderMessage(msg(key, params), lang);
827
+ }