agent-passport-system 7.2.0 → 8.0.0

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 (61) hide show
  1. package/README.md +4 -5
  2. package/SECURITY.md +2 -2
  3. package/dist/src/cli/index.js +2 -2
  4. package/dist/src/cli/index.js.map +1 -1
  5. package/dist/src/core/delegation.d.ts.map +1 -1
  6. package/dist/src/core/delegation.js +52 -3
  7. package/dist/src/core/delegation.js.map +1 -1
  8. package/dist/src/core/external-action-ref.d.ts +31 -0
  9. package/dist/src/core/external-action-ref.d.ts.map +1 -1
  10. package/dist/src/core/external-action-ref.js +251 -17
  11. package/dist/src/core/external-action-ref.js.map +1 -1
  12. package/dist/src/core/reputation-authority.d.ts +5 -2
  13. package/dist/src/core/reputation-authority.d.ts.map +1 -1
  14. package/dist/src/core/reputation-authority.js +6 -3
  15. package/dist/src/core/reputation-authority.js.map +1 -1
  16. package/dist/src/core-exports.d.ts +1 -0
  17. package/dist/src/core-exports.d.ts.map +1 -1
  18. package/dist/src/core-exports.js +1 -0
  19. package/dist/src/core-exports.js.map +1 -1
  20. package/dist/src/index.d.ts +1 -0
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +1 -0
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/types/reputation-authority.d.ts +12 -7
  25. package/dist/src/types/reputation-authority.d.ts.map +1 -1
  26. package/dist/src/v2/claim-verifier.d.ts +10 -0
  27. package/dist/src/v2/claim-verifier.d.ts.map +1 -1
  28. package/dist/src/v2/claim-verifier.js +10 -0
  29. package/dist/src/v2/claim-verifier.js.map +1 -1
  30. package/dist/src/v2/identity-binding/passport.d.ts.map +1 -1
  31. package/dist/src/v2/identity-binding/passport.js +28 -10
  32. package/dist/src/v2/identity-binding/passport.js.map +1 -1
  33. package/dist/src/v2/identity-binding/principal-binding.d.ts.map +1 -1
  34. package/dist/src/v2/identity-binding/principal-binding.js +28 -11
  35. package/dist/src/v2/identity-binding/principal-binding.js.map +1 -1
  36. package/dist/src/v2/identity-binding/profile-recognition.d.ts +25 -0
  37. package/dist/src/v2/identity-binding/profile-recognition.d.ts.map +1 -0
  38. package/dist/src/v2/identity-binding/profile-recognition.js +67 -0
  39. package/dist/src/v2/identity-binding/profile-recognition.js.map +1 -0
  40. package/dist/src/v2/identity-binding/resolution-outcome.d.ts +23 -0
  41. package/dist/src/v2/identity-binding/resolution-outcome.d.ts.map +1 -0
  42. package/dist/src/v2/identity-binding/resolution-outcome.js +69 -0
  43. package/dist/src/v2/identity-binding/resolution-outcome.js.map +1 -0
  44. package/dist/src/v2/identity-binding/revocation.d.ts.map +1 -1
  45. package/dist/src/v2/identity-binding/revocation.js +26 -10
  46. package/dist/src/v2/identity-binding/revocation.js.map +1 -1
  47. package/dist/src/v2/identity-binding/types.d.ts +9 -0
  48. package/dist/src/v2/identity-binding/types.d.ts.map +1 -1
  49. package/dist/src/v2/payment-rails/cycles/evidence-resolution.d.ts +5 -0
  50. package/dist/src/v2/payment-rails/cycles/evidence-resolution.d.ts.map +1 -1
  51. package/dist/src/v2/payment-rails/cycles/evidence-resolution.js +46 -10
  52. package/dist/src/v2/payment-rails/cycles/evidence-resolution.js.map +1 -1
  53. package/dist/src/v2/payment-rails/cycles/index.d.ts +14 -3
  54. package/dist/src/v2/payment-rails/cycles/index.d.ts.map +1 -1
  55. package/dist/src/v2/payment-rails/cycles/index.js +391 -29
  56. package/dist/src/v2/payment-rails/cycles/index.js.map +1 -1
  57. package/dist/src/v2/payment-rails/cycles/types.d.ts +53 -2
  58. package/dist/src/v2/payment-rails/cycles/types.d.ts.map +1 -1
  59. package/dist/src/v2/payment-rails/index.d.ts +1 -1
  60. package/dist/src/v2/payment-rails/index.d.ts.map +1 -1
  61. package/package.json +21 -10
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/v2/payment-rails/cycles/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/v2/payment-rails/cycles/index.ts"],"names":[],"mappings":"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"}
@@ -79,7 +79,7 @@ import { publicKeyFromPrivate, sign, verify as edVerify } from '../../../crypto/
79
79
  import { parseDidUri, publicKeyHexFromMethod, resolveVerificationMethod, } from '../../../core/did-uri.js';
