@sentropic/llm-mesh 0.27.1 → 0.28.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 (160) hide show
  1. package/README.md +143 -0
  2. package/dist/capabilities.d.ts +4 -1
  3. package/dist/capabilities.d.ts.map +1 -1
  4. package/dist/capabilities.js.map +1 -1
  5. package/dist/catalog.d.ts +135 -1
  6. package/dist/catalog.d.ts.map +1 -1
  7. package/dist/catalog.js +16 -0
  8. package/dist/catalog.js.map +1 -1
  9. package/dist/decision/adapter-result.d.ts +6 -0
  10. package/dist/decision/adapter-result.d.ts.map +1 -0
  11. package/dist/decision/adapter-result.js +148 -0
  12. package/dist/decision/adapter-result.js.map +1 -0
  13. package/dist/decision/adapter.d.ts +58 -0
  14. package/dist/decision/adapter.d.ts.map +1 -0
  15. package/dist/decision/adapter.js +70 -0
  16. package/dist/decision/adapter.js.map +1 -0
  17. package/dist/decision/admission.d.ts +23 -0
  18. package/dist/decision/admission.d.ts.map +1 -0
  19. package/dist/decision/admission.js +115 -0
  20. package/dist/decision/admission.js.map +1 -0
  21. package/dist/decision/budget.d.ts +60 -0
  22. package/dist/decision/budget.d.ts.map +1 -0
  23. package/dist/decision/budget.js +2 -0
  24. package/dist/decision/budget.js.map +1 -0
  25. package/dist/decision/client.d.ts +23 -0
  26. package/dist/decision/client.d.ts.map +1 -0
  27. package/dist/decision/client.js +836 -0
  28. package/dist/decision/client.js.map +1 -0
  29. package/dist/decision/criterion.d.ts +5 -0
  30. package/dist/decision/criterion.d.ts.map +1 -0
  31. package/dist/decision/criterion.js +54 -0
  32. package/dist/decision/criterion.js.map +1 -0
  33. package/dist/decision/deadline.d.ts +8 -0
  34. package/dist/decision/deadline.d.ts.map +1 -0
  35. package/dist/decision/deadline.js +56 -0
  36. package/dist/decision/deadline.js.map +1 -0
  37. package/dist/decision/descriptor-validation.d.ts +5 -0
  38. package/dist/decision/descriptor-validation.d.ts.map +1 -0
  39. package/dist/decision/descriptor-validation.js +99 -0
  40. package/dist/decision/descriptor-validation.js.map +1 -0
  41. package/dist/decision/descriptor.d.ts +81 -0
  42. package/dist/decision/descriptor.d.ts.map +1 -0
  43. package/dist/decision/descriptor.js +36 -0
  44. package/dist/decision/descriptor.js.map +1 -0
  45. package/dist/decision/dispatch.d.ts +11 -0
  46. package/dist/decision/dispatch.d.ts.map +1 -0
  47. package/dist/decision/dispatch.js +56 -0
  48. package/dist/decision/dispatch.js.map +1 -0
  49. package/dist/decision/errors.d.ts +43 -0
  50. package/dist/decision/errors.d.ts.map +1 -0
  51. package/dist/decision/errors.js +47 -0
  52. package/dist/decision/errors.js.map +1 -0
  53. package/dist/decision/execution.d.ts +31 -0
  54. package/dist/decision/execution.d.ts.map +1 -0
  55. package/dist/decision/execution.js +79 -0
  56. package/dist/decision/execution.js.map +1 -0
  57. package/dist/decision/host-options.d.ts +26 -0
  58. package/dist/decision/host-options.d.ts.map +1 -0
  59. package/dist/decision/host-options.js +2 -0
  60. package/dist/decision/host-options.js.map +1 -0
  61. package/dist/decision/host.d.ts +14 -0
  62. package/dist/decision/host.d.ts.map +1 -0
  63. package/dist/decision/host.js +6 -0
  64. package/dist/decision/host.js.map +1 -0
  65. package/dist/decision/index.d.ts +12 -0
  66. package/dist/decision/index.d.ts.map +1 -0
  67. package/dist/decision/index.js +3 -0
  68. package/dist/decision/index.js.map +1 -0
  69. package/dist/decision/jev-codec.d.ts +30 -0
  70. package/dist/decision/jev-codec.d.ts.map +1 -0
  71. package/dist/decision/jev-codec.js +51 -0
  72. package/dist/decision/jev-codec.js.map +1 -0
  73. package/dist/decision/jev-response.d.ts +7 -0
  74. package/dist/decision/jev-response.d.ts.map +1 -0
  75. package/dist/decision/jev-response.js +42 -0
  76. package/dist/decision/jev-response.js.map +1 -0
  77. package/dist/decision/jev-verdict.d.ts +14 -0
  78. package/dist/decision/jev-verdict.d.ts.map +1 -0
  79. package/dist/decision/jev-verdict.js +71 -0
  80. package/dist/decision/jev-verdict.js.map +1 -0
  81. package/dist/decision/policy.d.ts +11 -0
  82. package/dist/decision/policy.d.ts.map +1 -0
  83. package/dist/decision/policy.js +33 -0
  84. package/dist/decision/policy.js.map +1 -0
  85. package/dist/decision/pricing.d.ts +40 -0
  86. package/dist/decision/pricing.d.ts.map +1 -0
  87. package/dist/decision/pricing.js +65 -0
  88. package/dist/decision/pricing.js.map +1 -0
  89. package/dist/decision/raw.d.ts +7 -0
  90. package/dist/decision/raw.d.ts.map +1 -0
  91. package/dist/decision/raw.js +53 -0
  92. package/dist/decision/raw.js.map +1 -0
  93. package/dist/decision/receipt.d.ts +120 -0
  94. package/dist/decision/receipt.d.ts.map +1 -0
  95. package/dist/decision/receipt.js +58 -0
  96. package/dist/decision/receipt.js.map +1 -0
  97. package/dist/decision/request-policy.d.ts +3 -0
  98. package/dist/decision/request-policy.d.ts.map +1 -0
  99. package/dist/decision/request-policy.js +19 -0
  100. package/dist/decision/request-policy.js.map +1 -0
  101. package/dist/decision/request.d.ts +85 -0
  102. package/dist/decision/request.d.ts.map +1 -0
  103. package/dist/decision/request.js +49 -0
  104. package/dist/decision/request.js.map +1 -0
  105. package/dist/decision/service.d.ts +39 -0
  106. package/dist/decision/service.d.ts.map +1 -0
  107. package/dist/decision/service.js +138 -0
  108. package/dist/decision/service.js.map +1 -0
  109. package/dist/decision/structured.d.ts +125 -0
  110. package/dist/decision/structured.d.ts.map +1 -0
  111. package/dist/decision/structured.js +250 -0
  112. package/dist/decision/structured.js.map +1 -0
  113. package/dist/decision/validate-result.d.ts +8 -0
  114. package/dist/decision/validate-result.d.ts.map +1 -0
  115. package/dist/decision/validate-result.js +91 -0
  116. package/dist/decision/validate-result.js.map +1 -0
  117. package/dist/decision/validation-context.d.ts +55 -0
  118. package/dist/decision/validation-context.d.ts.map +1 -0
  119. package/dist/decision/validation-context.js +134 -0
  120. package/dist/decision/validation-context.js.map +1 -0
  121. package/dist/decision/validation.d.ts +19 -0
  122. package/dist/decision/validation.d.ts.map +1 -0
  123. package/dist/decision/validation.js +86 -0
  124. package/dist/decision/validation.js.map +1 -0
  125. package/dist/decision/verdict.d.ts +81 -0
  126. package/dist/decision/verdict.d.ts.map +1 -0
  127. package/dist/decision/verdict.js +140 -0
  128. package/dist/decision/verdict.js.map +1 -0
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +1 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/providers.d.ts +4 -3
  134. package/dist/providers.d.ts.map +1 -1
  135. package/dist/providers.js +2 -1
  136. package/dist/providers.js.map +1 -1
  137. package/dist/registry.d.ts +6 -0
  138. package/dist/registry.d.ts.map +1 -1
  139. package/dist/registry.js.map +1 -1
  140. package/dist/speech/capabilities.d.ts +48 -0
  141. package/dist/speech/capabilities.d.ts.map +1 -0
  142. package/dist/speech/capabilities.js +41 -0
  143. package/dist/speech/capabilities.js.map +1 -0
  144. package/dist/speech/catalog.d.ts +8 -0
  145. package/dist/speech/catalog.d.ts.map +1 -0
  146. package/dist/speech/catalog.js +5 -0
  147. package/dist/speech/catalog.js.map +1 -0
  148. package/dist/speech/contracts.d.ts +196 -0
  149. package/dist/speech/contracts.d.ts.map +1 -0
  150. package/dist/speech/contracts.js +39 -0
  151. package/dist/speech/contracts.js.map +1 -0
  152. package/dist/speech/index.d.ts +4 -0
  153. package/dist/speech/index.d.ts.map +1 -0
  154. package/dist/speech/index.js +4 -0
  155. package/dist/speech/index.js.map +1 -0
  156. package/dist/transport/jev.d.ts +23 -0
  157. package/dist/transport/jev.d.ts.map +1 -0
  158. package/dist/transport/jev.js +107 -0
  159. package/dist/transport/jev.js.map +1 -0
  160. package/package.json +17 -1
