@tinycloud/sdk-core 3.0.0-beta.0 → 3.0.0-beta.10

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.
@@ -38,6 +38,8 @@ __export(policy_exports, {
38
38
  POLICY_ENGINE_RECORD_SCHEMA: () => POLICY_ENGINE_RECORD_SCHEMA,
39
39
  POLICY_PRESENTATION_V3_DOMAIN: () => POLICY_PRESENTATION_V3_DOMAIN,
40
40
  POLICY_PRESENTATION_V3_SCHEMA: () => POLICY_PRESENTATION_V3_SCHEMA,
41
+ POLICY_PRESENTATION_V4_DOMAIN: () => POLICY_PRESENTATION_V4_DOMAIN,
42
+ POLICY_PRESENTATION_V4_SCHEMA: () => POLICY_PRESENTATION_V4_SCHEMA,
41
43
  POLICY_SCHEMA: () => POLICY_SCHEMA,
42
44
  POLICY_SESSION_UCAN_V1_PROFILE: () => POLICY_SESSION_UCAN_V1_PROFILE,
43
45
  POLICY_STATUS_SCHEMA: () => POLICY_STATUS_SCHEMA,
@@ -65,7 +67,9 @@ __export(policy_exports, {
65
67
  UnsupportedSignatureSuiteError: () => UnsupportedSignatureSuiteError,
66
68
  W3C_VC_CREDENTIAL_VERIFIER: () => W3C_VC_CREDENTIAL_VERIFIER,
67
69
  admitPolicyCredentialV3: () => admitPolicyCredentialV3,
70
+ admitPolicyCredentialV4: () => admitPolicyCredentialV4,
68
71
  buildPolicyCredentialPresentationV3: () => buildPolicyCredentialPresentationV3,
72
+ buildPolicyCredentialPresentationV4: () => buildPolicyCredentialPresentationV4,
69
73
  canonicalizePolicyCapability: () => canonicalizePolicyCapability,
70
74
  canonicalizeSignedObjectUnsigned: () => canonicalizeSignedObjectUnsigned,
71
75
  canonicalizeUnifiedPolicyCapability: () => canonicalizeUnifiedPolicyCapability,
@@ -83,6 +87,7 @@ __export(policy_exports, {
83
87
  createUnsignedPolicyEngineRecord: () => createUnsignedPolicyEngineRecord,
84
88
  deriveSignedObjectMaterial: () => deriveSignedObjectMaterial,
85
89
  getPolicyRootStatusV3: () => getPolicyRootStatusV3,
90
+ issuedCredentialEnvelopeFromVerified: () => issuedCredentialEnvelopeFromVerified,
86
91
  jcsCanonicalize: () => jcsCanonicalize,
87
92
  mintPolicySessionV3: () => mintPolicySessionV3,
88
93
  normalizeJson: () => normalizeJson,
@@ -98,12 +103,14 @@ __export(policy_exports, {
98
103
  policyV2DigestHex: () => policyV2DigestHex,
99
104
  projectUnifiedPolicyCapabilities: () => projectUnifiedPolicyCapabilities,
100
105
  projectUnifiedPolicyCapability: () => projectUnifiedPolicyCapability,
106
+ registerPolicyV3: () => registerPolicyV3,
101
107
  renewPolicyRootStatusV3: () => renewPolicyRootStatusV3,
102
108
  requestPolicyChallengeV3: () => requestPolicyChallengeV3,
103
109
  revokePolicyRootV3: () => revokePolicyRootV3,
104
110
  serializeJcsJson: () => serialize,
105
111
  signCompactUcanAuthorization: () => signCompactUcanAuthorization,
106
112
  signCompactUcanRootAuthorization: () => signCompactUcanRootAuthorization,
113
+ signPolicyCredentialPresentationV4: () => signPolicyCredentialPresentationV4,
107
114
  signedObjectIdFor: () => signedObjectIdFor,
108
115
  toSignedObjectError: () => toSignedObjectError,
109
116
  unifiedNativeProjectionHashHex: () => unifiedNativeProjectionHashHex,
@@ -3335,9 +3342,11 @@ var MAX_LEGACY_ENVELOPE_EXPIRES_AT = "2026-12-29T00:00:00Z";
3335
3342
  var LAST_V2_READ_AT = "2027-01-05T00:00:00Z";
3336
3343
  async function requestPolicyChallengeV3(input) {
3337
3344
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3338
- const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
3345
+ const policyRuntimePath = policyRuntimeBasePath(input.policyRuntimePath);
3346
+ const response = await fetchFn(new URL(`${policyRuntimePath}/challenges`, input.nodeOrigin), {
3339
3347
  method: "POST",
3340
3348
  redirect: "error",
3349
+ signal: input.signal,
3341
3350
  headers: { accept: "application/json", "content-type": "application/json" },
3342
3351
  body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
3343
3352
  });
@@ -3347,14 +3356,22 @@ async function requestPolicyChallengeV3(input) {
3347
3356
  throw new Error("policy challenge binding is invalid");
3348
3357
  return value;
3349
3358
  }
3359
+ function policyRuntimeBasePath(value) {
3360
+ const path = value ?? "/policy/v3";
3361
+ if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
3362
+ throw new Error("policy runtime path is invalid");
3363
+ }
3364
+ return path;
3365
+ }
3350
3366
  async function mintPolicySessionV3(input) {
3351
3367
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3352
3368
  const challenge = input.challenge ?? await requestPolicyChallengeV3(input);
3353
3369
  if (challenge.policyCid !== input.policyCid || challenge.recipientDid !== input.recipientDid)
3354
3370
  throw new Error("policy challenge binding is invalid");
3355
- const response = await fetchFn(new URL("/share/v3/policy/delegations", input.nodeOrigin), {
3371
+ const response = await fetchFn(new URL("/policy/v3/delegations", input.nodeOrigin), {
3356
3372
  method: "POST",
3357
3373
  redirect: "error",
3374
+ signal: input.signal,
3358
3375
  headers: { accept: "application/json", "content-type": "application/json" },
3359
3376
  body: JSON.stringify({ policyCid: input.policyCid, challengeId: challenge.challengeId, nonce: challenge.nonce, claim: input.claim, presentation: input.presentation })
3360
3377
  });
@@ -3413,7 +3430,7 @@ function verifyPolicyRootStatusCheckpointV3(input) {
3413
3430
  }
3414
3431
  async function getPolicyRootStatusV3(input) {
3415
3432
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3416
- const response = await fetchFn(new URL(`/share/v3/policy/status/${encodeURIComponent(input.rootCid)}`, input.nodeOrigin), {
3433
+ const response = await fetchFn(new URL(`/policy/v3/status/${encodeURIComponent(input.rootCid)}`, input.nodeOrigin), {
3417
3434
  method: "GET",
3418
3435
  redirect: "error",
3419
3436
  headers: { accept: "application/json" }
@@ -3472,7 +3489,7 @@ async function renewPolicyRootStatusV3(input) {
3472
3489
  };
3473
3490
  const signature = await input.sign((0, import_sha256.sha256)(new TextEncoder().encode(ROOT_STATUS_RENEWAL_V1_DOMAIN + jcsCanonicalize(unsigned))));
3474
3491
  if (signature.length !== 64) throw new Error("policy root renewal signature must be Ed25519");
3475
- const response = await fetchFn(new URL("/share/v3/policy/status", input.nodeOrigin), {
3492
+ const response = await fetchFn(new URL("/policy/v3/status", input.nodeOrigin), {
3476
3493
  method: "POST",
3477
3494
  redirect: "error",
3478
3495
  headers: { accept: "application/json", "content-type": "application/json" },
@@ -3528,6 +3545,11 @@ var POLICY_SESSION_FACT_KEYS = [
3528
3545
  "issuanceAuditDigestHex",
3529
3546
  "remainingRedelegationDepth"
3530
3547
  ];
3548
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
3549
+ "credentialIdAuditDigestHex",
3550
+ "presentationJtiAuditDigestHex"
3551
+ ];
3552
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
3531
3553
  var KV_ACTIONS = /* @__PURE__ */ new Set([
3532
3554
  "tinycloud.kv/get",
3533
3555
  "tinycloud.kv/list",
@@ -3722,6 +3744,133 @@ function contentSourceDigestHex(input) {
3722
3744
  )
3723
3745
  );
3724
3746
  }
3747
+ function exactObject(value, keys, label) {
3748
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3749
+ throw new Error(`${label} is invalid`);
3750
+ }
3751
+ const record2 = value;
3752
+ if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) {
3753
+ throw new Error(`${label} is invalid`);
3754
+ }
3755
+ return record2;
3756
+ }
3757
+ function didKeyEd25519PublicKey(value) {
3758
+ const principal = value.split("#", 1)[0];
3759
+ if (!principal.startsWith("did:key:")) {
3760
+ throw new Error("policy enforcer binding signer is invalid");
3761
+ }
3762
+ const material = base58btc.decode(principal.slice("did:key:".length));
3763
+ if (material.length !== 34 || material[0] !== 237 || material[1] !== 1) {
3764
+ throw new Error("policy enforcer binding signer is invalid");
3765
+ }
3766
+ return material.slice(2);
3767
+ }
3768
+ async function registerPolicyV3(input) {
3769
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3770
+ const rootExpiresAt = new Date(input.rootExpiresAt);
3771
+ const canonicalRootExpiresAt = new Date(
3772
+ Math.floor(rootExpiresAt.getTime() / 1e3) * 1e3
3773
+ ).toISOString().replace(/\.000Z$/, "Z");
3774
+ if (!Number.isFinite(rootExpiresAt.getTime()) || canonicalRootExpiresAt !== input.rootExpiresAt || rootExpiresAt.getTime() <= Date.now()) {
3775
+ throw new Error("policy root expiry is invalid");
3776
+ }
3777
+ const bindingResponse = await fetchFn(
3778
+ new URL("/policy/v3/enforcer-bindings", input.nodeOrigin),
3779
+ {
3780
+ method: "POST",
3781
+ redirect: "error",
3782
+ signal: input.signal,
3783
+ headers: { accept: "application/json", "content-type": "application/json" },
3784
+ body: JSON.stringify({
3785
+ rootExpiresAt: input.rootExpiresAt,
3786
+ enforcerDid: input.enforcerDid
3787
+ })
3788
+ }
3789
+ );
3790
+ if (!bindingResponse.ok) {
3791
+ throw new Error(`policy enforcer binding rejected (${bindingResponse.status})`);
3792
+ }
3793
+ const binding = exactObject(
3794
+ await bindingResponse.json(),
3795
+ [
3796
+ "schema",
3797
+ "enforcerDid",
3798
+ "nodeAudience",
3799
+ "attestationBindingDigestHex",
3800
+ "issuedAt",
3801
+ "expiresAt",
3802
+ "signature"
3803
+ ],
3804
+ "policy enforcer binding"
3805
+ );
3806
+ const signature = exactObject(
3807
+ binding.signature,
3808
+ ["suite", "signerDid", "value"],
3809
+ "policy enforcer binding signature"
3810
+ );
3811
+ const expectedBindingDigestHex = hex(
3812
+ (0, import_sha256.sha256)(
3813
+ new TextEncoder().encode(
3814
+ jcsCanonicalize({
3815
+ enforcerDid: input.enforcerDid,
3816
+ nodeAudience: input.expectedNodeAudience
3817
+ })
3818
+ )
3819
+ )
3820
+ );
3821
+ if (binding.schema !== ATTESTED_ENFORCER_BINDING_V2_SCHEMA || binding.enforcerDid !== input.enforcerDid || binding.nodeAudience !== input.expectedNodeAudience || binding.attestationBindingDigestHex !== expectedBindingDigestHex || binding.expiresAt !== input.rootExpiresAt || typeof binding.issuedAt !== "string" || Date.parse(binding.issuedAt) > Date.now() || signature.suite !== "Ed25519" || signature.signerDid !== input.expectedNodeAudience || typeof signature.value !== "string") {
3822
+ throw new Error("policy enforcer binding is invalid");
3823
+ }
3824
+ const { signature: _signature, ...unsignedBinding } = binding;
3825
+ const signedBinding = (0, import_sha256.sha256)(
3826
+ new TextEncoder().encode(
3827
+ `xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0${jcsCanonicalize(unsignedBinding)}`
3828
+ )
3829
+ );
3830
+ if (!import_ed255192.ed25519.verify(
3831
+ decodeBase64Url(signature.value),
3832
+ signedBinding,
3833
+ didKeyEd25519PublicKey(input.expectedNodeAudience),
3834
+ { zip215: false }
3835
+ )) {
3836
+ throw new Error("policy enforcer binding signature is invalid");
3837
+ }
3838
+ const response = await fetchFn(
3839
+ new URL("/policy/v3/policies", input.nodeOrigin),
3840
+ {
3841
+ method: "POST",
3842
+ redirect: "error",
3843
+ signal: input.signal,
3844
+ headers: { accept: "application/json", "content-type": "application/json" },
3845
+ body: JSON.stringify({
3846
+ policyCid: input.policyCid,
3847
+ policy: input.policy,
3848
+ policyRoot: input.policyRoot.authorization.replace(/^Bearer\s+/i, ""),
3849
+ enforcementRoot: input.enforcementRoot.authorization.replace(/^Bearer\s+/i, ""),
3850
+ contentSourceDigestHex: input.contentSourceDigestHex,
3851
+ nativeProjectionHashHex: input.nativeProjectionHashHex,
3852
+ attestedEnforcerBinding: binding
3853
+ })
3854
+ }
3855
+ );
3856
+ if (!response.ok) {
3857
+ throw new Error(`policy registration rejected (${response.status})`);
3858
+ }
3859
+ const receipt = exactObject(
3860
+ await response.json(),
3861
+ ["policyCid", "policyRootCid", "enforcementRootCid"],
3862
+ "policy registration response"
3863
+ );
3864
+ if (receipt.policyCid !== input.policyCid || receipt.policyRootCid !== input.policyRoot.cid || receipt.enforcementRootCid !== input.enforcementRoot.cid) {
3865
+ throw new Error("policy registration response binding is invalid");
3866
+ }
3867
+ return {
3868
+ policyCid: input.policyCid,
3869
+ policyRootCid: input.policyRoot.cid,
3870
+ enforcementRootCid: input.enforcementRoot.cid,
3871
+ attestedEnforcerBinding: binding
3872
+ };
3873
+ }
3725
3874
  function policyDigestHex(policy) {
3726
3875
  const canonicalPolicy = {
3727
3876
  ...policy,
@@ -3920,9 +4069,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
3920
4069
  if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
3921
4070
  throw new Error("policy session UCAN ordered proofs do not match");
3922
4071
  const factKeys = Object.keys(fact);
3923
- if (factKeys.length !== POLICY_SESSION_FACT_KEYS.length || factKeys.some((key) => !POLICY_SESSION_FACT_KEYS.includes(key)) || POLICY_SESSION_FACT_KEYS.some((key) => !(key in fact)))
4072
+ const hasLegacyFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact);
4073
+ const hasV4AuditFactShape = factKeys.length === POLICY_SESSION_FACT_KEYS.length + POLICY_SESSION_V4_AUDIT_FACT_KEYS.length && factKeys.every((key) => POLICY_SESSION_FACT_KEYS.includes(key) || POLICY_SESSION_V4_AUDIT_FACT_KEYS.includes(key)) && POLICY_SESSION_FACT_KEYS.every((key) => key in fact) && POLICY_SESSION_V4_AUDIT_FACT_KEYS.every((key) => key in fact);
4074
+ if (!hasLegacyFactShape && !hasV4AuditFactShape)
3924
4075
  throw new Error("policy session UCAN fact is incomplete");
3925
- if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.enforcerDid || parsed.payload.aud !== fact.recipientDid)
4076
+ if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4077
+ throw new Error("policy session UCAN v4 audit facts are invalid");
4078
+ if (fact.profile !== POLICY_SESSION_UCAN_V1_PROFILE || typeof fact.remainingRedelegationDepth !== "number" || !Number.isInteger(fact.remainingRedelegationDepth) || fact.remainingRedelegationDepth < 0 || fact.remainingRedelegationDepth > 8 || parsed.payload.exp - parsed.payload.nbf > 60 || parsed.payload.iss.split("#", 1)[0] !== fact.nodeAudience || parsed.payload.aud !== fact.recipientDid)
3926
4079
  throw new Error("policy session UCAN fact is invalid");
3927
4080
  return {
3928
4081
  authorization,
@@ -4050,6 +4203,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
4050
4203
  var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
4051
4204
  var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
4052
4205
  var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
4206
+ var POLICY_PRESENTATION_V4_SCHEMA = "xyz.tinycloud.policy/presentation/v4";
4207
+ var POLICY_PRESENTATION_V4_DOMAIN = "xyz.tinycloud.policy/Presentation/v4\0";
4053
4208
  function record(value, label) {
4054
4209
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4055
4210
  throw new Error(`${label} must be an object`);
@@ -4224,6 +4379,97 @@ function envelopeFromVerified(verified) {
4224
4379
  } = verified;
4225
4380
  return envelope;
4226
4381
  }
4382
+ function issuedCredentialEnvelopeFromVerified(verified) {
4383
+ return envelopeFromVerified(verified);
4384
+ }
4385
+ async function signPolicyCredentialPresentationV4(value, sign) {
4386
+ const item = record(value, "Policy presentation v4");
4387
+ exactKeys2(item, [
4388
+ "schema",
4389
+ "jti",
4390
+ "challengeId",
4391
+ "nonce",
4392
+ "policyCid",
4393
+ "nodeAudience",
4394
+ "holderDid",
4395
+ "subjectDid",
4396
+ "credentialDigest",
4397
+ "requirementDigest",
4398
+ "descriptorDigest",
4399
+ "requestedCapabilities",
4400
+ "issuedAt",
4401
+ "expiresAt"
4402
+ ], "Policy presentation v4");
4403
+ if (item.schema !== POLICY_PRESENTATION_V4_SCHEMA || item.holderDid !== item.subjectDid || !Array.isArray(item.requestedCapabilities)) throw new Error("Policy presentation v4 is invalid");
4404
+ const holderDid = nonEmpty(item.holderDid, "Policy presentation v4 holder DID");
4405
+ didKeyBytes(holderDid);
4406
+ const unsigned = Object.freeze({
4407
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4408
+ jti: nonEmpty(item.jti, "Policy presentation v4 jti"),
4409
+ challengeId: nonEmpty(item.challengeId, "Policy presentation v4 challenge"),
4410
+ nonce: nonEmpty(item.nonce, "Policy presentation v4 nonce"),
4411
+ policyCid: nonEmpty(item.policyCid, "Policy presentation v4 policy CID"),
4412
+ nodeAudience: nonEmpty(item.nodeAudience, "Policy presentation v4 audience"),
4413
+ holderDid,
4414
+ subjectDid: holderDid,
4415
+ credentialDigest: nonEmpty(item.credentialDigest, "Policy presentation v4 credential digest"),
4416
+ requirementDigest: nonEmpty(item.requirementDigest, "Policy presentation v4 requirement digest"),
4417
+ descriptorDigest: nonEmpty(item.descriptorDigest, "Policy presentation v4 descriptor digest"),
4418
+ // The low-level signer preserves the frozen wire bytes exactly. The
4419
+ // policy-aware builder performs capability normalization and containment.
4420
+ requestedCapabilities: Object.freeze(item.requestedCapabilities),
4421
+ issuedAt: date(item.issuedAt, "Policy presentation v4 issuedAt"),
4422
+ expiresAt: date(item.expiresAt, "Policy presentation v4 expiresAt")
4423
+ });
4424
+ const digest2 = (0, import_sha2562.sha256)(new TextEncoder().encode(
4425
+ POLICY_PRESENTATION_V4_DOMAIN + jcsCanonicalize(unsigned)
4426
+ ));
4427
+ const signature = await sign(digest2);
4428
+ if (signature.length !== 64 || !import_ed255193.ed25519.verify(signature, digest2, didKeyBytes(holderDid))) throw new Error("policy presentation signature is invalid");
4429
+ return Object.freeze({
4430
+ ...unsigned,
4431
+ signature: Object.freeze({
4432
+ suite: "Ed25519",
4433
+ signerDid: holderDid,
4434
+ value: encodeBase64Url2(signature)
4435
+ })
4436
+ });
4437
+ }
4438
+ async function buildPolicyCredentialPresentationV4(input) {
4439
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4440
+ const requirement = validateCredentialRequirement(input.requirement);
4441
+ const commitment = policy.credentialRequirement;
4442
+ const requirementDigest = await credentialRequirementDigest(requirement);
4443
+ const credentialDigest = await sha256Base64Url(input.credential.credential);
4444
+ if (requirementDigest !== commitment.requirementDigest || input.credential.descriptorDigest !== commitment.descriptorDigest || input.credential.credentialDigest !== credentialDigest || input.credential.issuerDid !== commitment.issuerDid || input.credential.issuerKid !== commitment.issuerKid || jcsCanonicalize(input.credential.profile) !== jcsCanonicalize(commitment.profile) || jcsCanonicalize(input.credential.credentialType) !== jcsCanonicalize(commitment.credentialType)) throw new Error("credential does not match the signed Policy/v2 requirement");
4445
+ const requestedCapabilities = input.requestedCapabilities.map(normalizeUnifiedPolicyCapability);
4446
+ if (requestedCapabilities.length === 0 || requestedCapabilities.some((requested) => !policy.capabilityCeiling.some((authority) => unifiedPolicyCapabilityContains(authority, requested)))) throw new Error("requested capabilities exceed the signed Policy/v2 ceiling");
4447
+ const holderDid = input.credential.holderDid;
4448
+ if (input.challenge.policyCid !== input.policyCid || input.challenge.recipientDid !== holderDid || input.credential.subjectDid !== holderDid || typeof input.challenge.nodeAudience !== "string" || input.challenge.nodeAudience.length === 0 || typeof input.challenge.expiresAt !== "string" || !Number.isFinite(Date.parse(input.challenge.expiresAt))) throw new Error("policy challenge binding is invalid");
4449
+ const now = input.now ?? /* @__PURE__ */ new Date();
4450
+ const expiresAtMs = Math.min(
4451
+ now.getTime() + 6e4,
4452
+ Date.parse(input.challenge.expiresAt),
4453
+ Date.parse(input.credential.expiresAt)
4454
+ );
4455
+ if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
4456
+ return signPolicyCredentialPresentationV4(Object.freeze({
4457
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4458
+ jti: input.jti ?? encodeBase64Url2(crypto.getRandomValues(new Uint8Array(16))),
4459
+ challengeId: input.challenge.challengeId,
4460
+ nonce: input.challenge.nonce,
4461
+ policyCid: input.policyCid,
4462
+ nodeAudience: input.challenge.nodeAudience,
4463
+ holderDid,
4464
+ subjectDid: holderDid,
4465
+ credentialDigest,
4466
+ requirementDigest,
4467
+ descriptorDigest: input.credential.descriptorDigest,
4468
+ requestedCapabilities: Object.freeze(requestedCapabilities),
4469
+ issuedAt: rfc3339Seconds(now),
4470
+ expiresAt: rfc3339Seconds(new Date(expiresAtMs))
4471
+ }), input.sign);
4472
+ }
4227
4473
  async function buildPolicyCredentialPresentationV3(input) {
4228
4474
  const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4229
4475
  const requirement = validateCredentialRequirement(input.requirement);
@@ -4310,10 +4556,12 @@ async function admitPolicyCredentialV3(input) {
4310
4556
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4311
4557
  const challenge = await requestPolicyChallengeV3({
4312
4558
  nodeOrigin,
4559
+ policyRuntimePath: input.policyRuntimePath,
4313
4560
  policyCid: input.policyCid,
4314
4561
  recipientDid: input.credential.holderDid,
4315
4562
  requestedCapabilities: input.requestedCapabilities,
4316
- fetch: fetchFn
4563
+ fetch: fetchFn,
4564
+ signal: input.signal
4317
4565
  });
4318
4566
  const presentation = await buildPolicyCredentialPresentationV3({
4319
4567
  ...input,
@@ -4321,10 +4569,11 @@ async function admitPolicyCredentialV3(input) {
4321
4569
  challenge
4322
4570
  });
4323
4571
  const response = await fetchFn(
4324
- new URL("/share/v3/policy/delegations", nodeOrigin),
4572
+ new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4325
4573
  {
4326
4574
  method: "POST",
4327
4575
  redirect: "error",
4576
+ signal: input.signal,
4328
4577
  headers: { accept: "application/json", "content-type": "application/json" },
4329
4578
  body: JSON.stringify({
4330
4579
  policyCid: input.policyCid,
@@ -4353,6 +4602,72 @@ async function admitPolicyCredentialV3(input) {
4353
4602
  if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) {
4354
4603
  throw new Error("policy delegation signed binding is invalid");
4355
4604
  }
4605
+ input.signal?.throwIfAborted();
4356
4606
  return Object.freeze({ challenge, presentation, session });
4357
4607
  }
4608
+ function validatePolicyCredentialAdmissionV4Authority(session, expected) {
4609
+ if (session.iss.split("#", 1)[0] !== expected.nodeAudience || session.fact.nodeAudience !== expected.nodeAudience || session.fact.enforcerDid !== expected.enforcerDid) throw new Error("policy delegation authority binding is invalid");
4610
+ }
4611
+ async function admitPolicyCredentialV4(input) {
4612
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4613
+ const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
4614
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4615
+ const challenge = await requestPolicyChallengeV3({
4616
+ nodeOrigin,
4617
+ policyRuntimePath: input.policyRuntimePath,
4618
+ policyCid: input.policyCid,
4619
+ recipientDid: input.credential.holderDid,
4620
+ requestedCapabilities: input.requestedCapabilities,
4621
+ fetch: fetchFn,
4622
+ signal: input.signal
4623
+ });
4624
+ const presentation = await buildPolicyCredentialPresentationV4({
4625
+ ...input,
4626
+ policy,
4627
+ challenge
4628
+ });
4629
+ const response = await fetchFn(new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin), {
4630
+ method: "POST",
4631
+ redirect: "error",
4632
+ signal: input.signal,
4633
+ headers: { accept: "application/json", "content-type": "application/json" },
4634
+ body: JSON.stringify({
4635
+ policyCid: input.policyCid,
4636
+ challengeId: challenge.challengeId,
4637
+ nonce: challenge.nonce,
4638
+ requirement: validateCredentialRequirement(input.requirement),
4639
+ credential: envelopeFromVerified(input.credential),
4640
+ presentation
4641
+ })
4642
+ });
4643
+ if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4644
+ const value = record(await response.json(), "policy delegation");
4645
+ if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
4646
+ const session = parsePolicySessionUcan(value.authorization, [
4647
+ input.policyRootCid,
4648
+ input.enforcementRootCid
4649
+ ]);
4650
+ const expectedAttenuation = compactAttenuationForPolicyCapabilities(input.requestedCapabilities);
4651
+ validatePolicyCredentialAdmissionV4Authority(session, {
4652
+ nodeAudience: input.expectedNodeAudience,
4653
+ enforcerDid: input.expectedEnforcerDid
4654
+ });
4655
+ if (session.cid !== value.sessionCid || session.aud !== input.credential.holderDid || session.fact.policyCid !== input.policyCid || session.fact.recipientDid !== input.credential.holderDid || session.fact.policyDelegationCid !== input.policyRootCid || session.fact.enforcementDelegationCid !== input.enforcementRootCid || jcsCanonicalize(session.att) !== jcsCanonicalize(expectedAttenuation)) throw new Error("policy delegation signed binding is invalid");
4656
+ const importResponse = await fetchFn(new URL("/delegate", nodeOrigin), {
4657
+ method: "POST",
4658
+ redirect: "error",
4659
+ signal: input.signal,
4660
+ headers: { Authorization: session.authorization }
4661
+ });
4662
+ if (!importResponse.ok) throw new Error(`delegation import rejected (${importResponse.status})`);
4663
+ input.signal?.throwIfAborted();
4664
+ return Object.freeze({ challenge, presentation, session, delegationImported: true });
4665
+ }
4666
+ function policyRuntimeBasePath2(value) {
4667
+ const path = value ?? "/policy/v3";
4668
+ if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
4669
+ throw new Error("policy runtime path is invalid");
4670
+ }
4671
+ return path;
4672
+ }
4358
4673
  //# sourceMappingURL=index.cjs.map