@coderifts/agent-guard 17.2.0 → 17.3.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.
@@ -0,0 +1,507 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * verifiedExecutionBinding — the ONE predicate that answers "authorized and committed".
5
+ *
6
+ * ── WHY ONE (1459) ──────────────────────────────────────────────────────────────────────────
7
+ *
8
+ * The same question was being answered in several places with different rules, and the answers
9
+ * disagreed. Measured on the public agent-guard 17.2.0, with a forged-signature grant and a REAL
10
+ * attestation from a trusted executor bound to that grant's jti and scope:
11
+ *
12
+ * ENFORCING_STRICT authorized_and_committed = false
13
+ * ENFORCING_ATOMIC authorized_and_committed = true
14
+ *
15
+ * Two profiles of one product, looking at one set of bytes, reaching opposite conclusions — because
16
+ * the Atomic formula was `receipt_verified && committed && class === 'executor_attested'` and never
17
+ * asked whether the GRANT was signed by anyone. A predicate that is recomputed is a predicate that
18
+ * drifts; this file exists so callers QUOTE the answer instead.
19
+ *
20
+ * ── FOUR AUTHORITIES, AND ONLY THEIR INTERSECTION ───────────────────────────────────────────
21
+ *
22
+ * Each is independently true or not, and each answers a different question. None of them implies
23
+ * another, which is exactly why the intersection — not any one of them — is the success condition:
24
+ *
25
+ * issuer_grant did CodeRifts authorize THIS change? (a signature under a pinned key)
26
+ * executor_attestation did the executor commit THAT grant? (a signature over the grant's ids)
27
+ * one_run_root are these bytes from ONE run? (cr.evidence.root.v1 digests)
28
+ * provider_witness did the provider record it? (a readback — UNSIGNED by nature)
29
+ *
30
+ * ── SHORTFALLS ARE NAMED, NOT FOLDED INTO `false` ───────────────────────────────────────────
31
+ *
32
+ * "Not authorized" and "authorized but the commit is unproven" are different facts with different
33
+ * remedies, and a boolean loses that. Every shortfall gets a state a human can act on, and the
34
+ * states are ORDERED by severity so the returned one is the most serious thing that is wrong.
35
+ *
36
+ * WHAT THIS DOES NOT DO. It reaches no network and holds no keys: every input is supplied by the
37
+ * caller, already-verified or verifiable, and the four authorities are recomputed here from those
38
+ * inputs rather than taken as claims. It also cannot see a lying executor — the root closes
39
+ * third-party splicing, not an executor misreporting its own run.
40
+ */
41
+
42
+ const crypto = require('node:crypto');
43
+
44
+ const { verifyExecutionGrant } = require('./verify-grant.js');
45
+ const { verifyEvidenceRootBinding } = require('./verify-evidence.js');
46
+ const { verifyReceipt } = require('./verify.js');
47
+
48
+ const BINDING_V = 'cr.verified-execution-binding.v1';
49
+
50
+ /**
51
+ * Ordered most-severe first. The returned `state` is the FIRST unmet one, so a caller that renders
52
+ * a single line renders the thing that most needs fixing.
53
+ */
54
+ const STATE = Object.freeze({
55
+ UNAUTHORIZED: 'UNAUTHORIZED',
56
+ COMMIT_UNPROVEN: 'COMMIT_UNPROVEN',
57
+ ONE_RUN_UNPROVEN: 'ONE_RUN_UNPROVEN',
58
+ RECORDED_UNWITNESSED: 'RECORDED_UNWITNESSED',
59
+ NOT_COMMITTED: 'NOT_COMMITTED',
60
+ AUTHORIZED_AND_COMMITTED: 'AUTHORIZED_AND_COMMITTED',
61
+ /**
62
+ * A CUSTOM aggregation was satisfied — and it is NOT the global success token.
63
+ *
64
+ * ── THE HOLE THIS CLOSES, REPRODUCED BEFORE IT WAS WRITTEN ──────────────────────────────
65
+ *
66
+ * valid grant + committed + NO attestation + NO root, required: ['issuer_grant']
67
+ * -> AUTHORIZED_AND_COMMITTED, authorized_and_committed: true
68
+ *
69
+ * Nothing was forged. The caller simply asked for less, and the function handed back the token
70
+ * every consumer reads as "authorized and committed". `required[]` was a strictness dial that
71
+ * also selected the NAME of success, so narrowing the dial upgraded the verdict — the strongest
72
+ * word in the vocabulary, reachable by asking for the least.
73
+ *
74
+ * A caller may still choose its own authorities. What it may no longer do is call the result by
75
+ * the global name. `AUTHORIZED_AND_COMMITTED` is now reachable ONLY through a closed, versioned
76
+ * profile whose authority set the caller does not get to shorten.
77
+ */
78
+ CUSTOM_REQUIREMENTS_SATISFIED: 'CUSTOM_REQUIREMENTS_SATISFIED',
79
+ });
80
+
81
+ /** What each authority is allowed to be missing for, so a caller can choose its own strictness. */
82
+ const AUTHORITY = Object.freeze({
83
+ ISSUER_GRANT: 'issuer_grant',
84
+ EXECUTOR_ATTESTATION: 'executor_attestation',
85
+ ONE_RUN_ROOT: 'one_run_root',
86
+ PROVIDER_WITNESS: 'provider_witness',
87
+ });
88
+
89
+ /**
90
+ * CLOSED, VERSIONED ASSURANCE PROFILES.
91
+ *
92
+ * A profile fixes its authority set. The caller names the profile; it does not get to edit what
93
+ * the profile means, and the version in the name is what lets the set change later without
94
+ * silently changing what an older caller was promised.
95
+ *
96
+ * `TRUSTED_EXECUTOR_INTEGRITY_V1` is the honest ceiling of a single-machine chain: the decision
97
+ * receipt verified, the issuer's grant verified, the executor's attestation verified, and one
98
+ * signed root binding the set to one run. It says nothing about a third party, and the name is
99
+ * chosen so it cannot be read as one — `EXTERNALLY_WITNESSED_EXECUTION_V1` is a different profile
100
+ * and no producer can satisfy it today (no signed-witness format exists), which is why it is not
101
+ * declared here as an empty promise.
102
+ */
103
+ const PROFILE = Object.freeze({
104
+ TRUSTED_EXECUTOR_INTEGRITY_V1: Object.freeze({
105
+ name: 'TRUSTED_EXECUTOR_INTEGRITY_V1',
106
+ authorities: Object.freeze([
107
+ AUTHORITY.ISSUER_GRANT,
108
+ AUTHORITY.EXECUTOR_ATTESTATION,
109
+ AUTHORITY.ONE_RUN_ROOT,
110
+ ]),
111
+ // The receipt is mandatory in every profile and is not listed as an authority because it is
112
+ // not optional anywhere — it gates the whole predicate above the state machine.
113
+ proof_scope: 'TRUSTED_EXECUTOR',
114
+ externally_witnessed: false,
115
+ }),
116
+ });
117
+ const PROFILE_NAMES = Object.freeze(Object.keys(PROFILE));
118
+
119
+ const sha256pref = (v) =>
120
+ `sha256:${crypto.createHash('sha256').update(String(v), 'utf8').digest('hex')}`;
121
+
122
+ const b64json = (seg) => {
123
+ try { return JSON.parse(Buffer.from(seg, 'base64url').toString('utf8')); } catch (_) { return null; }
124
+ };
125
+
126
+ /** The four fields an attestation binds, read from its own signed preimage. */
127
+ function attestationClaims(token) {
128
+ if (typeof token !== 'string') return null;
129
+ const seg = token.split('|');
130
+ if (seg.length !== 4 || !seg[2]) return null;
131
+ const body = b64json(seg[2]);
132
+ return body && typeof body === 'object' ? body : null;
133
+ }
134
+
135
+ /** The grant's identity, in one vocabulary across v1 and v2. */
136
+ function grantClaims(token) {
137
+ if (typeof token !== 'string') return null;
138
+ const body = b64json(String(token).split('.')[0]);
139
+ if (!body || typeof body !== 'object') return null;
140
+ return {
141
+ v: body.v,
142
+ jti: body.grant_id || body.jti || null,
143
+ scope_hash: body.after_payload_hash || body.scope_hash || null,
144
+ receipt_hash: body.receipt_hash || body.receipt_digest || null,
145
+ operation: body.operation || null,
146
+ // The rest of what a grant SAYS, so the attestation can be checked against all of it rather
147
+ // than against the two fields that happened to be compared first.
148
+ target: body.target_uri || body.target_id || null,
149
+ tenant_id: body.tenant_id || null,
150
+ executor_id: body.executor_id || null,
151
+ adapter_id: body.adapter_id || null,
152
+ audience: body.audience_hash || body.audience || null,
153
+ policy_hash: body.policy_hash || null,
154
+ state_token: body.expected_state_token || body.state_nonce || null,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * @param {object} o
160
+ * @param {{verified: boolean, token?: string}} o.receipt
161
+ * @param {{token: string, publicKey?, keyring?, intended?: object, now?: number}} o.grant
162
+ * THE EXACT BYTES THE ISSUER SIGNED. Not a grant read back out of a tool result: a caller
163
+ * that lets the executed tool hand back its own authorization has already lost.
164
+ * @param {{token: string, registry?: object, verify?: Function}} [o.attestation]
165
+ * @param {{artifact: object, executorKey}} [o.evidenceRoot]
166
+ * @param {{signed: boolean}} [o.providerReadback]
167
+ * @param {boolean} o.committed
168
+ * @param {string} [o.profile] a CLOSED profile name (see PROFILE). Its authority set cannot be
169
+ * edited by the caller, and only a profile can reach AUTHORIZED_AND_COMMITTED.
170
+ * @param {string[]} [o.required] a CUSTOM authority set. Legal, and it can never produce the
171
+ * global success token — a satisfied custom set reads CUSTOM_REQUIREMENTS_SATISFIED.
172
+ */
173
+ function verifiedExecutionBinding(o = {}) {
174
+ // ── WHICH SET, AND WHO CHOSE IT ─────────────────────────────────────────────────────────
175
+ //
176
+ // A profile and a custom set are different KINDS of question, so they are answered separately
177
+ // and never merged. Passing both is refused rather than resolved by precedence: a caller that
178
+ // names a profile and then also lists authorities is asking two things at once, and picking one
179
+ // silently is how a caller ends up believing it got the other.
180
+ const profileName = typeof o.profile === 'string' && o.profile ? o.profile : null;
181
+ const customRequired = Array.isArray(o.required) && o.required.length ? o.required : null;
182
+ if (profileName && customRequired) {
183
+ return {
184
+ v: BINDING_V,
185
+ authorized_and_committed: false,
186
+ requirements_satisfied: false,
187
+ profile: null,
188
+ proof_scope: null,
189
+ externally_witnessed: false,
190
+ state: STATE.UNAUTHORIZED,
191
+ authorities: {},
192
+ shortfalls: ['profile: both `profile` and `required` were supplied — a closed profile\'s '
193
+ + 'authority set is not editable, so this asks two different questions at once'],
194
+ does_not_prove: [],
195
+ };
196
+ }
197
+ if (profileName && !Object.prototype.hasOwnProperty.call(PROFILE, profileName)) {
198
+ // FAIL CLOSED on an unknown profile. Falling back to a default would let a typo — or a caller
199
+ // written against a future version — silently receive a weaker check than it named.
200
+ return {
201
+ v: BINDING_V,
202
+ authorized_and_committed: false,
203
+ requirements_satisfied: false,
204
+ profile: null,
205
+ proof_scope: null,
206
+ externally_witnessed: false,
207
+ state: STATE.UNAUTHORIZED,
208
+ authorities: {},
209
+ shortfalls: [`profile: unknown assurance profile "${profileName}"; known: ${PROFILE_NAMES.join(', ')}`],
210
+ does_not_prove: [],
211
+ };
212
+ }
213
+ const profile = profileName ? PROFILE[profileName] : null;
214
+ const required = new Set(profile
215
+ ? profile.authorities
216
+ : (customRequired || [AUTHORITY.ISSUER_GRANT, AUTHORITY.EXECUTOR_ATTESTATION]));
217
+ const shortfalls = [];
218
+ const authorities = {};
219
+ const note = (name, ok, detail) => {
220
+ authorities[name] = { ok, required: required.has(name), detail };
221
+ if (!ok && required.has(name)) shortfalls.push(`${name}: ${detail}`);
222
+ return ok;
223
+ };
224
+
225
+ // ── 1. THE ISSUER GRANT ────────────────────────────────────────────────────────────────
226
+ const g = o.grant || {};
227
+ let grantOk = false;
228
+ let gClaims = null;
229
+ if (typeof g.token !== 'string' || g.token.length === 0) {
230
+ note(AUTHORITY.ISSUER_GRANT, false, 'no execution grant was supplied');
231
+ } else if (!g.publicKey && !g.keyring) {
232
+ // FAIL-CLOSED, and named as its own thing: "we had no key" is not "the signature was bad".
233
+ note(AUTHORITY.ISSUER_GRANT, false,
234
+ 'no pinned issuer keyring was supplied, so the grant could not be authenticated');
235
+ } else {
236
+ const r = verifyExecutionGrant(g.token, {
237
+ ctx: { publicKey: g.publicKey, keyring: g.keyring, expectedKid: g.expectedKid ?? null },
238
+ ...(g.intended ? { intended: g.intended } : {}),
239
+ ...(Number.isFinite(g.now) ? { now: g.now } : {}),
240
+ });
241
+ gClaims = grantClaims(g.token);
242
+ grantOk = note(AUTHORITY.ISSUER_GRANT, r.valid === true,
243
+ `${r.status}${r.reason ? `/${r.reason}` : ''}`);
244
+ }
245
+
246
+ // ── 2. THE EXECUTOR ATTESTATION ────────────────────────────────────────────────────────
247
+ const a = o.attestation || {};
248
+ let attOk = false;
249
+ if (typeof a.token !== 'string' || a.token.length === 0) {
250
+ note(AUTHORITY.EXECUTOR_ATTESTATION, false, 'no executor attestation was supplied');
251
+ } else if (typeof a.verify !== 'function') {
252
+ note(AUTHORITY.EXECUTOR_ATTESTATION, false,
253
+ 'no attestation verifier was supplied, so the commit could not be checked');
254
+ } else {
255
+ const r = a.verify(a.token, { registry: a.registry });
256
+ const sigOk = r && r.valid === true;
257
+ // BOUND TO THE VERIFIED GRANT'S OWN IDS — not to ids the caller passed alongside. This is the
258
+ // join that makes the two signatures one statement instead of two unrelated true things.
259
+ const c = attestationClaims(a.token);
260
+ // ── THE ATTESTATION MUST BIND THE SAME EXECUTION, NOT MERELY THE SAME NAMES (1464) ────
261
+ //
262
+ // REPRODUCED before this was written. A correctly-signed grant bound to receipt R1, and a
263
+ // correctly-signed attestation from a trusted executor bound to receipt R2, sharing a
264
+ // grant_jti and a scope_hash — R1 != R2 — read AUTHORIZED_AND_COMMITTED. Both signatures are
265
+ // real; the two documents describe DIFFERENT executions and the join could not tell.
266
+ //
267
+ // `jti` and `scope_hash` are the two fields an attacker controls most cheaply: they are copied
268
+ // FROM the grant into the attestation by whoever assembles the pair. Comparing only those is
269
+ // comparing a value with its own copy. What binds is the receipt each side was issued against,
270
+ // and — the strongest available — the sha256 of the exact grant token bytes.
271
+ const attReceipt = c ? String(c.receipt_digest || '') : '';
272
+ const grantReceipt = gClaims ? String(gClaims.receipt_hash || '') : '';
273
+ const mismatch = (() => {
274
+ if (!sigOk) return `attestation ${r ? r.status : 'unverifiable'}`;
275
+ if (!gClaims) return 'there is no verified grant for the attestation to bind';
276
+ if (String(c.grant_jti || '') !== String(gClaims.jti || '')) {
277
+ return 'the attestation binds a different grant id than the verified grant';
278
+ }
279
+ if (String(c.scope_hash || '') !== String(gClaims.scope_hash || '')) {
280
+ return 'the attestation binds a different scope than the verified grant';
281
+ }
282
+ // THE CROSS-RECEIPT CHECK. Empty on either side is a mismatch: an attestation that names no
283
+ // receipt cannot be shown to be about this authorization, and "unstated" must not read as
284
+ // "the same".
285
+ if (!attReceipt || !grantReceipt || attReceipt !== grantReceipt) {
286
+ return `the grant was issued against receipt ${grantReceipt || '(none)'} and the `
287
+ + `attestation commits receipt ${attReceipt || '(none)'} — two different executions`;
288
+ }
289
+ // ── WHAT cr.exec.attest.v1 CAN AND CANNOT BE ASKED ──────────────────────────────
290
+ //
291
+ // MEASURED, and it bounds this check rather than the check bounding the format: the
292
+ // attestation body is a CLOSED set — executor_kid, grant_jti, receipt_digest, scope_hash,
293
+ // committed_at, state_nonce, result_digest, meta. Any other key is refused
294
+ // ATTEST_MALFORMED / unknown_field by its own verifier.
295
+ //
296
+ // So target, operation, tenant, executor, adapter, audience and policy CANNOT be
297
+ // cross-checked here: the attestation never states them, and a comparison against a field
298
+ // that cannot exist is not a check — it is a line that always passes. They are named in
299
+ // `does_not_prove` instead, which is the honest place for a binding the format cannot carry.
300
+ //
301
+ // The same is true of the exact grant-token digest: there is no field for it. Binding the
302
+ // grant BYTES rather than its claims would be the tightest join available and it needs a
303
+ // format change (a `grant_token_digest` slot in cr.exec.attest.v2), not a check here.
304
+ //
305
+ // What the format DOES let us bind is the state nonce, and it is bound below.
306
+ if (gClaims.state_token != null && c.state_nonce != null
307
+ && String(gClaims.state_token) !== String(c.state_nonce)) {
308
+ return 'the grant and the attestation disagree about the state nonce';
309
+ }
310
+ return null;
311
+ })();
312
+ attOk = note(AUTHORITY.EXECUTOR_ATTESTATION, mismatch === null, mismatch || 'bound');
313
+ }
314
+
315
+ // ── 3. ONE RUN ─────────────────────────────────────────────────────────────────────────
316
+ const er = o.evidenceRoot || null;
317
+ if (!er || !er.artifact) {
318
+ note(AUTHORITY.ONE_RUN_ROOT, false,
319
+ 'no cr.evidence.root.v1 was supplied, so these bytes are not shown to be one run');
320
+ } else if (typeof verifyEvidenceRootBinding !== 'function') {
321
+ // ── A MISSING VERIFIER IS A REFUSAL, NEVER A CRASH ───────────────────────────────────
322
+ //
323
+ // MEASURED on a consumer: agent-guard vendors an OLDER `verify-evidence.js` that does not
324
+ // export `verifyEvidenceRootBinding` (true on its HEAD too), so this line threw a TypeError
325
+ // the moment anyone passed an `evidenceRoot`. Latent there — that guard holds no root — and
326
+ // the SHAPE is what matters: a crash is not an answer, and a caller cannot tell "the core
327
+ // blew up" from "the core is unavailable" from "the binding failed".
328
+ //
329
+ // Every OTHER dependency this file has is already used unconditionally, so this is the one
330
+ // place a mixed vendor pin can leave a hole. Refusing here is the fail-closed answer, and it
331
+ // names the cause rather than reporting a generic unbound root.
332
+ note(AUTHORITY.ONE_RUN_ROOT, false,
333
+ 'the evidence-root verifier is not available in this build (the vendored verify-evidence.js '
334
+ + 'does not export verifyEvidenceRootBinding) — the root was NOT checked, and an unchecked '
335
+ + 'root is refused rather than reported as unbound');
336
+ } else {
337
+ let r;
338
+ try {
339
+ r = verifyEvidenceRootBinding(er.artifact, { executorKey: er.executorKey, sidecars: er.sidecars });
340
+ } catch (err) {
341
+ r = { ok: false, failures: [`the evidence-root verifier threw: ${(err && err.message) || 'error'}`] };
342
+ }
343
+ note(AUTHORITY.ONE_RUN_ROOT, r.ok === true,
344
+ r.ok ? 'bound' : ((r.failures && r.failures[0]) || 'unbound'));
345
+ }
346
+
347
+ // ── 4. THE PROVIDER WITNESS ────────────────────────────────────────────────────────────
348
+ // A readback is an UNSIGNED document by nature. It is carried, not verified, and this authority
349
+ // is false unless a caller states it was witnessed some stronger way — never true by default.
350
+ // ── A CALLER BOOLEAN IS NOT EVIDENCE (1465) ────────────────────────────────────────────
351
+ //
352
+ // REPRODUCED before this was written:
353
+ //
354
+ // required: ['provider_witness'], receipt: {verified: true},
355
+ // providerReadback: {signed: true}, committed: true, NO grant, NO attestation, NO root
356
+ // → AUTHORIZED_AND_COMMITTED, shortfalls: []
357
+ //
358
+ // `signed: true` was a bare boolean the caller wrote, and this function aggregated it into a
359
+ // global success. Nothing was verified; a field named `signed` was believed because it was set.
360
+ //
361
+ // A witness now requires a VERIFIED witness envelope: bytes plus a verifier plus a trust anchor.
362
+ // No such format exists yet (phase D measured that the readback is unsigned by nature), so this
363
+ // authority cannot currently be satisfied at all — and saying that plainly is the honest answer.
364
+ // Asking for it yields RECORDED_UNWITNESSED, which is exactly what it means.
365
+ //
366
+ // NOT a breaking change for the five consumers: none of them requires `provider_witness` today
367
+ // (guard and contract-gate ask for issuer_grant + executor_attestation; prove and conformance for
368
+ // issuer_grant + one_run_root). It removes a way to LIE, not a way anyone works.
369
+ const pw = o.providerReadback || null;
370
+ const witnessVerified = !!(pw && pw.verified === true && pw.envelope && pw.verifier);
371
+ note(AUTHORITY.PROVIDER_WITNESS, witnessVerified,
372
+ pw
373
+ ? (pw.signed === true && !witnessVerified
374
+ ? 'the caller asserted `signed: true` and supplied no verifiable witness envelope — a '
375
+ + 'boolean is not evidence, and no signed-witness format exists yet'
376
+ : 'the provider readback is an unsigned document (carried, not verified)')
377
+ : 'no provider readback was supplied');
378
+
379
+ // ── THE INTERSECTION ───────────────────────────────────────────────────────────────────
380
+ const committed = o.committed === true;
381
+ // THE RECEIPT, and what this function can honestly say about it.
382
+ //
383
+ // `verified` is the CALLER's determination: this core is not given the receipt token or a
384
+ // keyring, so it cannot re-establish it. That is recorded rather than hidden — a reader of the
385
+ // result can see whether the receipt was verified HERE or asserted by whoever called.
386
+ //
387
+ // Left as-is deliberately: making a bare boolean insufficient would change the input shape of
388
+ // all five consumers at once, and that belongs with the closed-profile work (1465), not
389
+ // half-done in a round that would leave them broken. The gap is named, not narrowed in silence.
390
+ // ── THE DECISION RECEIPT, VERIFIED HERE WHEN IT CAN BE ─────────────────────────────────
391
+ //
392
+ // ── THE FOURTH CALLER-BOOLEAN THIS REPOSITORY HAS MET ──────────────────────────────────
393
+ //
394
+ // providerReadback: { signed: true } believed because it was set (closed)
395
+ // attestation: { present: true } believed because it was set (closed)
396
+ // call_hash present == "tool-call bound" (pre-empted)
397
+ // receipt: { verified: true } believed because it was set (this)
398
+ //
399
+ // `receipt.verified` is the CALLER's word. With no token and no keyring, nothing was checked and
400
+ // this function had no way to check it — and a caller that simply set the flag reached a
401
+ // satisfied verdict. `receipt_caller_asserted` recorded it, which is honest reporting and not a
402
+ // gate: a field nobody branches on does not stop anything.
403
+ //
404
+ // Now: when a token AND a key source are supplied, THIS function verifies the receipt. When they
405
+ // are not, the assertion is accepted only for a CUSTOM aggregation — where the caller owns its
406
+ // own question — and can never reach AUTHORIZED_AND_COMMITTED, which is the word every consumer
407
+ // reads as the answer.
408
+ const rc = o.receipt || {};
409
+ let receiptOk = false;
410
+ let receiptAsserted = false;
411
+ let receiptDetail = 'the decision receipt did not verify';
412
+ const keySource = rc.keyring || rc.publicKey;
413
+ if (typeof rc.token === 'string' && rc.token.length > 0 && keySource) {
414
+ let rv;
415
+ try {
416
+ rv = verifyReceipt(rc.token, {
417
+ ctx: { ...(rc.keyring ? { keyring: rc.keyring } : { publicKey: rc.publicKey }),
418
+ expectedKid: rc.expectedKid === undefined ? null : rc.expectedKid },
419
+ ...(Number.isFinite(rc.now) ? { now: rc.now } : {}),
420
+ });
421
+ } catch (err) {
422
+ rv = { valid: false, status: 'VERIFIER_ERROR', reason: (err && err.message) || 'error' };
423
+ }
424
+ receiptOk = rv.valid === true;
425
+ if (!receiptOk) receiptDetail = `the decision receipt did not verify (${rv.status}: ${rv.reason})`;
426
+ } else if (rc.verified === true) {
427
+ // Accepted, and MARKED. The state machine below refuses to hand this the global name.
428
+ receiptOk = true;
429
+ receiptAsserted = true;
430
+ } else if (rc.token || keySource) {
431
+ receiptDetail = 'the receipt was supplied without '
432
+ + `${rc.token ? 'a keyring or public key' : 'its token'}, so it could not be verified here`;
433
+ } else {
434
+ receiptDetail = 'no decision receipt was supplied';
435
+ }
436
+ if (!receiptOk) shortfalls.unshift(`receipt: ${receiptDetail}`);
437
+ if (receiptAsserted) {
438
+ shortfalls.push('decision_receipt: `verified` was taken on the caller\'s word — no token and '
439
+ + 'no keyring were supplied, so nothing was checked here');
440
+ }
441
+
442
+ // THE NAME OF SUCCESS DEPENDS ON WHO CHOSE THE SET, not on how much of it passed. A custom
443
+ // aggregation that meets everything it asked for is satisfied — and says so in its own words.
444
+ // A CALLER-ASSERTED RECEIPT CAN NEVER BE THE GLOBAL CLAIM. It is not downgraded to a failure —
445
+ // a custom aggregation may legitimately own that determination — but the strongest word in the
446
+ // vocabulary is not available to a run whose receipt nobody verified.
447
+ let state = (profile && !receiptAsserted)
448
+ ? STATE.AUTHORIZED_AND_COMMITTED : STATE.CUSTOM_REQUIREMENTS_SATISFIED;
449
+ if (!receiptOk || (required.has(AUTHORITY.ISSUER_GRANT) && !grantOk)) state = STATE.UNAUTHORIZED;
450
+ else if (required.has(AUTHORITY.EXECUTOR_ATTESTATION) && !attOk) state = STATE.COMMIT_UNPROVEN;
451
+ else if (required.has(AUTHORITY.ONE_RUN_ROOT) && !authorities[AUTHORITY.ONE_RUN_ROOT].ok) {
452
+ state = STATE.ONE_RUN_UNPROVEN;
453
+ } else if (required.has(AUTHORITY.PROVIDER_WITNESS) && !authorities[AUTHORITY.PROVIDER_WITNESS].ok) {
454
+ state = STATE.RECORDED_UNWITNESSED;
455
+ } else if (!committed) state = STATE.NOT_COMMITTED;
456
+ // The default set (no profile, no custom list) is the historical grant+attestation pair. It is
457
+ // an implicit choice by the caller, not a profile, so it lands in the custom lane too.
458
+
459
+
460
+ return {
461
+ v: BINDING_V,
462
+ authorized_and_committed: state === STATE.AUTHORIZED_AND_COMMITTED,
463
+ /**
464
+ * Did the set THIS CALLER ASKED FOR pass? True for a satisfied profile and for a satisfied
465
+ * custom aggregation alike.
466
+ *
467
+ * Separated from `authorized_and_committed` on purpose. A caller that only wants to know
468
+ * whether its own question was answered should not have to string-compare a state name, and
469
+ * — more importantly — should not be tempted to read the global claim because it was the only
470
+ * boolean available. That temptation is how `required: ['issuer_grant']` came to mean
471
+ * "authorized and committed" in the first place.
472
+ */
473
+ requirements_satisfied: state === STATE.AUTHORIZED_AND_COMMITTED
474
+ || state === STATE.CUSTOM_REQUIREMENTS_SATISFIED,
475
+ state,
476
+ /** Which closed profile answered this, or null when the caller aggregated its own set. */
477
+ profile: profile ? profile.name : null,
478
+ /** Stated on every result so a reader never has to infer it from the state name. */
479
+ proof_scope: profile ? profile.proof_scope : null,
480
+ externally_witnessed: profile ? profile.externally_witnessed : false,
481
+ authorities,
482
+ shortfalls,
483
+ /** True when `receipt.verified` was taken on the caller's word rather than established here. */
484
+ receipt_caller_asserted: receiptAsserted,
485
+ // Said out loud so a caller cannot read success as more than it is.
486
+ does_not_prove: [
487
+ 'that the executor told the truth about its own run — the evidence root closes third-party '
488
+ + 'splicing, not an executor misreporting itself',
489
+ 'that a provider merged anything; `provider_witness` is an unsigned readback unless a caller '
490
+ + 'states otherwise',
491
+ 'that the grant and the attestation agree about target, operation, tenant, executor, adapter, '
492
+ + 'audience or policy — cr.exec.attest.v1 is a closed field set that states none of them, so '
493
+ + 'those are UNCHECKED here rather than checked and equal (1464)',
494
+ 'that the attestation commits the exact grant BYTES — the format carries no grant-token '
495
+ + 'digest, so the join is over the grant id, scope, receipt and state nonce',
496
+ ...(receiptAsserted
497
+ ? ['that the decision receipt verifies — `receipt.verified` was asserted by the caller and '
498
+ + 'not established here; this core is given no receipt token or keyring to check it with']
499
+ : []),
500
+ ],
501
+ };
502
+ }
503
+
504
+ module.exports = {
505
+ verifiedExecutionBinding, STATE, AUTHORITY, PROFILE, PROFILE_NAMES, BINDING_V,
506
+ grantClaims, attestationClaims,
507
+ };