@@ -0,0 +1,836 @@
1
+ import { parseDecisionRequest } from './request.js';
2
+ import { parseDecisionUsage, resolveDecisionCorrelationId, unreportedDecisionUsage } from './receipt.js';
3
+ import { createDecisionError, isDecisionErrorCode } from './errors.js';
4
+ import { sanitizeJevId } from './jev-response.js';
5
+ import { parseDecisionConfidence, parseDecisionScore } from './verdict.js';
6
+ import { DECISION_MAX_JSON_BYTES, has, hasOnlyKeys, isMicroUsd, isRecord, isText, snapshotBoundedJson } from './validation.js';
7
+ import { freezeDecisionData } from './descriptor-validation.js';
8
+ import { parseDecisionValidationContext } from './validation-context.js';
9
+ const resolveEndpoint = (base) => {
10
+ const trimmed = base.replace(/\/+$/, '');
11
+ return trimmed.endsWith('/decision') ? trimmed : `${trimmed}/decision`;
12
+ };
13
+ const connectionError = (code, phase) => createDecisionError(code, phase);
14
+ const BUDGET_STATES = new Set(['not-admitted', 'released', 'settled', 'pending-reconciliation']);
15
+ const DISPATCH_STATES = new Set(['not-started', 'started', 'completed', 'unknown']);
16
+ /** Only the three service-emitted value shapes: binary scores alone, choice
17
+ * labels with a distribution, graded scores with one. Anything else never
18
+ * leaves the service, so the client rejects it without consulting the wire. */
19
+ const hasServiceVerdictShape = (score, label, confidence, distribution) => {
20
+ const hasScore = score !== null, hasLabel = label !== null;
21
+ const hasConfidence = confidence !== null, hasDistribution = distribution !== null;
22
+ if (hasLabel)
23
+ return !hasScore && hasDistribution && hasConfidence;
24
+ if (hasDistribution)
25
+ return hasScore && hasConfidence;
26
+ return hasScore && !hasConfidence;
27
+ };
28
+ /** Raw native values must agree with the typed verdict derived with them:
29
+ * same values under the same fields, and a truncation flag that matches
30
+ * exactly which native fields were omitted. */
31
+ const wireRawAgreesWithTyped = (data, verdict, truncated) => {
32
+ const score = isRecord(verdict.score) && typeof verdict.score.value === 'number' ? verdict.score.value : null;
33
+ const label = isRecord(verdict.label) && typeof verdict.label.value === 'string' ? verdict.label.value : null;
34
+ const confidence = isRecord(verdict.confidence) && typeof verdict.confidence.value === 'number'
35
+ ? verdict.confidence.value : null;
36
+ const distribution = Array.isArray(verdict.distribution) ? verdict.distribution : null;
37
+ const expected = distribution === null ? ['noul']
38
+ : label !== null ? ['choice', 'probabilities', 'confidence'] : ['score', 'probabilities', 'confidence'];
39
+ if (!Object.keys(data).every((key) => expected.includes(key)))
40
+ return false;
41
+ if (has(data, 'noul') && data.noul !== score)
42
+ return false;
43
+ if (has(data, 'score') && data.score !== score)
44
+ return false;
45
+ if (has(data, 'choice') && data.choice !== label)
46
+ return false;
47
+ if (has(data, 'confidence') && data.confidence !== confidence)
48
+ return false;
49
+ if (has(data, 'probabilities')) {
50
+ const probs = data.probabilities;
51
+ if (!isRecord(probs) || !distribution)
52
+ return false;
53
+ if (Object.keys(probs).length !== distribution.length)
54
+ return false;
55
+ for (const entry of distribution) {
56
+ if (!isRecord(entry) || typeof entry.nativeOutcome !== 'string' || typeof entry.probability !== 'number'
57
+ || !has(probs, entry.nativeOutcome) || probs[entry.nativeOutcome] !== entry.probability)
58
+ return false;
59
+ }
60
+ }
61
+ return truncated === expected.some((key) => !has(data, key));
62
+ };
63
+ /** Cost union invariant: an unincurred or estimated cost never carries a spent amount. */
64
+ function isValidWireCost(cost) {
65
+ if (!isRecord(cost))
66
+ return false;
67
+ if (cost.status === 'actual') {
68
+ return hasOnlyKeys(cost, ['status', 'amountMicroUsd', 'source', 'priceRevision', 'rounding'])
69
+ && isMicroUsd(cost.amountMicroUsd) && cost.source === 'provider' && cost.rounding === 'ceil-micro-usd'
70
+ && (cost.priceRevision === undefined || typeof cost.priceRevision === 'string');
71
+ }
72
+ if (cost.status === 'estimated') {
73
+ return hasOnlyKeys(cost, ['status', 'amountMicroUsd', 'source', 'priceRevision', 'rounding'])
74
+ && isMicroUsd(cost.amountMicroUsd) && cost.rounding === 'ceil-micro-usd'
75
+ && typeof cost.priceRevision === 'string'
76
+ && (cost.source === 'reported-usage-times-rate' || cost.source === 'call-rate' || cost.source === 'reserved-liability');
77
+ }
78
+ return cost.status === 'not-incurred' && hasOnlyKeys(cost, ['status', 'amountMicroUsd', 'source'])
79
+ && cost.amountMicroUsd === '0' && cost.source === 'no-dispatch';
80
+ }
81
+ function isValidWireRetention(retention) {
82
+ if (!isRecord(retention))
83
+ return false;
84
+ if (!hasOnlyKeys(retention, ['status', 'basis', 'evidenceRefs', 'retentionDays', 'gap']))
85
+ return false;
86
+ if (retention.status !== 'non-zdr' && retention.status !== 'unknown' && retention.status !== 'not-evaluated')
87
+ return false;
88
+ if (retention.basis !== 'published-policy' && retention.basis !== 'none')
89
+ return false;
90
+ if (!Array.isArray(retention.evidenceRefs) || !retention.evidenceRefs.every((ref) => typeof ref === 'string'))
91
+ return false;
92
+ if (retention.retentionDays !== null && (typeof retention.retentionDays !== 'number'
93
+ || !Number.isSafeInteger(retention.retentionDays) || retention.retentionDays < 0))
94
+ return false;
95
+ return retention.gap === undefined || retention.gap === 'unknown (source-gap)';
96
+ }
97
+ const EVALUATOR_KEYS = ['providerId', 'evaluatorId', 'revision'];
98
+ const bindsEvaluator = (candidate, request) => isRecord(candidate) && hasOnlyKeys(candidate, EVALUATOR_KEYS)
99
+ && candidate.providerId === request.evaluator.providerId
100
+ && candidate.evaluatorId === request.evaluator.evaluatorId
101
+ && candidate.revision === request.evaluator.revision;
102
+ const RECEIPT_KEYS = ['schemaVersion', 'callId', 'correlationId', 'evaluator', 'servedEvaluator',
103
+ 'servedModelId', 'providerRequestId', 'startedAt', 'finishedAt', 'latencyMs', 'providerLatencyMs',
104
+ 'dispatch', 'attempts', 'usage', 'cost', 'retention', 'budget', 'outcome', 'errorCode'];
105
+ export function isValidDecisionReceipt(r, request, expectedCorrelationId, validation) {
106
+ if (!isRecord(r))
107
+ return false;
108
+ if (!hasOnlyKeys(r, RECEIPT_KEYS))
109
+ return false;
110
+ if (r.schemaVersion !== 1 || typeof r.callId !== 'string' || !r.callId)
111
+ return false;
112
+ if (r.correlationId !== expectedCorrelationId)
113
+ return false;
114
+ if (r.evaluator !== null && !bindsEvaluator(r.evaluator, request))
115
+ return false;
116
+ if (r.servedEvaluator !== null && (!isRecord(r.servedEvaluator)
117
+ || !hasOnlyKeys(r.servedEvaluator, ['providerId', 'evaluatorId', 'revision'])
118
+ || typeof r.servedEvaluator.providerId !== 'string' || typeof r.servedEvaluator.evaluatorId !== 'string'
119
+ || typeof r.servedEvaluator.revision !== 'string'))
120
+ return false;
121
+ if (r.servedModelId !== null && sanitizeJevId(r.servedModelId) !== r.servedModelId)
122
+ return false;
123
+ if (has(r, 'providerRequestId') && sanitizeJevId(r.providerRequestId) !== r.providerRequestId)
124
+ return false;
125
+ if (has(r, 'providerLatencyMs') && (typeof r.providerLatencyMs !== 'number'
126
+ || !Number.isFinite(r.providerLatencyMs) || r.providerLatencyMs < 0))
127
+ return false;
128
+ if (typeof r.startedAt !== 'string' || Number.isNaN(Date.parse(r.startedAt)))
129
+ return false;
130
+ if (typeof r.finishedAt !== 'string' || Number.isNaN(Date.parse(r.finishedAt)))
131
+ return false;
132
+ if (typeof r.latencyMs !== 'number' || !Number.isFinite(r.latencyMs) || r.latencyMs < 0)
133
+ return false;
134
+ if (!DISPATCH_STATES.has(r.dispatch))
135
+ return false;
136
+ if (r.attempts !== 0 && r.attempts !== 1)
137
+ return false;
138
+ if (!isValidWireRetention(r.retention))
139
+ return false;
140
+ if (!isRecord(r.budget) || !hasOnlyKeys(r.budget, ['state', 'overrun', 'reservedMicroUsd'])
141
+ || !BUDGET_STATES.has(r.budget.state) || typeof r.budget.overrun !== 'boolean'
142
+ || (r.budget.reservedMicroUsd !== undefined && !isMicroUsd(r.budget.reservedMicroUsd)))
143
+ return false;
144
+ const cost = r.cost;
145
+ if (!isValidWireCost(cost))
146
+ return false;
147
+ if (r.outcome !== 'verdict' && r.outcome !== 'error')
148
+ return false;
149
+ if (r.outcome === 'error' && (typeof r.errorCode !== 'string' || !r.errorCode))
150
+ return false;
151
+ if (r.outcome === 'verdict' && r.errorCode !== undefined)
152
+ return false;
153
+ const usage = parseDecisionUsage(r.usage);
154
+ if (!usage)
155
+ return false;
156
+ // A verdict proves dispatch: identities served, one attempt completed,
157
+ // provider counters reported, liability estimated under a held budget.
158
+ // V-I1: a successful receipt names exactly the transmitted evaluator.
159
+ if (r.outcome === 'verdict') {
160
+ if (!validation)
161
+ return false;
162
+ if (r.evaluator === null || r.servedEvaluator === null || r.servedModelId === null)
163
+ return false;
164
+ if (!bindsEvaluator(r.servedEvaluator, request))
165
+ return false;
166
+ // P16: the served model must sit in the trusted accepted set, including
167
+ // JEV through the canonical context-bound API.
168
+ if (!validation.acceptedServedModelIds.includes(r.servedModelId))
169
+ return false;
170
+ if (r.attempts !== 1 || r.dispatch !== 'completed')
171
+ return false;
172
+ if (usage.tokens.input.source !== 'provider' || usage.tokens.output.source !== 'provider'
173
+ || usage.evaluatorCalls !== 1 || usage.explanations !== 0)
174
+ return false;
175
+ if (cost.status !== 'estimated')
176
+ return false;
177
+ if (r.budget.state !== 'settled' && r.budget.state !== 'pending-reconciliation')
178
+ return false;
179
+ }
180
+ return true;
181
+ }
182
+ const wireDistributionEntries = (distribution) => {
183
+ if (!Array.isArray(distribution) || !distribution.length)
184
+ return null;
185
+ const entries = [];
186
+ for (const entry of distribution) {
187
+ if (!isRecord(entry) || !hasOnlyKeys(entry, ['outcome', 'nativeOutcome', 'probability'])
188
+ || typeof entry.outcome !== 'string' || typeof entry.nativeOutcome !== 'string'
189
+ || typeof entry.probability !== 'number' || !Number.isFinite(entry.probability)
190
+ || entry.probability < 0 || entry.probability > 1)
191
+ return null;
192
+ entries.push({ outcome: entry.outcome, nativeOutcome: entry.nativeOutcome, probability: entry.probability });
193
+ }
194
+ if (Math.abs(entries.reduce((sum, entry) => sum + entry.probability, 0) - 1) > 1e-4)
195
+ return null;
196
+ return entries;
197
+ };
198
+ /** V4-L1 JEV evidence binding: exact native index/label mapping, order-independent, no invention. */
199
+ function isValidChoiceEvidence(distribution, label, request) {
200
+ if (request.criterion.kind !== 'choice')
201
+ return false;
202
+ const declared = request.criterion.options.map((option) => option.id);
203
+ const entries = wireDistributionEntries(distribution);
204
+ if (!entries || entries.length !== declared.length)
205
+ return false;
206
+ const declaredSet = new Set(declared);
207
+ const outcomeSet = new Set();
208
+ const nativeSet = new Set();
209
+ for (const entry of entries) {
210
+ if (entry.outcome !== entry.nativeOutcome)
211
+ return false;
212
+ if (!declaredSet.has(entry.outcome) || !declaredSet.has(entry.nativeOutcome))
213
+ return false;
214
+ outcomeSet.add(entry.outcome);
215
+ nativeSet.add(entry.nativeOutcome);
216
+ }
217
+ if (outcomeSet.size !== declared.length || nativeSet.size !== declared.length)
218
+ return false;
219
+ if (!isRecord(label) || !hasOnlyKeys(label, ['value', 'origin'])
220
+ || typeof label.value !== 'string' || label.origin !== 'provider')
221
+ return false;
222
+ if (!declaredSet.has(label.value))
223
+ return false;
224
+ const max = Math.max(...entries.map((entry) => entry.probability));
225
+ const selected = entries.find((entry) => entry.outcome === label.value);
226
+ return selected !== undefined && selected.probability === max;
227
+ }
228
+ function isValidGradedEvidence(distribution, score, request) {
229
+ if (request.criterion.kind !== 'graded')
230
+ return false;
231
+ const levels = request.criterion.levels;
232
+ const entries = wireDistributionEntries(distribution);
233
+ if (!entries || entries.length !== levels.length)
234
+ return false;
235
+ const expectedNative = new Set(levels.map((_, index) => String(index)));
236
+ const seenNative = new Set();
237
+ const seenOutcome = new Set();
238
+ for (const entry of entries) {
239
+ if (!expectedNative.has(entry.nativeOutcome))
240
+ return false;
241
+ const index = Number(entry.nativeOutcome);
242
+ if (!Number.isSafeInteger(index) || index < 0 || index >= levels.length)
243
+ return false;
244
+ if (levels[index].label !== entry.outcome)
245
+ return false;
246
+ seenNative.add(entry.nativeOutcome);
247
+ seenOutcome.add(entry.outcome);
248
+ }
249
+ if (seenNative.size !== levels.length || seenOutcome.size !== levels.length)
250
+ return false;
251
+ if (score === null || !isRecord(score) || typeof score.value !== 'number')
252
+ return false;
253
+ const weighted = entries.reduce((sum, entry) => sum + Number(entry.nativeOutcome) * entry.probability, 0);
254
+ return Math.abs(weighted - score.value) <= 1e-4;
255
+ }
256
+ function sameDecisionRule(left, right) {
257
+ if (right === undefined)
258
+ return false;
259
+ if (!isRecord(left))
260
+ return false;
261
+ if (right.kind === 'threshold') {
262
+ return hasOnlyKeys(left, ['kind', 'field', 'op', 'value'])
263
+ && left.kind === 'threshold' && left.field === 'score'
264
+ && (left.op === 'gte' || left.op === 'lte')
265
+ && left.op === right.op && left.value === right.value;
266
+ }
267
+ if (right.kind === 'labels') {
268
+ if (!hasOnlyKeys(left, ['kind', 'passing']) || left.kind !== 'labels' || !Array.isArray(left.passing))
269
+ return false;
270
+ const passing = left.passing;
271
+ return passing.length === right.passing.length
272
+ && passing.every((value, index) => value === right.passing[index]);
273
+ }
274
+ return false;
275
+ }
276
+ /** V-D1/V-D2: exact rule echo and recomputation from evidence. P18: a declared
277
+ * native boolean passes without a rule key; anything else provider-origin is forged. */
278
+ function isValidWireDecision(decision, request, evidence, sem) {
279
+ if (isRecord(decision) && decision.origin === 'provider') {
280
+ return sem.nativeDecision && (request.decision === undefined || request.decision.kind === 'provider');
281
+ }
282
+ if (request.decision === undefined)
283
+ return decision === null;
284
+ if (request.decision.kind === 'provider')
285
+ return false;
286
+ if (decision === null)
287
+ return false;
288
+ if (!isRecord(decision) || typeof decision.pass !== 'boolean' || typeof decision.origin !== 'string')
289
+ return false;
290
+ if (request.decision.kind === 'threshold') {
291
+ if (decision.origin !== 'threshold' || !hasOnlyKeys(decision, ['pass', 'origin', 'rule']))
292
+ return false;
293
+ if (!sameDecisionRule(decision.rule, request.decision))
294
+ return false;
295
+ if (evidence.score === null)
296
+ return false;
297
+ // V-D2: the threshold must lie within the trusted native bounded scale.
298
+ const parsed = parseDecisionScore(evidence.score);
299
+ if (parsed === null || parsed.scale.kind !== 'bounded')
300
+ return false;
301
+ if (request.decision.value < parsed.scale.min || request.decision.value > parsed.scale.max)
302
+ return false;
303
+ const value = parsed.value;
304
+ const expected = request.decision.op === 'gte' ? value >= request.decision.value : value <= request.decision.value;
305
+ return decision.pass === expected;
306
+ }
307
+ if (request.decision.kind === 'labels') {
308
+ if (decision.origin !== 'labels' || !hasOnlyKeys(decision, ['pass', 'origin', 'rule']))
309
+ return false;
310
+ if (!sameDecisionRule(decision.rule, request.decision))
311
+ return false;
312
+ if (!isRecord(evidence.label) || typeof evidence.label.value !== 'string')
313
+ return false;
314
+ return decision.pass === request.decision.passing.includes(evidence.label.value);
315
+ }
316
+ return false;
317
+ }
318
+ /** Raw evidence is accepted only when allowlisted by the request, byte-exact,
319
+ * and in full agreement with the typed verdict it was derived with. */
320
+ function isValidWireProviderRaw(raw, request, verdict) {
321
+ if (raw === undefined)
322
+ return true;
323
+ if (!isRecord(raw) || request.raw?.mode !== 'allowlisted')
324
+ return false;
325
+ if (!hasOnlyKeys(raw, ['data', 'bytes', 'truncated']) || !isRecord(raw.data))
326
+ return false;
327
+ if (typeof raw.bytes !== 'number' || !Number.isSafeInteger(raw.bytes) || raw.bytes < 0)
328
+ return false;
329
+ if (typeof raw.truncated !== 'boolean')
330
+ return false;
331
+ const actual = new TextEncoder().encode(JSON.stringify(raw.data)).byteLength;
332
+ if (raw.bytes !== actual || raw.bytes > Math.min(16384, request.raw.maxBytes ?? 16384))
333
+ return false;
334
+ return wireRawAgreesWithTyped(raw.data, verdict, raw.truncated);
335
+ }
336
+ const SCALAR_RAW_KEYS = ['score', 'choice', 'confidence'];
337
+ const scalarRawAgreesWithTyped = (data, verdict, truncated) => {
338
+ if (!Object.keys(data).every((key) => SCALAR_RAW_KEYS.includes(key)))
339
+ return false;
340
+ const score = isRecord(verdict.score) && typeof verdict.score.value === 'number' ? verdict.score.value : null;
341
+ const label = isRecord(verdict.label) && typeof verdict.label.value === 'string' ? verdict.label.value : null;
342
+ const confidence = isRecord(verdict.confidence) && typeof verdict.confidence.value === 'number'
343
+ ? verdict.confidence.value : null;
344
+ if (has(data, 'score') && data.score !== score)
345
+ return false;
346
+ if (has(data, 'choice') && data.choice !== label)
347
+ return false;
348
+ if (has(data, 'confidence') && data.confidence !== confidence)
349
+ return false;
350
+ const expected = [];
351
+ if (score !== null)
352
+ expected.push('score');
353
+ if (label !== null)
354
+ expected.push('choice');
355
+ if (confidence !== null)
356
+ expected.push('confidence');
357
+ if (!Object.keys(data).every((key) => expected.includes(key)))
358
+ return false;
359
+ return truncated === expected.some((key) => !has(data, key));
360
+ };
361
+ const isValidScalarProviderRaw = (raw, request, verdict) => {
362
+ if (raw === undefined)
363
+ return true;
364
+ if (!isRecord(raw) || request.raw?.mode !== 'allowlisted')
365
+ return false;
366
+ if (!hasOnlyKeys(raw, ['data', 'bytes', 'truncated']) || !isRecord(raw.data))
367
+ return false;
368
+ if (typeof raw.bytes !== 'number' || !Number.isSafeInteger(raw.bytes) || raw.bytes < 0)
369
+ return false;
370
+ if (typeof raw.truncated !== 'boolean')
371
+ return false;
372
+ const actual = new TextEncoder().encode(JSON.stringify(raw.data)).byteLength;
373
+ if (raw.bytes !== actual || raw.bytes > Math.min(16384, request.raw.maxBytes ?? 16384))
374
+ return false;
375
+ return scalarRawAgreesWithTyped(raw.data, verdict, raw.truncated);
376
+ };
377
+ const VERDICT_KEYS = ['criterionKind', 'resolvedCriterionKind', 'score', 'decision', 'label',
378
+ 'rationale', 'confidence', 'distribution', 'providerRaw'];
379
+ /** scalar-label-v1 (§3.4): descriptor-declared scalar or label evidence only.
380
+ * Distribution is absent; confidence and rationale follow the declared
381
+ * semantics. P19: a substituted scale is rejected even when the decision is
382
+ * self-consistent. P20: undeclared confidence is rejected. */
383
+ function isValidScalarVerdict(v, request, effective, sem) {
384
+ if (!isRecord(v))
385
+ return false;
386
+ if (v.distribution !== null)
387
+ return false;
388
+ if (!confidenceMatchesSemantics(v.confidence, sem))
389
+ return false;
390
+ const criterion = effective.criterion;
391
+ const isProviderBool = isRecord(v.decision)
392
+ && v.decision.origin === 'provider';
393
+ if (criterion.kind === 'binary') {
394
+ if (v.label !== null || !scoreMatchesSemantics(v.score, sem))
395
+ return false;
396
+ if (!isProviderBool && v.score === null)
397
+ return false;
398
+ const score = v.score === null ? null : parseDecisionScore(v.score);
399
+ if (v.score !== null && (score === null || score.scale.kind !== 'bounded' || score.scale.direction !== 'unordered'))
400
+ return false;
401
+ return isValidWireDecision(v.decision, request, { score, label: null }, sem);
402
+ }
403
+ if (criterion.kind === 'choice') {
404
+ if (v.score !== null)
405
+ return false;
406
+ if (!isProviderBool && v.label === null)
407
+ return false;
408
+ if (v.label !== null) {
409
+ if (!isRecord(v.label) || !hasOnlyKeys(v.label, ['value', 'origin']) || v.label.origin !== 'provider')
410
+ return false;
411
+ const labelValue = v.label.value;
412
+ if (typeof labelValue !== 'string' || !criterion.options.some((option) => option.id === labelValue))
413
+ return false;
414
+ const label = { value: labelValue, origin: 'provider' };
415
+ return isValidWireDecision(v.decision, request, { score: null, label }, sem);
416
+ }
417
+ return isValidWireDecision(v.decision, request, { score: null, label: null }, sem);
418
+ }
419
+ if (criterion.kind === 'graded') {
420
+ if (v.label !== null || !scoreMatchesSemantics(v.score, sem))
421
+ return false;
422
+ if (!isProviderBool && v.score === null)
423
+ return false;
424
+ const score = v.score === null ? null : parseDecisionScore(v.score);
425
+ if (v.score !== null && (score === null || score.scale.kind !== 'bounded' || score.scale.direction !== criterion.direction))
426
+ return false;
427
+ return isValidWireDecision(v.decision, request, { score, label: null }, sem);
428
+ }
429
+ return false;
430
+ }
431
+ export function isValidDecisionVerdict(v, request, validation) {
432
+ if (!isRecord(v))
433
+ return false;
434
+ if (!hasOnlyKeys(v, VERDICT_KEYS))
435
+ return false;
436
+ if (!validation)
437
+ return false;
438
+ if (v.criterionKind !== request.criterion.kind)
439
+ return false;
440
+ // V-D3/L2: direct criteria bind resolved kind to the transmitted kind.
441
+ // Registered criteria resolve only through a trusted context resolution and
442
+ // fail closed otherwise; each concurrent call binds its own resolution.
443
+ const resolution = request.criterion.kind === 'registered' ? validation.registeredResolution ?? null : null;
444
+ if (request.criterion.kind === 'registered' && resolution === null)
445
+ return false;
446
+ if (request.criterion.kind === 'registered' && v.resolvedCriterionKind !== resolution?.resolvedKind)
447
+ return false;
448
+ if (request.criterion.kind !== 'registered' && v.resolvedCriterionKind !== request.criterion.kind)
449
+ return false;
450
+ const effective = resolution ? { ...request, criterion: resolution.criterion } : request;
451
+ // Evidence semantics come only from the trusted context. A response never
452
+ // selects its own profile; missing context never causes profile inference.
453
+ const sem = validation.semantics;
454
+ const scalar = validation.validationProfile.profile === 'scalar-label-v1';
455
+ // P18/V-D1: a provider-origin boolean is forged unless the selected profile
456
+ // declares native-boolean support; then exactly {pass, origin} with no rule.
457
+ if (isRecord(v.decision) && v.decision.origin === 'provider') {
458
+ if (!sem.nativeDecision || (request.decision !== undefined && request.decision.kind !== 'provider')
459
+ || !hasOnlyKeys(v.decision, ['pass', 'origin']) || typeof v.decision.pass !== 'boolean')
460
+ return false;
461
+ }
462
+ if (!isValidRationale(v.rationale, sem))
463
+ return false;
464
+ if (scalar)
465
+ return isValidScalarVerdict(v, request, effective, sem)
466
+ && isValidScalarProviderRaw(v.providerRaw, request, { score: v.score, label: v.label, confidence: v.confidence });
467
+ if (!hasServiceVerdictShape(v.score, v.label, v.confidence, v.distribution))
468
+ return false;
469
+ if (effective.criterion.kind === 'binary') {
470
+ if (v.score === null || v.label !== null || v.distribution !== null || v.confidence !== null)
471
+ return false;
472
+ const score = parseDecisionScore(v.score);
473
+ if (!score || score.meaning !== 'probability-true' || score.scale.kind !== 'bounded'
474
+ || score.scale.min !== 0 || score.scale.max !== 1 || score.scale.direction !== 'unordered')
475
+ return false;
476
+ if (validation && !scoreMatchesSemantics(v.score, sem))
477
+ return false;
478
+ if (!isValidWireDecision(v.decision, request, { score, label: null }, sem))
479
+ return false;
480
+ }
481
+ else if (effective.criterion.kind === 'choice') {
482
+ if (v.score !== null || v.label === null || v.distribution === null || v.confidence === null)
483
+ return false;
484
+ const confidence = parseDecisionConfidence(v.confidence);
485
+ if (!confidence || confidence.meaning !== 'provider-confidence' || confidence.min !== 0 || confidence.max !== 1)
486
+ return false;
487
+ if (validation && !confidenceMatchesSemantics(v.confidence, sem))
488
+ return false;
489
+ if (!isValidChoiceEvidence(v.distribution, v.label, effective))
490
+ return false;
491
+ const label = isRecord(v.label) && typeof v.label.value === 'string'
492
+ ? { value: v.label.value, origin: 'provider' } : null;
493
+ if (!isValidWireDecision(v.decision, request, { score: null, label: label }, sem))
494
+ return false;
495
+ }
496
+ else if (effective.criterion.kind === 'graded') {
497
+ if (v.score === null || v.label !== null || v.distribution === null || v.confidence === null)
498
+ return false;
499
+ const score = parseDecisionScore(v.score);
500
+ const confidence = parseDecisionConfidence(v.confidence);
501
+ if (!score || !confidence)
502
+ return false;
503
+ if (score.meaning !== 'rubric-grade' || score.scale.kind !== 'bounded'
504
+ || score.scale.min !== 0 || score.scale.max !== effective.criterion.levels.length - 1
505
+ || score.scale.direction !== effective.criterion.direction)
506
+ return false;
507
+ if (confidence.meaning !== 'provider-confidence' || confidence.min !== 0 || confidence.max !== 1)
508
+ return false;
509
+ if (validation && (!scoreMatchesSemantics(v.score, sem) || !confidenceMatchesSemantics(v.confidence, sem)))
510
+ return false;
511
+ if (!isValidGradedEvidence(v.distribution, score, effective))
512
+ return false;
513
+ if (!isValidWireDecision(v.decision, request, { score, label: null }, sem))
514
+ return false;
515
+ }
516
+ else {
517
+ return false;
518
+ }
519
+ return isValidWireProviderRaw(v.providerRaw, request, { score: v.score, label: v.label,
520
+ confidence: v.confidence, distribution: v.distribution });
521
+ }
522
+ /** Wire errors never carry wording: rebuild from code, phase and receipt liability evidence. */
523
+ function rebuildWireError(error, receipt) {
524
+ if (!isRecord(error) || !hasOnlyKeys(error, ['code', 'message', 'phase', 'retryable', 'retryAfterMs'])
525
+ || !isDecisionErrorCode(error.code)
526
+ || !['preflight', 'admission', 'dispatch', 'parse', 'settlement'].includes(error.phase))
527
+ return null;
528
+ const unresolved = receipt.budget.state !== 'not-admitted' && receipt.budget.state !== 'released';
529
+ const nonRetryable = unresolved || error.retryable === false;
530
+ const rebuilt = createDecisionError(error.code, error.phase, { retryAfterMs: !nonRetryable && has(error, 'retryAfterMs') && typeof error.retryAfterMs === 'number'
531
+ ? error.retryAfterMs : undefined });
532
+ if (nonRetryable) {
533
+ rebuilt.retryable = false;
534
+ delete rebuilt.retryAfterMs;
535
+ }
536
+ return rebuilt;
537
+ }
538
+ const scoreMatchesSemantics = (score, sem) => {
539
+ const parsed = score === null ? null : parseDecisionScore(score);
540
+ if (parsed === null && score !== null)
541
+ return false;
542
+ if (sem.score === null)
543
+ return parsed === null;
544
+ if (parsed === null)
545
+ return false;
546
+ return parsed.meaning === sem.score.meaning && parsed.scale.kind === 'bounded'
547
+ && parsed.scale.min === sem.score.min && parsed.scale.max === sem.score.max;
548
+ };
549
+ const confidenceMatchesSemantics = (confidence, sem) => {
550
+ if (confidence === null)
551
+ return sem.confidence === null;
552
+ if (sem.confidence === null)
553
+ return false;
554
+ const parsed = parseDecisionConfidence(confidence);
555
+ return !!parsed && parsed.meaning === sem.confidence.meaning
556
+ && parsed.meaningRef === sem.confidence.meaningRef
557
+ && parsed.min === sem.confidence.min && parsed.max === sem.confidence.max;
558
+ };
559
+ const isValidRationale = (rationale, sem) => {
560
+ if (rationale === null)
561
+ return true;
562
+ if (sem.rationale === 'forbidden')
563
+ return false;
564
+ return isRecord(rationale) && hasOnlyKeys(rationale, ['text', 'origin'])
565
+ && isText(rationale.text) && rationale.origin === 'provider';
566
+ };
567
+ /** Remote decision client calling an authenticated mesh endpoint without custody of provider keys. */
568
+ export function createRemoteDecisionClient(options) {
569
+ const fetchFn = options.fetch ?? globalThis.fetch;
570
+ const cap = options.maxResponseBytes ?? DECISION_MAX_JSON_BYTES;
571
+ const endpoint = resolveEndpoint(options.baseUrl);
572
+ const generate = options.generateId ?? (() => globalThis.crypto.randomUUID());
573
+ return {
574
+ async decide(request, callOptions) {
575
+ let correlationId = 'unknown';
576
+ try {
577
+ const raw = request.correlationId !== undefined
578
+ ? resolveDecisionCorrelationId(request.correlationId, generate)
579
+ : resolveDecisionCorrelationId(generate(), () => globalThis.crypto.randomUUID());
580
+ if (!raw) {
581
+ return { kind: 'connection-failure', ok: false, error: connectionError('MESH_UNAVAILABLE', 'preflight'),
582
+ receipt: null, usage: unreportedDecisionUsage('not-dispatched'), callStatus: 'not-sent', correlationId: 'unknown' };
583
+ }
584
+ correlationId = raw;
585
+ }
586
+ catch {
587
+ return { kind: 'connection-failure', ok: false, error: connectionError('MESH_UNAVAILABLE', 'preflight'),
588
+ receipt: null, usage: unreportedDecisionUsage('not-dispatched'), callStatus: 'not-sent', correlationId: 'unknown' };
589
+ }
590
+ const serverTimeout = request.policy?.timeoutMs ?? 10_000;
591
+ const serverWithMargin = serverTimeout + 1_000;
592
+ const effectiveTimeout = options.timeoutMs !== undefined ? Math.min(options.timeoutMs, serverWithMargin) : serverWithMargin;
593
+ const timeoutMs = Math.max(1, Math.min(61_000, effectiveTimeout));
594
+ const controller = new AbortController();
595
+ let timedOut = false;
596
+ let reader;
597
+ const onAbort = () => { void reader?.cancel().catch(() => { }); controller.abort(); };
598
+ const timer = setTimeout(() => { timedOut = true; void reader?.cancel().catch(() => { }); controller.abort(); }, timeoutMs);
599
+ callOptions?.signal?.addEventListener('abort', onAbort, { once: true });
600
+ const preflightFail = (code) => {
601
+ clearTimeout(timer);
602
+ callOptions?.signal?.removeEventListener('abort', onAbort);
603
+ return {
604
+ kind: 'connection-failure', ok: false, error: connectionError(code, 'preflight'),
605
+ receipt: null, usage: unreportedDecisionUsage('not-dispatched'), callStatus: 'not-sent', correlationId,
606
+ };
607
+ };
608
+ if (callOptions?.signal?.aborted)
609
+ return preflightFail('CANCELLED');
610
+ // Detached 256-KiB snapshot before any async resolution: later caller
611
+ // mutation, NaN/undefined smuggling, depth and size abuse never egress.
612
+ const outgoing = parseDecisionRequest({ ...request, correlationId });
613
+ if (!outgoing.ok)
614
+ return preflightFail('MESH_UNAVAILABLE');
615
+ freezeDecisionData(outgoing.request);
616
+ const racePreflight = async (promiseOrValue) => {
617
+ if (controller.signal.aborted)
618
+ throw new Error('preflight-interrupted');
619
+ let cleanup;
620
+ try {
621
+ return await Promise.race([
622
+ promiseOrValue,
623
+ new Promise((_, reject) => {
624
+ const abort = () => reject(new Error('preflight-interrupted'));
625
+ controller.signal.addEventListener('abort', abort, { once: true });
626
+ cleanup = () => controller.signal.removeEventListener('abort', abort);
627
+ }),
628
+ ]);
629
+ }
630
+ finally {
631
+ cleanup?.();
632
+ }
633
+ };
634
+ // Trusted context (§3.3): resolve revision-specific facts on the frozen
635
+ // transmitted snapshot, bounded by the call deadline. Any fault refuses
636
+ // client-local with zero fetches and zero header/credential resolution.
637
+ if (typeof options.validationContext !== 'function')
638
+ return preflightFail('MESH_UNAVAILABLE');
639
+ let validation = null;
640
+ try {
641
+ const resolved = await racePreflight(options.validationContext(outgoing.request));
642
+ validation = parseDecisionValidationContext(resolved, outgoing.request, Date.now());
643
+ }
644
+ catch {
645
+ return preflightFail(timedOut ? 'TIMEOUT'
646
+ : controller.signal.aborted || callOptions?.signal?.aborted ? 'CANCELLED' : 'MESH_UNAVAILABLE');
647
+ }
648
+ if (!validation)
649
+ return preflightFail('MESH_UNAVAILABLE');
650
+ const headers = { 'content-type': 'application/json', accept: 'application/json' };
651
+ try {
652
+ if (options.authToken) {
653
+ const token = await racePreflight(typeof options.authToken === 'function' ? options.authToken() : options.authToken);
654
+ if (token)
655
+ headers.authorization = `Bearer ${token}`;
656
+ }
657
+ if (options.headers) {
658
+ const extra = await racePreflight(typeof options.headers === 'function' ? options.headers() : options.headers);
659
+ if (extra)
660
+ Object.assign(headers, extra);
661
+ }
662
+ }
663
+ catch {
664
+ return preflightFail(controller.signal.aborted || callOptions?.signal?.aborted ? (timedOut ? 'TIMEOUT' : 'CANCELLED') : 'MESH_UNAVAILABLE');
665
+ }
666
+ if (controller.signal.aborted || callOptions?.signal?.aborted)
667
+ return preflightFail(timedOut ? 'TIMEOUT' : 'CANCELLED');
668
+ const fail = (code) => ({
669
+ kind: 'connection-failure', ok: false, error: connectionError(code, 'dispatch'),
670
+ receipt: null, usage: unreportedDecisionUsage('unknown'), callStatus: 'unknown', correlationId,
671
+ });
672
+ // Race dispatch against deadline/cancellation: an injected fetch that
673
+ // ignores abort still resolves to TIMEOUT/CANCELLED, and a late
674
+ // settlement is discarded without retrying or surfacing rejections.
675
+ let response;
676
+ let interruptDispatch = () => { };
677
+ try {
678
+ const pending = fetchFn(endpoint, {
679
+ method: 'POST', redirect: 'manual', headers, body: JSON.stringify(outgoing.request), signal: controller.signal,
680
+ });
681
+ let lost = false;
682
+ const guard = new Promise((_, reject) => {
683
+ interruptDispatch = () => { lost = true; reject(new Error('dispatch-interrupted')); };
684
+ if (controller.signal.aborted || callOptions?.signal?.aborted)
685
+ interruptDispatch();
686
+ });
687
+ if (!controller.signal.aborted && !callOptions?.signal?.aborted) {
688
+ controller.signal.addEventListener('abort', interruptDispatch, { once: true });
689
+ }
690
+ void Promise.resolve(pending).then((late) => { if (lost)
691
+ void late.body?.cancel().catch(() => { }); }, () => { });
692
+ try {
693
+ response = await Promise.race([pending, guard]);
694
+ }
695
+ finally {
696
+ controller.signal.removeEventListener('abort', interruptDispatch);
697
+ }
698
+ }
699
+ catch {
700
+ clearTimeout(timer);
701
+ callOptions?.signal?.removeEventListener('abort', onAbort);
702
+ return fail(timedOut ? 'TIMEOUT' : callOptions?.signal?.aborted ? 'CANCELLED' : 'TRANSPORT_FAILED');
703
+ }
704
+ if (response.redirected || (response.status >= 300 && response.status < 400)) {
705
+ clearTimeout(timer);
706
+ callOptions?.signal?.removeEventListener('abort', onAbort);
707
+ void response.body?.cancel().catch(() => { });
708
+ return fail('MESH_UNAVAILABLE');
709
+ }
710
+ const declared = response.headers.get('content-length');
711
+ if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > cap)) {
712
+ clearTimeout(timer);
713
+ callOptions?.signal?.removeEventListener('abort', onAbort);
714
+ void response.body?.cancel().catch(() => { });
715
+ return fail('MESH_UNAVAILABLE');
716
+ }
717
+ const buffer = new Uint8Array(Math.min(cap, 65_536));
718
+ let size = 0;
719
+ let chunks = buffer;
720
+ try {
721
+ reader = response.body?.getReader();
722
+ if (reader) {
723
+ for (;;) {
724
+ const chunk = await racePreflight(reader.read());
725
+ if (timedOut || controller.signal.aborted || callOptions?.signal?.aborted) {
726
+ return fail(timedOut ? 'TIMEOUT' : callOptions?.signal?.aborted ? 'CANCELLED' : 'TRANSPORT_FAILED');
727
+ }
728
+ if (chunk.done)
729
+ break;
730
+ if (size + chunk.value.byteLength > cap) {
731
+ void reader.cancel().catch(() => { });
732
+ return fail('MESH_UNAVAILABLE');
733
+ }
734
+ if (size + chunk.value.byteLength > chunks.byteLength) {
735
+ const next = new Uint8Array(Math.min(cap, Math.max(chunks.byteLength * 2, size + chunk.value.byteLength)));
736
+ next.set(chunks.subarray(0, size));
737
+ chunks = next;
738
+ }
739
+ chunks.set(chunk.value, size);
740
+ size += chunk.value.byteLength;
741
+ }
742
+ }
743
+ }
744
+ catch {
745
+ return fail(timedOut ? 'TIMEOUT' : callOptions?.signal?.aborted ? 'CANCELLED' : 'TRANSPORT_FAILED');
746
+ }
747
+ finally {
748
+ try {
749
+ reader?.releaseLock();
750
+ }
751
+ catch { }
752
+ clearTimeout(timer);
753
+ callOptions?.signal?.removeEventListener('abort', onAbort);
754
+ }
755
+ if (timedOut || controller.signal.aborted || callOptions?.signal?.aborted) {
756
+ return fail(timedOut ? 'TIMEOUT' : callOptions?.signal?.aborted ? 'CANCELLED' : 'TRANSPORT_FAILED');
757
+ }
758
+ let parsed;
759
+ try {
760
+ parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(chunks.subarray(0, size)));
761
+ }
762
+ catch {
763
+ return fail('MESH_UNAVAILABLE');
764
+ }
765
+ const snapshot = snapshotBoundedJson(parsed, cap);
766
+ if (!snapshot.ok || !isRecord(snapshot.value))
767
+ return fail('MESH_UNAVAILABLE');
768
+ const val = snapshot.value;
769
+ // V-K1/V-K2: closed envelopes and field-by-field reconstruction. Undeclared
770
+ // content can never survive, even when raw permission is allowlisted.
771
+ const rebuildVerdict = (verdict) => {
772
+ const rebuilt = {
773
+ criterionKind: verdict.criterionKind,
774
+ resolvedCriterionKind: verdict.resolvedCriterionKind,
775
+ score: structuredClone(verdict.score),
776
+ decision: structuredClone(verdict.decision),
777
+ label: structuredClone(verdict.label),
778
+ rationale: structuredClone(verdict.rationale),
779
+ confidence: structuredClone(verdict.confidence),
780
+ distribution: structuredClone(verdict.distribution),
781
+ };
782
+ if (Object.hasOwn(verdict, 'providerRaw'))
783
+ rebuilt.providerRaw = structuredClone(verdict.providerRaw);
784
+ return rebuilt;
785
+ };
786
+ const rebuildReceipt = (receipt) => {
787
+ const rebuilt = {
788
+ schemaVersion: receipt.schemaVersion,
789
+ callId: receipt.callId,
790
+ correlationId: receipt.correlationId,
791
+ evaluator: receipt.evaluator === null ? null : { providerId: receipt.evaluator.providerId,
792
+ evaluatorId: receipt.evaluator.evaluatorId, revision: receipt.evaluator.revision },
793
+ servedEvaluator: receipt.servedEvaluator === null ? null : { providerId: receipt.servedEvaluator.providerId,
794
+ evaluatorId: receipt.servedEvaluator.evaluatorId, revision: receipt.servedEvaluator.revision },
795
+ servedModelId: receipt.servedModelId,
796
+ startedAt: receipt.startedAt,
797
+ finishedAt: receipt.finishedAt,
798
+ latencyMs: receipt.latencyMs,
799
+ dispatch: receipt.dispatch,
800
+ attempts: receipt.attempts,
801
+ usage: structuredClone(receipt.usage),
802
+ cost: structuredClone(receipt.cost),
803
+ retention: structuredClone(receipt.retention),
804
+ budget: structuredClone(receipt.budget),
805
+ outcome: receipt.outcome,
806
+ };
807
+ if (Object.hasOwn(receipt, 'providerRequestId'))
808
+ rebuilt.providerRequestId = receipt.providerRequestId;
809
+ if (Object.hasOwn(receipt, 'providerLatencyMs'))
810
+ rebuilt.providerLatencyMs = receipt.providerLatencyMs;
811
+ if (Object.hasOwn(receipt, 'errorCode'))
812
+ rebuilt.errorCode = receipt.errorCode;
813
+ return rebuilt;
814
+ };
815
+ // Concordance and permissions bind the transmitted snapshot: the live
816
+ // request may have mutated during auth resolution or transport.
817
+ if (isRecord(val) && hasOnlyKeys(val, ['kind', 'ok', 'verdict', 'receipt'])
818
+ && val.kind === 'verdict' && val.ok === true
819
+ && isValidDecisionReceipt(val.receipt, outgoing.request, correlationId, validation) && val.receipt.outcome === 'verdict'
820
+ && isValidDecisionVerdict(val.verdict, outgoing.request, validation)) {
821
+ return { kind: 'verdict', ok: true, verdict: rebuildVerdict(val.verdict), receipt: rebuildReceipt(val.receipt) };
822
+ }
823
+ if (isRecord(val) && hasOnlyKeys(val, ['kind', 'ok', 'error', 'receipt'])
824
+ && val.kind === 'error' && val.ok === false
825
+ && isValidDecisionReceipt(val.receipt, outgoing.request, correlationId, validation) && val.receipt.outcome === 'error') {
826
+ const error = rebuildWireError(val.error, val.receipt);
827
+ if (error && val.receipt.errorCode === error.code) {
828
+ return { kind: 'error', ok: false, error, receipt: rebuildReceipt(val.receipt) };
829
+ }
830
+ return fail('MESH_UNAVAILABLE');
831
+ }
832
+ return fail('MESH_UNAVAILABLE');
833
+ },
834
+ };
835
+ }
836
+ //# sourceMappingURL=client.js.map