80
80
  import { RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_DENIAL_CLAIM_TYPE, } from './types.js';
81
81
  export { CYCLES_PROTOCOL_VERSION, CYCLES_EVIDENCE_SCHEMA_VERSION, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, RAIL_BUDGET_RESERVATION_DENIAL_CLAIM_TYPE, } from './types.js';
82
- import { resolveEvidenceRef, toEvidenceDescriptorInput, } from './evidence-resolution.js';
82
+ import { readEvidenceFetchResult, resolveEvidenceRef, toEvidenceDescriptorInput, } from './evidence-resolution.js';
83
83
  import { cyclesJwksUrl, isDIDCycles } from '../../key-resolution/index.js';
84
84
  // ── Internal helpers ──────────────────────────────────────────────
85
85
  function nowIso() {
@@ -269,18 +269,26 @@ export function signCyclesPermitReceipt(input, signerPrivateKeyHex) {
269
269
  return { ...unsigned, signature };
270
270
  }
271
271
  /** Verify a Cycles permit receipt (sync). With `options.evidence`, also
272
- * checks the receipt↔envelope hash binding (see `_checkEvidenceJoin`).
272
+ * checks the receipt↔envelope hash binding and that the envelope is the
273
+ * referenced stage for the same reservation_id (see `_checkEvidenceJoin`).
274
+ * Amount, unit, action and server identity are not compared.
273
275
  * NOTE: `valid: true` does NOT assert the envelope's own Ed25519
274
276
  * authenticity or signer authority — that is the v0.2 item (#43). Pin
275
277
  * `expected_signer` for issuer trust until then. */
276
278
  export function verifyCyclesPermitReceipt(receipt, options = {}) {
277
- return _verifyReceiptCore(receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, options);
279
+ const captured = _captureInputs(receipt, options);
280
+ if (!captured.ok)
281
+ return captured.failure;
282
+ return _verifyReceiptCore(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
278
283
  }
279
284
  /** Verify a Cycles permit receipt (async, DID-URI signers). Join-integrity
280
285
  * caveat as in `verifyCyclesPermitReceipt`: `valid: true` does NOT assert
281
286
  * the envelope's own Ed25519 authenticity (v0.2 — #43). */
282
287
  export async function verifyCyclesPermitReceiptWithDID(receipt, options = {}) {
283
- return _verifyReceiptWithDID(receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, options);
288
+ const captured = _captureInputs(receipt, options);
289
+ if (!captured.ok)
290
+ return captured.failure;
291
+ return _verifyReceiptWithDID(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
284
292
  }
285
293
  // ── Sign / verify: release receipt ────────────────────────────────
286
294
  export function signCyclesReleaseReceipt(input, signerPrivateKeyHex) {
@@ -309,18 +317,26 @@ export function signCyclesReleaseReceipt(input, signerPrivateKeyHex) {
309
317
  return { ...unsigned, signature };
310
318
  }
311
319
  /** Verify a Cycles release receipt (sync). With `options.evidence`, also
312
- * checks the receipt↔envelope hash binding (see `_checkEvidenceJoin`).
320
+ * checks the receipt↔envelope hash binding and that the envelope is the
321
+ * referenced stage for the same reservation_id (see `_checkEvidenceJoin`).
322
+ * Amount, unit, action and server identity are not compared.
313
323
  * NOTE: `valid: true` does NOT assert the envelope's own Ed25519
314
324
  * authenticity or signer authority — that is the v0.2 item (#43). Pin
315
325
  * `expected_signer` for issuer trust until then. */
316
326
  export function verifyCyclesReleaseReceipt(receipt, options = {}) {
317
- return _verifyReceiptCore(receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, options);
327
+ const captured = _captureInputs(receipt, options);
328
+ if (!captured.ok)
329
+ return captured.failure;
330
+ return _verifyReceiptCore(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
318
331
  }
319
332
  /** Verify a Cycles release receipt (async, DID-URI signers). Join-integrity
320
333
  * caveat as in `verifyCyclesReleaseReceipt`: `valid: true` does NOT assert
321
334
  * the envelope's own Ed25519 authenticity (v0.2 — #43). */
322
335
  export async function verifyCyclesReleaseReceiptWithDID(receipt, options = {}) {
323
- return _verifyReceiptWithDID(receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, options);
336
+ const captured = _captureInputs(receipt, options);
337
+ if (!captured.ok)
338
+ return captured.failure;
339
+ return _verifyReceiptWithDID(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
324
340
  }
325
341
  // ── Sign / verify: denial ─────────────────────────────────────────
326
342
  export function signCyclesDenial(input, signerPrivateKeyHex) {
@@ -385,6 +401,15 @@ export async function verifyCyclesDenialWithDID(denial, options = {}) {
385
401
  * but `evidence_id` POPULATED — per cycles-evidence-v0.1's normative
386
402
  * "Signature derivation". Failure → EVIDENCE_SIGNATURE_INVALID.
387
403
  *
404
+ * 3. CORRESPONDENCE (8.0.0, permit and release only): the envelope is the
405
+ * event the receipt references, same stage and same reservation_id (see
406
+ * `_evaluateCorrespondence`). Failure → EVIDENCE_CORRESPONDENCE_FIELD_MISSING
407
+ * or EVIDENCE_CORRESPONDENCE_MISMATCH. Denials are not checked here.
408
+ * The permit and release entry points pass captured inputs (see
409
+ * `_captureInputs`), so checks 1 to 3 read one value whose fields are
410
+ * exactly those in the hashed and signed bytes. The stage comes from the
411
+ * entry point's receipt kind.
412
+ *
388
413
  * Scope boundary (the (a)/(b) split): check 2 proves the bytes were signed
389
414
  * by the key in `signer_did` — it does NOT prove that key is the legitimate
390
415
  * Cycles signer rather than an attacker key inside a self-consistent forgery.
@@ -398,7 +423,7 @@ export async function verifyCyclesDenialWithDID(denial, options = {}) {
398
423
  * fixture, so the empty-string-sentinel rules here are the SAME
399
424
  * canonicalizations the Cycles server used. Field omission would differ.
400
425
  */
401
- function _checkEvidenceJoin(obj, options) {
426
+ function _checkEvidenceJoin(obj, options, stage) {
402
427
  const env = options.evidence;
403
428
  if (!env)
404
429
  return null;
@@ -407,14 +432,14 @@ function _checkEvidenceJoin(obj, options) {
407
432
  return {
408
433
  valid: false,
409
434
  reason: 'EVIDENCE_REF_HASH_MISMATCH',
410
- detail: `envelope evidence_id ${String(env.evidence_id)} != recomputed ${recomputed}`,
435
+ detail: `envelope evidence_id ${_show(env.evidence_id)} != recomputed ${recomputed}`,
411
436
  };
412
437
  }
413
438
  if (recomputed !== obj.cycles_evidence.cycles_evidence_id_sha256) {
414
439
  return {
415
440
  valid: false,
416
441
  reason: 'EVIDENCE_REF_HASH_MISMATCH',
417
- detail: `receipt cycles_evidence_id_sha256 ${obj.cycles_evidence.cycles_evidence_id_sha256} != envelope ${recomputed}`,
442
+ detail: `receipt cycles_evidence_id_sha256 ${_show(obj.cycles_evidence.cycles_evidence_id_sha256)} != envelope ${recomputed}`,
418
443
  };
419
444
  }
420
445
  // (a) Envelope authenticity: verify the envelope's own Ed25519 signature
@@ -438,8 +463,275 @@ function _checkEvidenceJoin(obj, options) {
438
463
  detail: 'envelope Ed25519 signature does not verify against signer_did',
439
464
  };
440
465
  }
466
+ // (c) Correspondence (permit and release): the envelope must be the event
467
+ // the receipt references. Runs only after the hash and signature checks
468
+ // pass, so a semantic failure is reported as such. The stage comes from
469
+ // the entry point's receipt kind, never from a field of the input.
470
+ if (!stage)
471
+ return null;
472
+ const correspondence = _evaluateCorrespondence(stage, obj, env);
473
+ if (correspondence.status === 'field_missing') {
474
+ return {
475
+ valid: false,
476
+ reason: 'EVIDENCE_CORRESPONDENCE_FIELD_MISSING',
477
+ detail: `${correspondence.field}: ${correspondence.detail}`,
478
+ };
479
+ }
480
+ if (correspondence.status === 'mismatch') {
481
+ return {
482
+ valid: false,
483
+ reason: 'EVIDENCE_CORRESPONDENCE_MISMATCH',
484
+ detail: `${correspondence.field}: ${correspondence.detail}`,
485
+ };
486
+ }
441
487
  return null;
442
488
  }
489
+ /** Fields deliberately outside the correspondence check. No equivalence rule
490
+ * is defined for them yet (estimate vs reserved vs actual amounts, unit
491
+ * families, action naming, server identity). Reported, never compared. */
492
+ const CORRESPONDENCE_NOT_COMPARED = Object.freeze([
493
+ 'amount',
494
+ 'unit',
495
+ 'action',
496
+ 'server_id',
497
+ ]);
498
+ function _isPlainObject(v) {
499
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
500
+ }
501
+ /** The stage a receipt kind references. Taken from the verifier entry point,
502
+ * never from the input, so a changing `claim_type` cannot switch the check off. */
503
+ function _stageFor(claimType) {
504
+ if (claimType === RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE)
505
+ return 'reserve';
506
+ if (claimType === RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE)
507
+ return 'release';
508
+ return undefined;
509
+ }
510
+ /** Detail text for any value. `String()` throws on a prototype-free object,
511
+ * so fall back to JSON. Ordinary values print exactly as before. */
512
+ function _show(v) {
513
+ try {
514
+ return String(v);
515
+ }
516
+ catch {
517
+ try {
518
+ return JSON.stringify(v) ?? 'unprintable';
519
+ }
520
+ catch {
521
+ return 'unprintable';
522
+ }
523
+ }
524
+ }
525
+ function _parseTime(v) {
526
+ try {
527
+ return Date.parse(v);
528
+ }
529
+ catch {
530
+ return NaN;
531
+ }
532
+ }
533
+ /** JSON.parse reviver: every object becomes a prototype-free record, so a
534
+ * property lookup finds own fields only, even if Object.prototype carries
535
+ * extra properties. Arrays stay arrays. */
536
+ function _prototypeFree(_key, value) {
537
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
538
+ return Object.assign(Object.create(null), value);
539
+ }
540
+ return value;
541
+ }
542
+ /**
543
+ * Capture an input as the data its canonical bytes encode: one JCS
544
+ * canonicalization, parsed back into prototype-free records. Inherited,
545
+ * non-enumerable and accessor properties and non-JSON containers such as Date
546
+ * are absent or already reduced to their JSON form, and later reads cannot
547
+ * change. Hashing, signature checks and correspondence on the permit and
548
+ * release paths read only captured values.
549
+ */
550
+ function _capture(value) {
551
+ let bytes;
552
+ try {
553
+ bytes = canonicalizeJCS(value);
554
+ }
555
+ catch (err) {
556
+ return { ok: false, detail: `has no canonical JSON form (${err instanceof Error ? err.message : _show(err)})` };
557
+ }
558
+ let parsed;
559
+ try {
560
+ parsed = JSON.parse(bytes, _prototypeFree);
561
+ }
562
+ catch (err) {
563
+ return { ok: false, detail: `has no capturable JSON form (${err instanceof Error ? err.message : _show(err)})` };
564
+ }
565
+ if (!_isPlainObject(parsed))
566
+ return { ok: false, detail: 'is not a JSON object' };
567
+ return { ok: true, value: parsed };
568
+ }
569
+ /** Every supported option, read by name. The check below fails the build if
570
+ * VerifyCyclesOptions gains a key that is not listed here. */
571
+ const VERIFY_OPTION_KEYS = [
572
+ 'now',
573
+ 'ttl_seconds',
574
+ 'expected_signer',
575
+ 'resolveDidDocument',
576
+ 'resolveEvidence',
577
+ 'evidenceFailurePolicy',
578
+ 'cyclesKeyResolver',
579
+ 'expectedEvidenceSigner',
580
+ 'maxEvidenceClockSkewMs',
581
+ 'evidence',
582
+ ];
583
+ const _allOptionKeysListed = true;
584
+ void _allOptionKeysListed;
585
+ /** Snapshot of the caller's options: each supported option read once by
586
+ * ordinary property lookup, as verification always has (own, non-enumerable
587
+ * or inherited), into a prototype-free object. Later reads and writes touch
588
+ * only this snapshot, so an accessor on Object.prototype cannot intercept
589
+ * them, and no constraint the caller supplied is dropped. */
590
+ function _snapshotOptions(options) {
591
+ const out = Object.create(null);
592
+ for (const key of VERIFY_OPTION_KEYS) {
593
+ const value = options[key];
594
+ if (value !== undefined)
595
+ out[key] = value;
596
+ }
597
+ return out;
598
+ }
599
+ function _isFetchFailureStatus(v) {
600
+ return v === 'not_found' || v === 'unreachable' || v === 'malformed';
601
+ }
602
+ /** The envelope a resolution result actually carries: an own property only,
603
+ * so a value on Object.prototype is never mistaken for a fetched envelope. */
604
+ function _ownEnvelope(evidence) {
605
+ return Object.prototype.hasOwnProperty.call(evidence, 'envelope') ? evidence.envelope : undefined;
606
+ }
607
+ /**
608
+ * Entry step for every permit and release verifier. Captures the receipt, the
609
+ * supplied envelope, and (through the resolver) any fetched envelope. A failed
610
+ * capture is a structured rejection. Verification never resumes on the
611
+ * uncaptured object. Denial verifiers do not use this.
612
+ */
613
+ function _captureInputs(receipt, options) {
614
+ // One read of each supported option into a prototype-free snapshot (see
615
+ // `_snapshotOptions`). Captured values below replace the originals there.
616
+ const out = _snapshotOptions(options);
617
+ const supplied = out.evidence;
618
+ const inner = out.resolveEvidence;
619
+ delete out.evidence;
620
+ delete out.resolveEvidence;
621
+ const r = _capture(receipt);
622
+ if (!r.ok) {
623
+ const detail = `receipt ${r.detail}, so its signature cannot be checked`;
624
+ return { ok: false, field: 'receipt', detail, options: out, failure: { valid: false, reason: 'SIGNATURE_INVALID', detail } };
625
+ }
626
+ if (supplied) {
627
+ const e = _capture(supplied);
628
+ if (!e.ok) {
629
+ const detail = `envelope ${e.detail}, so its content hash cannot be recomputed`;
630
+ return { ok: false, field: 'envelope', detail, options: out, failure: { valid: false, reason: 'EVIDENCE_REF_HASH_MISMATCH', detail } };
631
+ }
632
+ out.evidence = e.value;
633
+ }
634
+ if (inner) {
635
+ out.resolveEvidence = {
636
+ canResolve: (url) => inner.canResolve(url),
637
+ resolve: async (url) => {
638
+ // Read the caller's result once and return a fresh object, never the
639
+ // caller's own, so resolution cannot re-read a changing field.
640
+ const got = readEvidenceFetchResult(await inner.resolve(url));
641
+ const ok = got.ok;
642
+ const envelope = got.envelope;
643
+ const status = got.status;
644
+ const reason = got.reason;
645
+ if (!ok || !envelope) {
646
+ // A failure keeps only a status that means failure. Anything else,
647
+ // including a value that is not the resolver's own, becomes malformed.
648
+ const failed = Object.create(null);
649
+ failed.ok = false;
650
+ if (status !== undefined)
651
+ failed.status = _isFetchFailureStatus(status) ? status : 'malformed';
652
+ if (typeof reason === 'string')
653
+ failed.reason = reason;
654
+ return failed;
655
+ }
656
+ const e = _capture(envelope);
657
+ return e.ok
658
+ ? { ok: true, envelope: e.value }
659
+ : { ok: false, status: 'malformed', reason: `envelope ${e.detail}` };
660
+ },
661
+ };
662
+ }
663
+ return { ok: true, receipt: r.value, options: out };
664
+ }
665
+ /**
666
+ * Receipt-to-envelope correspondence (8.0.0). A permit references the reserve
667
+ * event for its reservation, a release references the release event for its
668
+ * reservation. Checks, in order: the receipt's reservation_id, the envelope's
669
+ * `artifact_type`, agreement of the single payload variant with that
670
+ * `artifact_type`, and the envelope's reservation_id at the stage path
671
+ * (reserve: payload.reserve.response.reservation_id, release:
672
+ * payload.release.reservation_id). A missing or malformed field is
673
+ * `field_missing`, never a pass. Callers pass the stage from the verifier
674
+ * entry point and captured inputs only (see `_captureInputs`).
675
+ */
676
+ function _evaluateCorrespondence(stage, obj, env) {
677
+ const receiptReservation = obj.reservation_id;
678
+ if (typeof receiptReservation !== 'string' || receiptReservation.length === 0) {
679
+ return {
680
+ status: 'field_missing',
681
+ field: 'receipt.reservation_id',
682
+ detail: 'absent or not a non-empty string',
683
+ };
684
+ }
685
+ const artifactType = env.artifact_type;
686
+ if (typeof artifactType !== 'string' || artifactType.length === 0) {
687
+ return { status: 'field_missing', field: 'artifact_type', detail: 'absent or not a non-empty string' };
688
+ }
689
+ if (artifactType !== stage) {
690
+ return {
691
+ status: 'mismatch',
692
+ field: 'artifact_type',
693
+ detail: `receipt references a ${stage} event, envelope is ${artifactType}`,
694
+ };
695
+ }
696
+ const payload = env.payload;
697
+ if (!_isPlainObject(payload)) {
698
+ return { status: 'field_missing', field: 'payload', detail: 'absent or not an object' };
699
+ }
700
+ const variants = Object.keys(payload);
701
+ if (variants.length === 0) {
702
+ return { status: 'field_missing', field: 'payload', detail: 'no payload variant' };
703
+ }
704
+ if (variants.length !== 1 || variants[0] !== stage) {
705
+ return {
706
+ status: 'mismatch',
707
+ field: 'payload',
708
+ detail: `payload variant [${variants.join(', ')}] does not agree with artifact_type ${stage}`,
709
+ };
710
+ }
711
+ const variant = payload[stage];
712
+ if (!_isPlainObject(variant)) {
713
+ return { status: 'field_missing', field: `payload.${stage}`, detail: 'absent or not an object' };
714
+ }
715
+ const holder = stage === 'reserve' ? variant.response : variant;
716
+ const path = stage === 'reserve' ? 'payload.reserve.response.reservation_id' : 'payload.release.reservation_id';
717
+ const envReservation = _isPlainObject(holder) ? holder.reservation_id : undefined;
718
+ if (typeof envReservation !== 'string' || envReservation.length === 0) {
719
+ return { status: 'field_missing', field: path, detail: 'absent or not a non-empty string' };
720
+ }
721
+ if (envReservation !== receiptReservation) {
722
+ return {
723
+ status: 'mismatch',
724
+ field: 'reservation_id',
725
+ detail: `receipt ${receiptReservation} != envelope ${envReservation}`,
726
+ };
727
+ }
728
+ return {
729
+ status: 'matched',
730
+ stage,
731
+ reservation_id: receiptReservation,
732
+ not_compared: CORRESPONDENCE_NOT_COMPARED,
733
+ };
734
+ }
443
735
  /**
444
736
  * Build the passing result, tagging envelope authenticity (APS#43) when an
445
737
  * envelope was supplied. Reaching here with `options.evidence` set means the
@@ -460,28 +752,32 @@ function _validResult(options) {
460
752
  function _verifyReceiptCore(obj, expectedClaimType, options) {
461
753
  const ttl = options.ttl_seconds ?? 24 * 60 * 60;
462
754
  if (obj.claim_type !== expectedClaimType) {
463
- return { valid: false, reason: 'INVALID_CLAIM_TYPE', detail: `expected ${expectedClaimType} got ${obj.claim_type}` };
755
+ return { valid: false, reason: 'INVALID_CLAIM_TYPE', detail: `expected ${expectedClaimType} got ${_show(obj.claim_type)}` };
464
756
  }
465
757
  if (!obj.receipt_id || !obj.signer || !obj.agent_id || !obj.delegation_ref || !obj.action_ref) {
466
758
  return { valid: false, reason: 'MISSING_REQUIRED_FIELD' };
467
759
  }
468
760
  if (obj.rail_name !== 'cycles') {
469
- return { valid: false, reason: 'INVALID_RAIL_NAME', detail: `expected 'cycles' got ${String(obj.rail_name)}` };
761
+ return { valid: false, reason: 'INVALID_RAIL_NAME', detail: `expected 'cycles' got ${_show(obj.rail_name)}` };
470
762
  }
471
- if (!obj.cycles_evidence?.cycles_evidence_url || !obj.cycles_evidence?.cycles_evidence_id_sha256) {
763
+ // cycles_evidence must be an object. An array or other value would let its
764
+ // fields be looked up on a prototype instead of in the signed bytes.
765
+ if (!_isPlainObject(obj.cycles_evidence) ||
766
+ !obj.cycles_evidence.cycles_evidence_url ||
767
+ !obj.cycles_evidence.cycles_evidence_id_sha256) {
472
768
  return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'cycles_evidence' };
473
769
  }
474
770
  // Join-integrity: when the fetched envelope is supplied, bind it to the
475
771
  // receipt. Runs before signature/DID checks so a broken receipt↔envelope
476
772
  // join surfaces regardless of signer type (the DID path calls this core
477
773
  // first and propagates any non-DID_RESOLVER_MISSING failure unchanged).
478
- const joinFailure = _checkEvidenceJoin(obj, options);
774
+ const joinFailure = _checkEvidenceJoin(obj, options, _stageFor(expectedClaimType));
479
775
  if (joinFailure)
480
776
  return joinFailure;
481
777
  if (options.expected_signer && obj.signer !== options.expected_signer) {
482
778
  return { valid: false, reason: 'SIGNATURE_INVALID', detail: 'signer mismatch' };
483
779
  }
484
- const issuedMs = Date.parse(obj.issued_at);
780
+ const issuedMs = _parseTime(obj.issued_at);
485
781
  if (!Number.isFinite(issuedMs)) {
486
782
  return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'issued_at' };
487
783
  }
@@ -495,7 +791,7 @@ function _verifyReceiptCore(obj, expectedClaimType, options) {
495
791
  if (obj.timestamp !== obj.issued_at) {
496
792
  return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'timestamp != issued_at' };
497
793
  }
498
- if (!obj.scope_of_claim ||
794
+ if (!_isPlainObject(obj.scope_of_claim) ||
499
795
  typeof obj.scope_of_claim.asserts !== 'string' ||
500
796
  obj.scope_of_claim.asserts.length === 0) {
501
797
  return { valid: false, reason: 'MISSING_REQUIRED_FIELD', detail: 'scope_of_claim' };
@@ -530,7 +826,7 @@ async function _verifyReceiptWithDID(obj, expectedClaimType, options) {
530
826
  const didDoc = await options.resolveDidDocument(parsed.agentId);
531
827
  if (!didDoc)
532
828
  return { valid: false, reason: 'DID_DOC_NOT_FOUND' };
533
- const issuedMs = Date.parse(obj.issued_at);
829
+ const issuedMs = _parseTime(obj.issued_at);
534
830
  const result = resolveVerificationMethod(didDoc, obj.signer, options.now ? options.now.getTime() : undefined, Number.isFinite(issuedMs) ? issuedMs : undefined);
535
831
  if (!result)
536
832
  return { valid: false, reason: 'DID_KEY_NOT_IN_DOC' };
@@ -559,6 +855,22 @@ const _verified = (disposition, signerDid, options, extra = {}) => {
559
855
  return { disposition, signerDid, ...extra };
560
856
  };
561
857
  const RAW_HEX_KEY = /^[0-9a-fA-F]{64}$/;
858
+ /** The supported fields of a key resolver answer, each read once by name and
859
+ * only when it is the answer's own property (enumerable or not), into a
860
+ * prototype-free record. No other field is read. Undefined for anything that
861
+ * is not an object. Callers read this inside their error guard, so a
862
+ * throwing accessor on a supported field becomes a structured failure. */
863
+ const KEY_ANSWER_FIELDS = ['ok', 'publicKeyHex', 'kid', 'status', 'reason'];
864
+ function _readKeyAnswer(value) {
865
+ if (value === null || typeof value !== 'object')
866
+ return undefined;
867
+ const out = Object.create(null);
868
+ for (const key of KEY_ANSWER_FIELDS) {
869
+ if (Object.prototype.hasOwnProperty.call(value, key))
870
+ out[key] = value[key];
871
+ }
872
+ return out;
873
+ }
562
874
  /** Map a non-ok KeyResolution status to the spec disposition. Failure to
563
875
  * OBTAIN/parse the SET (network, or an unparseable set body) is
564
876
  * resolution_failed; a parsed set with no single authorized key — incl. the
@@ -644,7 +956,7 @@ async function _resolveEnvelopeAuthority(env, options) {
644
956
  return _authority('signature_invalid', `envelope canonicalization failed: ${err instanceof Error ? err.message : String(err)}`);
645
957
  }
646
958
  if (recomputedId !== env.evidence_id) {
647
- return _authority('signature_invalid', `evidence_id does not re-derive (${recomputedId} != ${String(env.evidence_id)}) — envelope tampered`);
959
+ return _authority('signature_invalid', `evidence_id does not re-derive (${recomputedId} != ${_show(env.evidence_id)}) — envelope tampered`);
648
960
  }
649
961
  const resolver = options.cyclesKeyResolver;
650
962
  if (!resolver) {
@@ -664,44 +976,94 @@ async function _resolveEnvelopeAuthority(env, options) {
664
976
  : { jwksUrl: cyclesJwksUrl(serverId), publicKeyHex: signerDid, issuedAtMs };
665
977
  // The verify path must never throw — a non-conformant resolver that rejects
666
978
  // is treated as a resolution failure, never authentic.
667
- let resolution;
979
+ // Read the key resolver's answer from its own supported properties only,
980
+ // once, inside the guard. A key or success flag found only on
981
+ // Object.prototype is not a resolution.
982
+ let res;
668
983
  try {
669
- resolution = await resolver.resolve(locator);
984
+ res = _readKeyAnswer(await resolver.resolve(locator));
670
985
  }
671
986
  catch (err) {
672
987
  return _authority('signer_resolution_failed', `authority resolution threw: ${err instanceof Error ? err.message : String(err)}`);
673
988
  }
674
- if (!resolution.ok || !resolution.publicKeyHex) {
675
- return _authority(_authorityFromResolution(resolution.status, resolution.reason), `signer-key resolution failed (status=${resolution.status}${resolution.reason ? `: ${resolution.reason}` : ''})`);
989
+ if (!res) {
990
+ return _authority('signer_resolution_failed', 'key resolver did not return an object');
991
+ }
992
+ const resStatus = typeof res.status === 'string' ? res.status : '';
993
+ const resReason = typeof res.reason === 'string' ? res.reason : undefined;
994
+ const publicKeyHex = typeof res.publicKeyHex === 'string' ? res.publicKeyHex : '';
995
+ if (res.ok !== true || !publicKeyHex) {
996
+ return _authority(_authorityFromResolution(resStatus, resReason), `signer-key resolution failed (status=${resStatus || 'none'}${resReason ? `: ${resReason}` : ''})`);
676
997
  }
677
- if (!edVerify(signingInput, signature, resolution.publicKeyHex)) {
998
+ if (!edVerify(signingInput, signature, publicKeyHex)) {
678
999
  return _authority('signature_invalid', 'envelope signature did not verify under the resolved key');
679
1000
  }
680
- return _verified('authentic', signerDid, options, { resolvedKid: resolution.kid });
1001
+ return _verified('authentic', signerDid, options, typeof res.kid === 'string' ? { resolvedKid: res.kid } : {});
681
1002
  }
682
1003
  async function _verifyWithEvidence(obj, expectedClaimType, options) {
683
1004
  // Run the full receipt verification (DID-aware) first.
684
1005
  const verify = await _verifyReceiptWithDID(obj, expectedClaimType, options);
1006
+ const stage = _stageFor(expectedClaimType);
685
1007
  // Resolve the evidence ref regardless of the receipt verdict, so the
686
1008
  // descriptor always reports the claimed-vs-resolved basis. Resolution
687
- // records failure, never throws.
688
- const evidence = await resolveEvidenceRef(obj.cycles_evidence, options.resolveEvidence, { failurePolicy: options.evidenceFailurePolicy });
1009
+ // records failure, never throws. On the permit and release paths a ref that
1010
+ // is not an object is not read (its fields could come from a prototype).
1011
+ const evidence = stage && !_isPlainObject(obj.cycles_evidence)
1012
+ ? await _unreadEvidence('receipt cycles_evidence is not an object', options)
1013
+ : await resolveEvidenceRef(obj.cycles_evidence, options.resolveEvidence, { failurePolicy: options.evidenceFailurePolicy });
689
1014
  // Signer authority (#43): resolve the fetched envelope's signer key and
690
1015
  // verify the envelope's own signature. Independent of the receipt verdict
691
1016
  // and the hash join; reports one of the five distinct dispositions, never throws.
692
- const authority = await _resolveEnvelopeAuthority(evidence.envelope, options);
1017
+ const fetchedEnvelope = _ownEnvelope(evidence);
1018
+ const authority = await _resolveEnvelopeAuthority(fetchedEnvelope, options);
1019
+ // Correspondence (8.0.0) on the FETCHED envelope, reported separately so
1020
+ // verify / evidence / authority keep their meanings.
1021
+ // Stage from the entry point. The fetched envelope reaching here on the
1022
+ // permit and release paths is the captured one (see `_captureInputs`).
1023
+ const correspondence = !stage
1024
+ ? { status: 'not_applicable', reason: 'correspondence is evaluated for permit and release receipts only' }
1025
+ : evidence.resolved === true && evidence.status === 'matched' && _isPlainObject(fetchedEnvelope)
1026
+ ? _evaluateCorrespondence(stage, obj, fetchedEnvelope)
1027
+ : { status: 'not_applicable', reason: `no matched envelope (status=${evidence.status})` };
693
1028
  return {
694
1029
  verify,
695
1030
  evidence,
696
1031
  descriptor: toEvidenceDescriptorInput(evidence),
697
1032
  authority,
1033
+ correspondence,
1034
+ };
1035
+ }
1036
+ /** The fetched-path result when capture failed. Nothing further is read from
1037
+ * the uncaptured input: verify carries the structured rejection, the evidence
1038
+ * reference is reported malformed and unresolved, authority is not evaluated,
1039
+ * and correspondence names the field that could not be captured. */
1040
+ /** An evidence result reported as malformed and unresolved, built without
1041
+ * reading any caller input. */
1042
+ function _unreadEvidence(detail, options) {
1043
+ const reason = `not read: ${detail}`;
1044
+ return resolveEvidenceRef({ cycles_evidence_url: '', cycles_evidence_id_sha256: '', action_ref: '', delegation_ref: '' }, { canResolve: () => true, resolve: async () => ({ ok: false, status: 'malformed', reason }) }, { failurePolicy: options.evidenceFailurePolicy });
1045
+ }
1046
+ async function _rejectedWithEvidence(captured) {
1047
+ const evidence = await _unreadEvidence(captured.detail, captured.options);
1048
+ return {
1049
+ verify: captured.failure,
1050
+ evidence,
1051
+ descriptor: toEvidenceDescriptorInput(evidence),
1052
+ authority: _authority('signer_resolution_failed', `not evaluated: ${captured.detail}`),
1053
+ correspondence: { status: 'field_missing', field: captured.field, detail: captured.detail },
698
1054
  };
699
1055
  }
700
1056
  export async function verifyCyclesPermitReceiptWithEvidence(receipt, options = {}) {
701
- return _verifyWithEvidence(receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, options);
1057
+ const captured = _captureInputs(receipt, options);
1058
+ if (!captured.ok)
1059
+ return _rejectedWithEvidence(captured);
1060
+ return _verifyWithEvidence(captured.receipt, RAIL_BUDGET_RESERVATION_PERMIT_CLAIM_TYPE, captured.options);
702
1061
  }
703
1062
  export async function verifyCyclesReleaseReceiptWithEvidence(receipt, options = {}) {
704
- return _verifyWithEvidence(receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, options);
1063
+ const captured = _captureInputs(receipt, options);
1064
+ if (!captured.ok)
1065
+ return _rejectedWithEvidence(captured);
1066
+ return _verifyWithEvidence(captured.receipt, RAIL_BUDGET_RESERVATION_RELEASE_CLAIM_TYPE, captured.options);
705
1067
  }
706
1068
  /**
707
1069
  * Verify a FETCHED CyclesEvidence envelope's own signer authority directly