@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.
package/dist/index.js CHANGED
@@ -565,6 +565,17 @@ function isDigest(value) {
565
565
 
566
566
  // src/credentials/requirement.ts
567
567
  var ID2 = /^[a-z0-9][a-z0-9._-]{0,127}(?:\/v1)?$/;
568
+ var EMAIL_CREDENTIAL_MAX_AGE_SECONDS = 3600;
569
+ function createEmailCredentialRequirement(input) {
570
+ return validateCredentialRequirement({
571
+ type: "TinyCloudCredentialRequirement",
572
+ version: 1,
573
+ profile: input.profile,
574
+ credentialType: input.credentialType,
575
+ claims: { email: input.email },
576
+ maxAgeSeconds: EMAIL_CREDENTIAL_MAX_AGE_SECONDS
577
+ });
578
+ }
568
579
  function validateCredentialRequirement(value) {
569
580
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new CredentialError("UNSUPPORTED_PROFILE", "Credential requirement is invalid");
570
581
  const raw = value;
@@ -1779,16 +1790,16 @@ function policyCapabilityContains(authority, request) {
1779
1790
  return caveatsContain(auth.caveats, req.caveats);
1780
1791
  }
1781
1792
  function normalizePolicyCapabilityWithOptions(input, options2) {
1782
- const object3 = expectObject(input, "$");
1783
- assertExactKeys(object3, ["service", "space", "path", "actions", "caveats"], "$");
1784
- const service = requiredString(object3, "service", "$", "policy-capability-malformed-service");
1793
+ const object4 = expectObject(input, "$");
1794
+ assertExactKeys(object4, ["service", "space", "path", "actions", "caveats"], "$");
1795
+ const service = requiredString(object4, "service", "$", "policy-capability-malformed-service");
1785
1796
  if (!CEILING_SERVICES.has(service)) {
1786
1797
  throw new PolicyCapabilityError(
1787
1798
  "policy-capability-malformed-service",
1788
1799
  "$.service is outside the frozen permissionsCeiling vocabulary"
1789
1800
  );
1790
1801
  }
1791
- const space = requiredString(object3, "space", "$", "policy-capability-malformed-space");
1802
+ const space = requiredString(object4, "space", "$", "policy-capability-malformed-space");
1792
1803
  validateConcreteSpace(space);
1793
1804
  const canonicalSpace = space.normalize("NFC");
1794
1805
  if (options2.requireCanonical && canonicalSpace !== space) {
@@ -1797,7 +1808,7 @@ function normalizePolicyCapabilityWithOptions(input, options2) {
1797
1808
  "$.space must already be canonical NFC"
1798
1809
  );
1799
1810
  }
1800
- const rawPath = requiredString(object3, "path", "$", "policy-capability-malformed-path");
1811
+ const rawPath = requiredString(object4, "path", "$", "policy-capability-malformed-path");
1801
1812
  validateRawPath(rawPath, options2);
1802
1813
  const path = normalizePath(rawPath);
1803
1814
  if (options2.requireCanonical && path !== rawPath) {
@@ -1808,7 +1819,7 @@ function normalizePolicyCapabilityWithOptions(input, options2) {
1808
1819
  }
1809
1820
  validateRawPath(path, options2);
1810
1821
  const actions = normalizeActions(
1811
- requiredArray(object3, "actions", "$", "policy-capability-malformed-action"),
1822
+ requiredArray(object4, "actions", "$", "policy-capability-malformed-action"),
1812
1823
  service,
1813
1824
  options2
1814
1825
  );
@@ -1817,9 +1828,9 @@ function normalizePolicyCapabilityWithOptions(input, options2) {
1817
1828
  output.space = canonicalSpace;
1818
1829
  output.path = path;
1819
1830
  output.actions = actions;
1820
- if (hasOwn(object3, "caveats")) {
1831
+ if (hasOwn(object4, "caveats")) {
1821
1832
  output.caveats = validateCaveats(
1822
- requiredValue(object3, "caveats", "$", "policy-capability-malformed-caveats"),
1833
+ requiredValue(object4, "caveats", "$", "policy-capability-malformed-caveats"),
1823
1834
  output.service
1824
1835
  );
1825
1836
  }
@@ -1842,9 +1853,9 @@ function expectObject(input, path) {
1842
1853
  );
1843
1854
  }
1844
1855
  }
1845
- function assertExactKeys(object3, allowed, path) {
1856
+ function assertExactKeys(object4, allowed, path) {
1846
1857
  const allowedSet = new Set(allowed);
1847
- for (const key of Object.keys(object3)) {
1858
+ for (const key of Object.keys(object4)) {
1848
1859
  if (!allowedSet.has(key)) {
1849
1860
  throw new PolicyCapabilityError(
1850
1861
  key === "id" || key === "scope" ? "policy-capability-malformed" : "policy-capability-unknown-key",
@@ -1853,21 +1864,21 @@ function assertExactKeys(object3, allowed, path) {
1853
1864
  }
1854
1865
  }
1855
1866
  }
1856
- function requiredValue(object3, key, path, code3) {
1857
- if (!hasOwn(object3, key)) {
1867
+ function requiredValue(object4, key, path, code3) {
1868
+ if (!hasOwn(object4, key)) {
1858
1869
  throw new PolicyCapabilityError(code3, `${path}.${key} is required`);
1859
1870
  }
1860
- return object3[key];
1871
+ return object4[key];
1861
1872
  }
1862
- function requiredString(object3, key, path, code3) {
1863
- const value = requiredValue(object3, key, path, code3);
1873
+ function requiredString(object4, key, path, code3) {
1874
+ const value = requiredValue(object4, key, path, code3);
1864
1875
  if (typeof value !== "string" || value.length === 0) {
1865
1876
  throw new PolicyCapabilityError(code3, `${path}.${key} must be a non-empty string`);
1866
1877
  }
1867
1878
  return value;
1868
1879
  }
1869
- function requiredArray(object3, key, path, code3) {
1870
- const value = requiredValue(object3, key, path, code3);
1880
+ function requiredArray(object4, key, path, code3) {
1881
+ const value = requiredValue(object4, key, path, code3);
1871
1882
  if (!Array.isArray(value)) {
1872
1883
  throw new PolicyCapabilityError(code3, `${path}.${key} must be an array`);
1873
1884
  }
@@ -2012,11 +2023,11 @@ function validateSqlStatement(input, path) {
2012
2023
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
2013
2024
  throw new PolicyCapabilityError("policy-capability-malformed-caveats", `${path} must be an object`);
2014
2025
  }
2015
- const object3 = input;
2016
- assertCaveatKeys(object3, ["name", "sql", "fixedParams"], path);
2017
- caveatString(object3, "name", path);
2018
- caveatString(object3, "sql", path);
2019
- const fixedParams = requiredCaveat(object3, "fixedParams", path);
2026
+ const object4 = input;
2027
+ assertCaveatKeys(object4, ["name", "sql", "fixedParams"], path);
2028
+ caveatString(object4, "name", path);
2029
+ caveatString(object4, "sql", path);
2030
+ const fixedParams = requiredCaveat(object4, "fixedParams", path);
2020
2031
  if (!Array.isArray(fixedParams)) {
2021
2032
  throw new PolicyCapabilityError(
2022
2033
  "policy-capability-malformed-caveats",
@@ -2031,20 +2042,20 @@ function validateFixedParam(input, path) {
2031
2042
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
2032
2043
  throw new PolicyCapabilityError("policy-capability-malformed-caveats", `${path} must be an object`);
2033
2044
  }
2034
- const object3 = input;
2035
- assertCaveatKeys(object3, ["index", "value"], path);
2036
- const index = requiredCaveat(object3, "index", path);
2045
+ const object4 = input;
2046
+ assertCaveatKeys(object4, ["index", "value"], path);
2047
+ const index = requiredCaveat(object4, "index", path);
2037
2048
  if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) {
2038
2049
  throw new PolicyCapabilityError(
2039
2050
  "policy-capability-malformed-caveats",
2040
2051
  `${path}.index must be a non-negative integer`
2041
2052
  );
2042
2053
  }
2043
- requiredCaveat(object3, "value", path);
2054
+ requiredCaveat(object4, "value", path);
2044
2055
  }
2045
- function assertCaveatKeys(object3, allowed, path) {
2056
+ function assertCaveatKeys(object4, allowed, path) {
2046
2057
  const allowedSet = new Set(allowed);
2047
- for (const key of Object.keys(object3)) {
2058
+ for (const key of Object.keys(object4)) {
2048
2059
  if (!allowedSet.has(key)) {
2049
2060
  throw new PolicyCapabilityError(
2050
2061
  "policy-capability-malformed-caveats",
@@ -2053,17 +2064,17 @@ function assertCaveatKeys(object3, allowed, path) {
2053
2064
  }
2054
2065
  }
2055
2066
  }
2056
- function requiredCaveat(object3, key, path) {
2057
- if (!hasOwn(object3, key)) {
2067
+ function requiredCaveat(object4, key, path) {
2068
+ if (!hasOwn(object4, key)) {
2058
2069
  throw new PolicyCapabilityError(
2059
2070
  "policy-capability-malformed-caveats",
2060
2071
  `${path}.${key} is required`
2061
2072
  );
2062
2073
  }
2063
- return object3[key];
2074
+ return object4[key];
2064
2075
  }
2065
- function caveatString(object3, key, path) {
2066
- const value = requiredCaveat(object3, key, path);
2076
+ function caveatString(object4, key, path) {
2077
+ const value = requiredCaveat(object4, key, path);
2067
2078
  if (typeof value !== "string" || value.length === 0) {
2068
2079
  throw new PolicyCapabilityError(
2069
2080
  "policy-capability-malformed-caveats",
@@ -2094,8 +2105,8 @@ function caveatsContain(authority, request) {
2094
2105
  (statement) => authorityStatements.some((candidate) => jcsCanonicalize(candidate) === jcsCanonicalize(statement))
2095
2106
  );
2096
2107
  }
2097
- function hasOwn(object3, key) {
2098
- return objectHasOwn2(object3, key);
2108
+ function hasOwn(object4, key) {
2109
+ return objectHasOwn2(object4, key);
2099
2110
  }
2100
2111
 
2101
2112
  // src/policy/signed-object.ts
@@ -2423,17 +2434,17 @@ async function createAndSignSignedObject(input, signer) {
2423
2434
  }
2424
2435
  function createAndSignPolicy(input, signer) {
2425
2436
  return createAndSignSignedObject(input, signer).then(
2426
- (object3) => validatePolicySignedShape(object3)
2437
+ (object4) => validatePolicySignedShape(object4)
2427
2438
  );
2428
2439
  }
2429
2440
  function createAndSignPolicyStatus(input, signer) {
2430
2441
  return createAndSignSignedObject(input, signer).then(
2431
- (object3) => validatePolicyStatusSignedShape(object3)
2442
+ (object4) => validatePolicyStatusSignedShape(object4)
2432
2443
  );
2433
2444
  }
2434
2445
  function createAndSignPolicyEngineRecord(input, signer) {
2435
2446
  return createAndSignSignedObject(input, signer).then(
2436
- (object3) => validatePolicyEngineRecordSignedShape(object3)
2447
+ (object4) => validatePolicyEngineRecordSignedShape(object4)
2437
2448
  );
2438
2449
  }
2439
2450
  async function verifySignedObject(input) {
@@ -2537,9 +2548,9 @@ function validateUnsignedForDescriptor(input, descriptor) {
2537
2548
  }
2538
2549
  }
2539
2550
  function validatePolicyShape(input, signed) {
2540
- const object3 = expectJsonObject(normalizeJson(input), "$");
2551
+ const object4 = expectJsonObject(normalizeJson(input), "$");
2541
2552
  assertExactKeys2(
2542
- object3,
2553
+ object4,
2543
2554
  signed ? [
2544
2555
  "schema",
2545
2556
  "policyId",
@@ -2567,30 +2578,30 @@ function validatePolicyShape(input, signed) {
2567
2578
  ],
2568
2579
  "$"
2569
2580
  );
2570
- expectConst(requiredString2(object3, "schema", "$"), POLICY_SCHEMA, "$.schema");
2581
+ expectConst(requiredString2(object4, "schema", "$"), POLICY_SCHEMA, "$.schema");
2571
2582
  if (signed) {
2572
- requiredString2(object3, "policyId", "$");
2573
- validateSignature(requiredValue2(object3, "signature", "$"));
2583
+ requiredString2(object4, "policyId", "$");
2584
+ validateSignature(requiredValue2(object4, "signature", "$"));
2574
2585
  }
2575
- requiredString2(object3, "ownerDid", "$");
2576
- requiredString2(object3, "signingKeyDid", "$");
2577
- requiredDateString(object3, "createdAt", "$");
2578
- optionalDateString(object3, "expiresAt", "$");
2579
- validatePolicyResource(requiredValue2(object3, "resource", "$"), "$.resource");
2580
- validateExpression(requiredValue2(object3, "when", "$"), "$.when");
2581
- validateGrant(requiredValue2(object3, "grant", "$"), "$.grant");
2582
- if (hasOwn2(object3, "disclosure")) {
2583
- validateDisclosure(requiredValue2(object3, "disclosure", "$"), "$.disclosure");
2586
+ requiredString2(object4, "ownerDid", "$");
2587
+ requiredString2(object4, "signingKeyDid", "$");
2588
+ requiredDateString(object4, "createdAt", "$");
2589
+ optionalDateString(object4, "expiresAt", "$");
2590
+ validatePolicyResource(requiredValue2(object4, "resource", "$"), "$.resource");
2591
+ validateExpression(requiredValue2(object4, "when", "$"), "$.when");
2592
+ validateGrant(requiredValue2(object4, "grant", "$"), "$.grant");
2593
+ if (hasOwn2(object4, "disclosure")) {
2594
+ validateDisclosure(requiredValue2(object4, "disclosure", "$"), "$.disclosure");
2584
2595
  }
2585
- if (hasOwn2(object3, "audit")) {
2586
- validateAudit(requiredValue2(object3, "audit", "$"), "$.audit");
2596
+ if (hasOwn2(object4, "audit")) {
2597
+ validateAudit(requiredValue2(object4, "audit", "$"), "$.audit");
2587
2598
  }
2588
- return object3;
2599
+ return object4;
2589
2600
  }
2590
2601
  function validatePolicyStatusShape(input, signed) {
2591
- const object3 = expectJsonObject(normalizeJson(input), "$");
2602
+ const object4 = expectJsonObject(normalizeJson(input), "$");
2592
2603
  assertExactKeys2(
2593
- object3,
2604
+ object4,
2594
2605
  signed ? [
2595
2606
  "schema",
2596
2607
  "statusId",
@@ -2614,28 +2625,28 @@ function validatePolicyStatusShape(input, signed) {
2614
2625
  ],
2615
2626
  "$"
2616
2627
  );
2617
- expectConst(requiredString2(object3, "schema", "$"), POLICY_STATUS_SCHEMA, "$.schema");
2628
+ expectConst(requiredString2(object4, "schema", "$"), POLICY_STATUS_SCHEMA, "$.schema");
2618
2629
  if (signed) {
2619
- requiredString2(object3, "statusId", "$");
2620
- validateSignature(requiredValue2(object3, "signature", "$"));
2630
+ requiredString2(object4, "statusId", "$");
2631
+ validateSignature(requiredValue2(object4, "signature", "$"));
2621
2632
  }
2622
- requiredString2(object3, "policyId", "$");
2623
- requiredString2(object3, "ownerDid", "$");
2624
- requiredInteger(object3, "sequence", "$", 0);
2625
- expectOneOf(requiredString2(object3, "disposition", "$"), [
2633
+ requiredString2(object4, "policyId", "$");
2634
+ requiredString2(object4, "ownerDid", "$");
2635
+ requiredInteger(object4, "sequence", "$", 0);
2636
+ expectOneOf(requiredString2(object4, "disposition", "$"), [
2626
2637
  "active",
2627
2638
  "suspended",
2628
2639
  "revoked"
2629
2640
  ], "$.disposition");
2630
- requiredDateString(object3, "effectiveAt", "$");
2631
- optionalString(object3, "reasonCode", "$");
2632
- requiredString2(object3, "signingKeyDid", "$");
2633
- return object3;
2641
+ requiredDateString(object4, "effectiveAt", "$");
2642
+ optionalString(object4, "reasonCode", "$");
2643
+ requiredString2(object4, "signingKeyDid", "$");
2644
+ return object4;
2634
2645
  }
2635
2646
  function validatePolicyEngineRecordShape(input, signed) {
2636
- const object3 = expectJsonObject(normalizeJson(input), "$");
2647
+ const object4 = expectJsonObject(normalizeJson(input), "$");
2637
2648
  assertExactKeys2(
2638
- object3,
2649
+ object4,
2639
2650
  signed ? [
2640
2651
  "schema",
2641
2652
  "engineRecordId",
@@ -2660,44 +2671,44 @@ function validatePolicyEngineRecordShape(input, signed) {
2660
2671
  "$"
2661
2672
  );
2662
2673
  expectConst(
2663
- requiredString2(object3, "schema", "$"),
2674
+ requiredString2(object4, "schema", "$"),
2664
2675
  POLICY_ENGINE_RECORD_SCHEMA,
2665
2676
  "$.schema"
2666
2677
  );
2667
2678
  if (signed) {
2668
- requiredString2(object3, "engineRecordId", "$");
2669
- validateSignature(requiredValue2(object3, "signature", "$"));
2679
+ requiredString2(object4, "engineRecordId", "$");
2680
+ validateSignature(requiredValue2(object4, "signature", "$"));
2670
2681
  }
2671
- requiredString2(object3, "ownerDid", "$");
2672
- requiredString2(object3, "endpoint", "$");
2673
- requiredString2(object3, "audience", "$");
2682
+ requiredString2(object4, "ownerDid", "$");
2683
+ requiredString2(object4, "endpoint", "$");
2684
+ requiredString2(object4, "audience", "$");
2674
2685
  requiredStringArray(
2675
- object3,
2686
+ object4,
2676
2687
  "supportedPolicyVersions",
2677
2688
  "$",
2678
2689
  (value, path) => expectConst(value, "v0", path)
2679
2690
  );
2680
- requiredStringArray(object3, "supportedEvidenceVerifiers", "$");
2681
- requiredString2(object3, "grantIssuerDid", "$");
2682
- requiredDateString(object3, "expiresAt", "$");
2683
- return object3;
2691
+ requiredStringArray(object4, "supportedEvidenceVerifiers", "$");
2692
+ requiredString2(object4, "grantIssuerDid", "$");
2693
+ requiredDateString(object4, "expiresAt", "$");
2694
+ return object4;
2684
2695
  }
2685
2696
  function validateSignature(input) {
2686
- const object3 = expectJsonObject(input, "$.signature");
2687
- assertExactKeys2(object3, ["suite", "signerDid", "value"], "$.signature");
2688
- const suite = requiredString2(object3, "suite", "$.signature");
2697
+ const object4 = expectJsonObject(input, "$.signature");
2698
+ assertExactKeys2(object4, ["suite", "signerDid", "value"], "$.signature");
2699
+ const suite = requiredString2(object4, "suite", "$.signature");
2689
2700
  assertSupportedSignatureSuite(suite);
2690
- const signerDid = requiredString2(object3, "signerDid", "$.signature");
2691
- const value = requiredString2(object3, "value", "$.signature");
2701
+ const signerDid = requiredString2(object4, "signerDid", "$.signature");
2702
+ const value = requiredString2(object4, "value", "$.signature");
2692
2703
  decodeSignatureValue(value, suite);
2693
2704
  return { suite, signerDid, value };
2694
2705
  }
2695
2706
  function validatePolicyResource(input, path) {
2696
- const object3 = expectJsonObject(input, path);
2697
- assertExactKeys2(object3, ["resourceType", "resourceId", "permissionsCeiling"], path);
2698
- requiredString2(object3, "resourceType", path);
2699
- requiredString2(object3, "resourceId", path);
2700
- const ceiling = requiredArray2(object3, "permissionsCeiling", path, 1);
2707
+ const object4 = expectJsonObject(input, path);
2708
+ assertExactKeys2(object4, ["resourceType", "resourceId", "permissionsCeiling"], path);
2709
+ requiredString2(object4, "resourceType", path);
2710
+ requiredString2(object4, "resourceId", path);
2711
+ const ceiling = requiredArray2(object4, "permissionsCeiling", path, 1);
2701
2712
  for (let index = 0; index < ceiling.length; index++) {
2702
2713
  validatePolicyCapability(ceiling[index], `${path}.permissionsCeiling[${index}]`);
2703
2714
  }
@@ -2713,10 +2724,10 @@ function validatePolicyPermissionsCeilingForSigning(input) {
2713
2724
  }
2714
2725
  }
2715
2726
  function validatePolicyCapability(input, path) {
2716
- const object3 = expectJsonObject(input, path);
2727
+ const object4 = expectJsonObject(input, path);
2717
2728
  try {
2718
- const canonical = canonicalizePolicyCapability(object3);
2719
- if (jcsCanonicalize(object3) !== jcsCanonicalize(canonical)) {
2729
+ const canonical = canonicalizePolicyCapability(object4);
2730
+ if (jcsCanonicalize(object4) !== jcsCanonicalize(canonical)) {
2720
2731
  throw new SignedObjectSchemaError(`${path} must be canonical PolicyCapability JSON`);
2721
2732
  }
2722
2733
  } catch (error) {
@@ -2729,10 +2740,10 @@ function validatePolicyCapability(input, path) {
2729
2740
  }
2730
2741
  }
2731
2742
  function validatePolicyCapabilityForSigning(input, path) {
2732
- const object3 = expectJsonObject(input, path);
2743
+ const object4 = expectJsonObject(input, path);
2733
2744
  try {
2734
- const canonical = normalizePolicyCapability(object3);
2735
- if (jcsCanonicalize(object3) !== jcsCanonicalize(canonical)) {
2745
+ const canonical = normalizePolicyCapability(object4);
2746
+ if (jcsCanonicalize(object4) !== jcsCanonicalize(canonical)) {
2736
2747
  throw new SignedObjectSchemaError(`${path} must be strict canonical PolicyCapability JSON`);
2737
2748
  }
2738
2749
  } catch (error) {
@@ -2745,43 +2756,43 @@ function validatePolicyCapabilityForSigning(input, path) {
2745
2756
  }
2746
2757
  }
2747
2758
  function validateExpression(input, path) {
2748
- const object3 = expectJsonObject(input, path);
2749
- const keys = Object.keys(object3);
2759
+ const object4 = expectJsonObject(input, path);
2760
+ const keys = Object.keys(object4);
2750
2761
  if (keys.length !== 1) {
2751
2762
  throw new SignedObjectSchemaError(`${path} must have exactly one expression key`);
2752
2763
  }
2753
2764
  const key = keys[0];
2754
2765
  if (key === "allOf" || key === "anyOf") {
2755
- const values = requiredArray2(object3, key, path, 1);
2766
+ const values = requiredArray2(object4, key, path, 1);
2756
2767
  for (let index = 0; index < values.length; index++) {
2757
2768
  validateExpression(values[index], `${path}.${key}[${index}]`);
2758
2769
  }
2759
2770
  return;
2760
2771
  }
2761
2772
  if (key === "subject") {
2762
- const subject = expectJsonObject(requiredValue2(object3, "subject", path), `${path}.subject`);
2773
+ const subject = expectJsonObject(requiredValue2(object4, "subject", path), `${path}.subject`);
2763
2774
  assertExactKeys2(subject, ["did"], `${path}.subject`);
2764
2775
  requiredString2(subject, "did", `${path}.subject`);
2765
2776
  return;
2766
2777
  }
2767
2778
  if (key === "evidence") {
2768
- validateEvidenceRequirement(requiredValue2(object3, "evidence", path), `${path}.evidence`);
2779
+ validateEvidenceRequirement(requiredValue2(object4, "evidence", path), `${path}.evidence`);
2769
2780
  return;
2770
2781
  }
2771
2782
  throw new SignedObjectSchemaError(`${path} has unknown expression key ${key}`);
2772
2783
  }
2773
2784
  function validateEvidenceRequirement(input, path) {
2774
- const object3 = expectJsonObject(input, path);
2785
+ const object4 = expectJsonObject(input, path);
2775
2786
  assertExactKeys2(
2776
- object3,
2787
+ object4,
2777
2788
  ["requirementId", "verifier", "requirements", "authority", "freshness"],
2778
2789
  path
2779
2790
  );
2780
- requiredString2(object3, "requirementId", path);
2781
- requiredString2(object3, "verifier", path);
2782
- requiredValue2(object3, "requirements", path);
2783
- if (hasOwn2(object3, "authority")) {
2784
- const authority = expectJsonObject(requiredValue2(object3, "authority", path), `${path}.authority`);
2791
+ requiredString2(object4, "requirementId", path);
2792
+ requiredString2(object4, "verifier", path);
2793
+ requiredValue2(object4, "requirements", path);
2794
+ if (hasOwn2(object4, "authority")) {
2795
+ const authority = expectJsonObject(requiredValue2(object4, "authority", path), `${path}.authority`);
2785
2796
  assertExactKeys2(
2786
2797
  authority,
2787
2798
  ["profile", "acceptedIssuers", "allowOwnerAuthorizedIssuer"],
@@ -2793,35 +2804,35 @@ function validateEvidenceRequirement(input, path) {
2793
2804
  }
2794
2805
  optionalBoolean(authority, "allowOwnerAuthorizedIssuer", `${path}.authority`);
2795
2806
  }
2796
- if (hasOwn2(object3, "freshness")) {
2797
- const freshness = expectJsonObject(requiredValue2(object3, "freshness", path), `${path}.freshness`);
2807
+ if (hasOwn2(object4, "freshness")) {
2808
+ const freshness = expectJsonObject(requiredValue2(object4, "freshness", path), `${path}.freshness`);
2798
2809
  assertExactKeys2(freshness, ["maxStatusAgeSeconds"], `${path}.freshness`);
2799
2810
  requiredInteger(freshness, "maxStatusAgeSeconds", `${path}.freshness`, 0);
2800
2811
  }
2801
2812
  }
2802
2813
  function validateGrant(input, path) {
2803
- const object3 = expectJsonObject(input, path);
2804
- assertExactKeys2(object3, ["output", "maxTtlSeconds", "delegationMode", "revocation"], path);
2805
- expectConst(requiredString2(object3, "output", path), "portable-delegation", `${path}.output`);
2806
- requiredInteger(object3, "maxTtlSeconds", path, 1);
2807
- expectOneOf(requiredString2(object3, "delegationMode", path), [
2814
+ const object4 = expectJsonObject(input, path);
2815
+ assertExactKeys2(object4, ["output", "maxTtlSeconds", "delegationMode", "revocation"], path);
2816
+ expectConst(requiredString2(object4, "output", path), "portable-delegation", `${path}.output`);
2817
+ requiredInteger(object4, "maxTtlSeconds", path, 1);
2818
+ expectOneOf(requiredString2(object4, "delegationMode", path), [
2808
2819
  "terminal",
2809
2820
  "attenuable"
2810
2821
  ], `${path}.delegationMode`);
2811
- expectOneOf(requiredString2(object3, "revocation", path), [
2822
+ expectOneOf(requiredString2(object4, "revocation", path), [
2812
2823
  "refresh_only",
2813
2824
  "active_cutoff"
2814
2825
  ], `${path}.revocation`);
2815
2826
  }
2816
2827
  function validateDisclosure(input, path) {
2817
- const object3 = expectJsonObject(input, path);
2818
- assertExactKeys2(object3, ["denial"], path);
2819
- expectOneOf(requiredString2(object3, "denial", path), ["none", "code", "debug"], `${path}.denial`);
2828
+ const object4 = expectJsonObject(input, path);
2829
+ assertExactKeys2(object4, ["denial"], path);
2830
+ expectOneOf(requiredString2(object4, "denial", path), ["none", "code", "debug"], `${path}.denial`);
2820
2831
  }
2821
2832
  function validateAudit(input, path) {
2822
- const object3 = expectJsonObject(input, path);
2823
- assertExactKeys2(object3, ["issuance"], path);
2824
- expectOneOf(requiredString2(object3, "issuance", path), ["off", "security", "full"], `${path}.issuance`);
2833
+ const object4 = expectJsonObject(input, path);
2834
+ assertExactKeys2(object4, ["issuance"], path);
2835
+ expectOneOf(requiredString2(object4, "issuance", path), ["off", "security", "full"], `${path}.issuance`);
2825
2836
  }
2826
2837
  function materialForUnsigned(unsigned, descriptor) {
2827
2838
  const jcs = jcsCanonicalize(unsigned);
@@ -2970,9 +2981,9 @@ function encodeSignatureValue(value, suite) {
2970
2981
  decodeSignatureValue(encoded, suite);
2971
2982
  return encoded;
2972
2983
  }
2973
- function stripOwnIdAndSignature(object3, descriptor) {
2984
+ function stripOwnIdAndSignature(object4, descriptor) {
2974
2985
  const output = /* @__PURE__ */ Object.create(null);
2975
- for (const [key, value] of Object.entries(object3)) {
2986
+ for (const [key, value] of Object.entries(object4)) {
2976
2987
  if (key === descriptor.idField || key === "signature") {
2977
2988
  continue;
2978
2989
  }
@@ -2981,8 +2992,8 @@ function stripOwnIdAndSignature(object3, descriptor) {
2981
2992
  return output;
2982
2993
  }
2983
2994
  function descriptorForUnsigned(input) {
2984
- const object3 = expectJsonObject(normalizeJson(input), "$");
2985
- return descriptorForSchema(requiredString2(object3, "schema", "$"));
2995
+ const object4 = expectJsonObject(normalizeJson(input), "$");
2996
+ return descriptorForSchema(requiredString2(object4, "schema", "$"));
2986
2997
  }
2987
2998
  function descriptorForSchema(schema) {
2988
2999
  if (schema === POLICY_SCHEMA) return DESCRIPTORS.Policy;
@@ -2996,28 +3007,28 @@ function expectJsonObject(input, path) {
2996
3007
  }
2997
3008
  return input;
2998
3009
  }
2999
- function assertExactKeys2(object3, allowed, path) {
3010
+ function assertExactKeys2(object4, allowed, path) {
3000
3011
  const allowedSet = new Set(allowed);
3001
- for (const key of Object.keys(object3)) {
3012
+ for (const key of Object.keys(object4)) {
3002
3013
  if (!allowedSet.has(key)) {
3003
3014
  throw new SignedObjectSchemaError(`${path} has unknown field ${key}`);
3004
3015
  }
3005
3016
  }
3006
3017
  }
3007
- function requiredValue2(object3, key, path) {
3008
- if (!hasOwn2(object3, key)) {
3018
+ function requiredValue2(object4, key, path) {
3019
+ if (!hasOwn2(object4, key)) {
3009
3020
  throw new SignedObjectSchemaError(`${path}.${key} is required`);
3010
3021
  }
3011
- return object3[key];
3022
+ return object4[key];
3012
3023
  }
3013
- function requiredString2(object3, key, path) {
3014
- return requireStringType(requiredValue2(object3, key, path), `${path}.${key}`);
3024
+ function requiredString2(object4, key, path) {
3025
+ return requireStringType(requiredValue2(object4, key, path), `${path}.${key}`);
3015
3026
  }
3016
- function optionalString(object3, key, path) {
3017
- if (!hasOwn2(object3, key)) {
3027
+ function optionalString(object4, key, path) {
3028
+ if (!hasOwn2(object4, key)) {
3018
3029
  return;
3019
3030
  }
3020
- requireStringType(requiredValue2(object3, key, path), `${path}.${key}`);
3031
+ requireStringType(requiredValue2(object4, key, path), `${path}.${key}`);
3021
3032
  }
3022
3033
  function requireStringType(value, path) {
3023
3034
  if (typeof value !== "string" || value.length === 0) {
@@ -3025,16 +3036,16 @@ function requireStringType(value, path) {
3025
3036
  }
3026
3037
  return value;
3027
3038
  }
3028
- function optionalBoolean(object3, key, path) {
3029
- if (!hasOwn2(object3, key)) {
3039
+ function optionalBoolean(object4, key, path) {
3040
+ if (!hasOwn2(object4, key)) {
3030
3041
  return;
3031
3042
  }
3032
- if (typeof requiredValue2(object3, key, path) !== "boolean") {
3043
+ if (typeof requiredValue2(object4, key, path) !== "boolean") {
3033
3044
  throw new SignedObjectSchemaError(`${path}.${key} must be a boolean`);
3034
3045
  }
3035
3046
  }
3036
- function requiredInteger(object3, key, path, minimum) {
3037
- const value = requiredValue2(object3, key, path);
3047
+ function requiredInteger(object4, key, path, minimum) {
3048
+ const value = requiredValue2(object4, key, path);
3038
3049
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum) {
3039
3050
  throw new SignedObjectSchemaError(
3040
3051
  `${path}.${key} must be an integer >= ${minimum}`
@@ -3042,8 +3053,8 @@ function requiredInteger(object3, key, path, minimum) {
3042
3053
  }
3043
3054
  return value;
3044
3055
  }
3045
- function requiredArray2(object3, key, path, minimum) {
3046
- const value = requiredValue2(object3, key, path);
3056
+ function requiredArray2(object4, key, path, minimum) {
3057
+ const value = requiredValue2(object4, key, path);
3047
3058
  if (!Array.isArray(value) || value.length < minimum) {
3048
3059
  throw new SignedObjectSchemaError(
3049
3060
  `${path}.${key} must be an array with at least ${minimum} item(s)`
@@ -3051,22 +3062,22 @@ function requiredArray2(object3, key, path, minimum) {
3051
3062
  }
3052
3063
  return value;
3053
3064
  }
3054
- function requiredStringArray(object3, key, path, check) {
3055
- const values = requiredArray2(object3, key, path, 0);
3065
+ function requiredStringArray(object4, key, path, check) {
3066
+ const values = requiredArray2(object4, key, path, 0);
3056
3067
  for (let index = 0; index < values.length; index++) {
3057
3068
  const itemPath = `${path}.${key}[${index}]`;
3058
3069
  const value = requireStringType(values[index], itemPath);
3059
3070
  check?.(value, itemPath);
3060
3071
  }
3061
3072
  }
3062
- function requiredDateString(object3, key, path) {
3063
- assertRfc3339(requiredString2(object3, key, path), `${path}.${key}`);
3073
+ function requiredDateString(object4, key, path) {
3074
+ assertRfc3339(requiredString2(object4, key, path), `${path}.${key}`);
3064
3075
  }
3065
- function optionalDateString(object3, key, path) {
3066
- if (!hasOwn2(object3, key)) {
3076
+ function optionalDateString(object4, key, path) {
3077
+ if (!hasOwn2(object4, key)) {
3067
3078
  return;
3068
3079
  }
3069
- assertRfc3339(requiredString2(object3, key, path), `${path}.${key}`);
3080
+ assertRfc3339(requiredString2(object4, key, path), `${path}.${key}`);
3070
3081
  }
3071
3082
  function assertRfc3339(value, path) {
3072
3083
  const match = value.match(
@@ -3098,8 +3109,8 @@ function expectOneOf(actual, allowed, path) {
3098
3109
  }
3099
3110
  throw new SignedObjectSchemaError(`${path} has unsupported value ${actual}`);
3100
3111
  }
3101
- function hasOwn2(object3, key) {
3102
- return objectHasOwn3(object3, key);
3112
+ function hasOwn2(object4, key) {
3113
+ return objectHasOwn3(object4, key);
3103
3114
  }
3104
3115
  function assertSupportedSignatureSuite(suite) {
3105
3116
  if (suite !== ED25519_JCS_SIGNATURE_SUITE && suite !== EIP191_JCS_SIGNATURE_SUITE) {
@@ -3326,9 +3337,9 @@ function expectObject2(input, path, code3) {
3326
3337
  );
3327
3338
  }
3328
3339
  }
3329
- function assertExactKeys3(object3, allowed, path) {
3340
+ function assertExactKeys3(object4, allowed, path) {
3330
3341
  const allowedSet = new Set(allowed);
3331
- for (const key of Object.keys(object3)) {
3342
+ for (const key of Object.keys(object4)) {
3332
3343
  if (!allowedSet.has(key)) {
3333
3344
  throw new PolicyAuthoringError(
3334
3345
  "policy-authoring-unknown-key",
@@ -3337,17 +3348,17 @@ function assertExactKeys3(object3, allowed, path) {
3337
3348
  }
3338
3349
  }
3339
3350
  }
3340
- function requiredValue3(object3, key, path) {
3341
- if (!hasOwn3(object3, key)) {
3351
+ function requiredValue3(object4, key, path) {
3352
+ if (!hasOwn3(object4, key)) {
3342
3353
  throw new PolicyAuthoringError(
3343
3354
  "policy-authoring-malformed",
3344
3355
  `${path}.${key} is required`
3345
3356
  );
3346
3357
  }
3347
- return object3[key];
3358
+ return object4[key];
3348
3359
  }
3349
- function requiredString3(object3, key, path) {
3350
- const value = requiredValue3(object3, key, path);
3360
+ function requiredString3(object4, key, path) {
3361
+ const value = requiredValue3(object4, key, path);
3351
3362
  if (typeof value !== "string" || value.length === 0) {
3352
3363
  throw new PolicyAuthoringError(
3353
3364
  "policy-authoring-malformed",
@@ -3356,11 +3367,11 @@ function requiredString3(object3, key, path) {
3356
3367
  }
3357
3368
  return value;
3358
3369
  }
3359
- function fieldString(object3, key, path, code3) {
3360
- if (!hasOwn3(object3, key)) {
3370
+ function fieldString(object4, key, path, code3) {
3371
+ if (!hasOwn3(object4, key)) {
3361
3372
  throw new PolicyAuthoringError(code3, `${path}.${key} is required`);
3362
3373
  }
3363
- const value = object3[key];
3374
+ const value = object4[key];
3364
3375
  if (typeof value !== "string" || value.length === 0) {
3365
3376
  throw new PolicyAuthoringError(
3366
3377
  code3,
@@ -3398,8 +3409,8 @@ function parseStrictRfc3339(value, path) {
3398
3409
  var objectHasOwn4 = Object.hasOwn ?? Object.prototype.hasOwnProperty.call.bind(
3399
3410
  Object.prototype.hasOwnProperty
3400
3411
  );
3401
- function hasOwn3(object3, key) {
3402
- return objectHasOwn4(object3, key);
3412
+ function hasOwn3(object4, key) {
3413
+ return objectHasOwn4(object4, key);
3403
3414
  }
3404
3415
 
3405
3416
  // src/policy/unified.ts
@@ -3426,9 +3437,11 @@ var MAX_LEGACY_ENVELOPE_EXPIRES_AT = "2026-12-29T00:00:00Z";
3426
3437
  var LAST_V2_READ_AT = "2027-01-05T00:00:00Z";
3427
3438
  async function requestPolicyChallengeV3(input) {
3428
3439
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3429
- const response = await fetchFn(new URL("/share/v3/policy/challenges", input.nodeOrigin), {
3440
+ const policyRuntimePath = policyRuntimeBasePath(input.policyRuntimePath);
3441
+ const response = await fetchFn(new URL(`${policyRuntimePath}/challenges`, input.nodeOrigin), {
3430
3442
  method: "POST",
3431
3443
  redirect: "error",
3444
+ signal: input.signal,
3432
3445
  headers: { accept: "application/json", "content-type": "application/json" },
3433
3446
  body: JSON.stringify({ policyCid: input.policyCid, recipientDid: input.recipientDid, requestedCapabilities: input.requestedCapabilities.map(normalizeUnifiedPolicyCapability) })
3434
3447
  });
@@ -3438,6 +3451,13 @@ async function requestPolicyChallengeV3(input) {
3438
3451
  throw new Error("policy challenge binding is invalid");
3439
3452
  return value;
3440
3453
  }
3454
+ function policyRuntimeBasePath(value) {
3455
+ const path = value ?? "/policy/v3";
3456
+ if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
3457
+ throw new Error("policy runtime path is invalid");
3458
+ }
3459
+ return path;
3460
+ }
3441
3461
  function compactAttenuationForPolicyCapabilities(input) {
3442
3462
  const attenuation = {};
3443
3463
  for (const raw of input) {
@@ -3483,7 +3503,7 @@ function verifyPolicyRootStatusCheckpointV3(input) {
3483
3503
  }
3484
3504
  async function getPolicyRootStatusV3(input) {
3485
3505
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3486
- const response = await fetchFn(new URL(`/share/v3/policy/status/${encodeURIComponent(input.rootCid)}`, input.nodeOrigin), {
3506
+ const response = await fetchFn(new URL(`/policy/v3/status/${encodeURIComponent(input.rootCid)}`, input.nodeOrigin), {
3487
3507
  method: "GET",
3488
3508
  redirect: "error",
3489
3509
  headers: { accept: "application/json" }
@@ -3542,7 +3562,7 @@ async function renewPolicyRootStatusV3(input) {
3542
3562
  };
3543
3563
  const signature = await input.sign(sha2564(new TextEncoder().encode(ROOT_STATUS_RENEWAL_V1_DOMAIN + jcsCanonicalize(unsigned))));
3544
3564
  if (signature.length !== 64) throw new Error("policy root renewal signature must be Ed25519");
3545
- const response = await fetchFn(new URL("/share/v3/policy/status", input.nodeOrigin), {
3565
+ const response = await fetchFn(new URL("/policy/v3/status", input.nodeOrigin), {
3546
3566
  method: "POST",
3547
3567
  redirect: "error",
3548
3568
  headers: { accept: "application/json", "content-type": "application/json" },
@@ -3598,6 +3618,11 @@ var POLICY_SESSION_FACT_KEYS = [
3598
3618
  "issuanceAuditDigestHex",
3599
3619
  "remainingRedelegationDepth"
3600
3620
  ];
3621
+ var POLICY_SESSION_V4_AUDIT_FACT_KEYS = [
3622
+ "credentialIdAuditDigestHex",
3623
+ "presentationJtiAuditDigestHex"
3624
+ ];
3625
+ var LOWER_SHA256_HEX = /^[0-9a-f]{64}$/;
3601
3626
  var KV_ACTIONS = /* @__PURE__ */ new Set([
3602
3627
  "tinycloud.kv/get",
3603
3628
  "tinycloud.kv/list",
@@ -3792,6 +3817,133 @@ function contentSourceDigestHex(input) {
3792
3817
  )
3793
3818
  );
3794
3819
  }
3820
+ function exactObject(value, keys, label) {
3821
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3822
+ throw new Error(`${label} is invalid`);
3823
+ }
3824
+ const record2 = value;
3825
+ if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) {
3826
+ throw new Error(`${label} is invalid`);
3827
+ }
3828
+ return record2;
3829
+ }
3830
+ function didKeyEd25519PublicKey(value) {
3831
+ const principal = value.split("#", 1)[0];
3832
+ if (!principal.startsWith("did:key:")) {
3833
+ throw new Error("policy enforcer binding signer is invalid");
3834
+ }
3835
+ const material = base58btc.decode(principal.slice("did:key:".length));
3836
+ if (material.length !== 34 || material[0] !== 237 || material[1] !== 1) {
3837
+ throw new Error("policy enforcer binding signer is invalid");
3838
+ }
3839
+ return material.slice(2);
3840
+ }
3841
+ async function registerPolicyV3(input) {
3842
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
3843
+ const rootExpiresAt = new Date(input.rootExpiresAt);
3844
+ const canonicalRootExpiresAt = new Date(
3845
+ Math.floor(rootExpiresAt.getTime() / 1e3) * 1e3
3846
+ ).toISOString().replace(/\.000Z$/, "Z");
3847
+ if (!Number.isFinite(rootExpiresAt.getTime()) || canonicalRootExpiresAt !== input.rootExpiresAt || rootExpiresAt.getTime() <= Date.now()) {
3848
+ throw new Error("policy root expiry is invalid");
3849
+ }
3850
+ const bindingResponse = await fetchFn(
3851
+ new URL("/policy/v3/enforcer-bindings", input.nodeOrigin),
3852
+ {
3853
+ method: "POST",
3854
+ redirect: "error",
3855
+ signal: input.signal,
3856
+ headers: { accept: "application/json", "content-type": "application/json" },
3857
+ body: JSON.stringify({
3858
+ rootExpiresAt: input.rootExpiresAt,
3859
+ enforcerDid: input.enforcerDid
3860
+ })
3861
+ }
3862
+ );
3863
+ if (!bindingResponse.ok) {
3864
+ throw new Error(`policy enforcer binding rejected (${bindingResponse.status})`);
3865
+ }
3866
+ const binding = exactObject(
3867
+ await bindingResponse.json(),
3868
+ [
3869
+ "schema",
3870
+ "enforcerDid",
3871
+ "nodeAudience",
3872
+ "attestationBindingDigestHex",
3873
+ "issuedAt",
3874
+ "expiresAt",
3875
+ "signature"
3876
+ ],
3877
+ "policy enforcer binding"
3878
+ );
3879
+ const signature = exactObject(
3880
+ binding.signature,
3881
+ ["suite", "signerDid", "value"],
3882
+ "policy enforcer binding signature"
3883
+ );
3884
+ const expectedBindingDigestHex = hex(
3885
+ sha2564(
3886
+ new TextEncoder().encode(
3887
+ jcsCanonicalize({
3888
+ enforcerDid: input.enforcerDid,
3889
+ nodeAudience: input.expectedNodeAudience
3890
+ })
3891
+ )
3892
+ )
3893
+ );
3894
+ 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") {
3895
+ throw new Error("policy enforcer binding is invalid");
3896
+ }
3897
+ const { signature: _signature, ...unsignedBinding } = binding;
3898
+ const signedBinding = sha2564(
3899
+ new TextEncoder().encode(
3900
+ `xyz.tinycloud.policy/AttestedEnforcerBinding/v2\0${jcsCanonicalize(unsignedBinding)}`
3901
+ )
3902
+ );
3903
+ if (!ed255193.verify(
3904
+ decodeBase64Url2(signature.value),
3905
+ signedBinding,
3906
+ didKeyEd25519PublicKey(input.expectedNodeAudience),
3907
+ { zip215: false }
3908
+ )) {
3909
+ throw new Error("policy enforcer binding signature is invalid");
3910
+ }
3911
+ const response = await fetchFn(
3912
+ new URL("/policy/v3/policies", input.nodeOrigin),
3913
+ {
3914
+ method: "POST",
3915
+ redirect: "error",
3916
+ signal: input.signal,
3917
+ headers: { accept: "application/json", "content-type": "application/json" },
3918
+ body: JSON.stringify({
3919
+ policyCid: input.policyCid,
3920
+ policy: input.policy,
3921
+ policyRoot: input.policyRoot.authorization.replace(/^Bearer\s+/i, ""),
3922
+ enforcementRoot: input.enforcementRoot.authorization.replace(/^Bearer\s+/i, ""),
3923
+ contentSourceDigestHex: input.contentSourceDigestHex,
3924
+ nativeProjectionHashHex: input.nativeProjectionHashHex,
3925
+ attestedEnforcerBinding: binding
3926
+ })
3927
+ }
3928
+ );
3929
+ if (!response.ok) {
3930
+ throw new Error(`policy registration rejected (${response.status})`);
3931
+ }
3932
+ const receipt = exactObject(
3933
+ await response.json(),
3934
+ ["policyCid", "policyRootCid", "enforcementRootCid"],
3935
+ "policy registration response"
3936
+ );
3937
+ if (receipt.policyCid !== input.policyCid || receipt.policyRootCid !== input.policyRoot.cid || receipt.enforcementRootCid !== input.enforcementRoot.cid) {
3938
+ throw new Error("policy registration response binding is invalid");
3939
+ }
3940
+ return {
3941
+ policyCid: input.policyCid,
3942
+ policyRootCid: input.policyRoot.cid,
3943
+ enforcementRootCid: input.enforcementRoot.cid,
3944
+ attestedEnforcerBinding: binding
3945
+ };
3946
+ }
3795
3947
  function policyDigestHex(policy) {
3796
3948
  const canonicalPolicy = {
3797
3949
  ...policy,
@@ -3990,9 +4142,13 @@ function parsePolicySessionUcan(authorization, expectedProofs) {
3990
4142
  if (expectedProofs !== void 0 && (parsed.payload.prf[0] !== expectedProofs[0] || parsed.payload.prf[1] !== expectedProofs[1]))
3991
4143
  throw new Error("policy session UCAN ordered proofs do not match");
3992
4144
  const factKeys = Object.keys(fact);
3993
- 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)))
4145
+ 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);
4146
+ 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);
4147
+ if (!hasLegacyFactShape && !hasV4AuditFactShape)
3994
4148
  throw new Error("policy session UCAN fact is incomplete");
3995
- 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)
4149
+ if (hasV4AuditFactShape && (!LOWER_SHA256_HEX.test(fact.credentialIdAuditDigestHex) || !LOWER_SHA256_HEX.test(fact.presentationJtiAuditDigestHex)))
4150
+ throw new Error("policy session UCAN v4 audit facts are invalid");
4151
+ 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)
3996
4152
  throw new Error("policy session UCAN fact is invalid");
3997
4153
  return {
3998
4154
  authorization,
@@ -4051,6 +4207,8 @@ var POLICY_V2_DOMAIN = "xyz.tinycloud.policy/policy/v2\0";
4051
4207
  var POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE = "TinyCloudPolicyCredentialRequirement";
4052
4208
  var POLICY_PRESENTATION_V3_SCHEMA = "xyz.tinycloud.policy/presentation/v3";
4053
4209
  var POLICY_PRESENTATION_V3_DOMAIN = "xyz.tinycloud.policy/Presentation/v3\0";
4210
+ var POLICY_PRESENTATION_V4_SCHEMA = "xyz.tinycloud.policy/presentation/v4";
4211
+ var POLICY_PRESENTATION_V4_DOMAIN = "xyz.tinycloud.policy/Presentation/v4\0";
4054
4212
  function record(value, label) {
4055
4213
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4056
4214
  throw new Error(`${label} must be an object`);
@@ -4225,6 +4383,97 @@ function envelopeFromVerified(verified) {
4225
4383
  } = verified;
4226
4384
  return envelope;
4227
4385
  }
4386
+ function issuedCredentialEnvelopeFromVerified(verified) {
4387
+ return envelopeFromVerified(verified);
4388
+ }
4389
+ async function signPolicyCredentialPresentationV4(value, sign) {
4390
+ const item = record(value, "Policy presentation v4");
4391
+ exactKeys2(item, [
4392
+ "schema",
4393
+ "jti",
4394
+ "challengeId",
4395
+ "nonce",
4396
+ "policyCid",
4397
+ "nodeAudience",
4398
+ "holderDid",
4399
+ "subjectDid",
4400
+ "credentialDigest",
4401
+ "requirementDigest",
4402
+ "descriptorDigest",
4403
+ "requestedCapabilities",
4404
+ "issuedAt",
4405
+ "expiresAt"
4406
+ ], "Policy presentation v4");
4407
+ if (item.schema !== POLICY_PRESENTATION_V4_SCHEMA || item.holderDid !== item.subjectDid || !Array.isArray(item.requestedCapabilities)) throw new Error("Policy presentation v4 is invalid");
4408
+ const holderDid = nonEmpty(item.holderDid, "Policy presentation v4 holder DID");
4409
+ didKeyBytes(holderDid);
4410
+ const unsigned = Object.freeze({
4411
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4412
+ jti: nonEmpty(item.jti, "Policy presentation v4 jti"),
4413
+ challengeId: nonEmpty(item.challengeId, "Policy presentation v4 challenge"),
4414
+ nonce: nonEmpty(item.nonce, "Policy presentation v4 nonce"),
4415
+ policyCid: nonEmpty(item.policyCid, "Policy presentation v4 policy CID"),
4416
+ nodeAudience: nonEmpty(item.nodeAudience, "Policy presentation v4 audience"),
4417
+ holderDid,
4418
+ subjectDid: holderDid,
4419
+ credentialDigest: nonEmpty(item.credentialDigest, "Policy presentation v4 credential digest"),
4420
+ requirementDigest: nonEmpty(item.requirementDigest, "Policy presentation v4 requirement digest"),
4421
+ descriptorDigest: nonEmpty(item.descriptorDigest, "Policy presentation v4 descriptor digest"),
4422
+ // The low-level signer preserves the frozen wire bytes exactly. The
4423
+ // policy-aware builder performs capability normalization and containment.
4424
+ requestedCapabilities: Object.freeze(item.requestedCapabilities),
4425
+ issuedAt: date(item.issuedAt, "Policy presentation v4 issuedAt"),
4426
+ expiresAt: date(item.expiresAt, "Policy presentation v4 expiresAt")
4427
+ });
4428
+ const digest4 = sha2565(new TextEncoder().encode(
4429
+ POLICY_PRESENTATION_V4_DOMAIN + jcsCanonicalize(unsigned)
4430
+ ));
4431
+ const signature = await sign(digest4);
4432
+ if (signature.length !== 64 || !ed255194.verify(signature, digest4, didKeyBytes(holderDid))) throw new Error("policy presentation signature is invalid");
4433
+ return Object.freeze({
4434
+ ...unsigned,
4435
+ signature: Object.freeze({
4436
+ suite: "Ed25519",
4437
+ signerDid: holderDid,
4438
+ value: encodeBase64Url(signature)
4439
+ })
4440
+ });
4441
+ }
4442
+ async function buildPolicyCredentialPresentationV4(input) {
4443
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4444
+ const requirement = validateCredentialRequirement(input.requirement);
4445
+ const commitment = policy.credentialRequirement;
4446
+ const requirementDigest = await credentialRequirementDigest(requirement);
4447
+ const credentialDigest = await sha256Base64Url(input.credential.credential);
4448
+ 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");
4449
+ const requestedCapabilities = input.requestedCapabilities.map(normalizeUnifiedPolicyCapability);
4450
+ 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");
4451
+ const holderDid = input.credential.holderDid;
4452
+ 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");
4453
+ const now = input.now ?? /* @__PURE__ */ new Date();
4454
+ const expiresAtMs = Math.min(
4455
+ now.getTime() + 6e4,
4456
+ Date.parse(input.challenge.expiresAt),
4457
+ Date.parse(input.credential.expiresAt)
4458
+ );
4459
+ if (expiresAtMs <= now.getTime()) throw new Error("policy challenge is expired");
4460
+ return signPolicyCredentialPresentationV4(Object.freeze({
4461
+ schema: POLICY_PRESENTATION_V4_SCHEMA,
4462
+ jti: input.jti ?? encodeBase64Url(crypto.getRandomValues(new Uint8Array(16))),
4463
+ challengeId: input.challenge.challengeId,
4464
+ nonce: input.challenge.nonce,
4465
+ policyCid: input.policyCid,
4466
+ nodeAudience: input.challenge.nodeAudience,
4467
+ holderDid,
4468
+ subjectDid: holderDid,
4469
+ credentialDigest,
4470
+ requirementDigest,
4471
+ descriptorDigest: input.credential.descriptorDigest,
4472
+ requestedCapabilities: Object.freeze(requestedCapabilities),
4473
+ issuedAt: rfc3339Seconds(now),
4474
+ expiresAt: rfc3339Seconds(new Date(expiresAtMs))
4475
+ }), input.sign);
4476
+ }
4228
4477
  async function buildPolicyCredentialPresentationV3(input) {
4229
4478
  const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4230
4479
  const requirement = validateCredentialRequirement(input.requirement);
@@ -4311,10 +4560,12 @@ async function admitPolicyCredentialV3(input) {
4311
4560
  const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4312
4561
  const challenge = await requestPolicyChallengeV3({
4313
4562
  nodeOrigin,
4563
+ policyRuntimePath: input.policyRuntimePath,
4314
4564
  policyCid: input.policyCid,
4315
4565
  recipientDid: input.credential.holderDid,
4316
4566
  requestedCapabilities: input.requestedCapabilities,
4317
- fetch: fetchFn
4567
+ fetch: fetchFn,
4568
+ signal: input.signal
4318
4569
  });
4319
4570
  const presentation = await buildPolicyCredentialPresentationV3({
4320
4571
  ...input,
@@ -4322,10 +4573,11 @@ async function admitPolicyCredentialV3(input) {
4322
4573
  challenge
4323
4574
  });
4324
4575
  const response = await fetchFn(
4325
- new URL("/share/v3/policy/delegations", nodeOrigin),
4576
+ new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin),
4326
4577
  {
4327
4578
  method: "POST",
4328
4579
  redirect: "error",
4580
+ signal: input.signal,
4329
4581
  headers: { accept: "application/json", "content-type": "application/json" },
4330
4582
  body: JSON.stringify({
4331
4583
  policyCid: input.policyCid,
@@ -4354,8 +4606,74 @@ async function admitPolicyCredentialV3(input) {
4354
4606
  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)) {
4355
4607
  throw new Error("policy delegation signed binding is invalid");
4356
4608
  }
4609
+ input.signal?.throwIfAborted();
4357
4610
  return Object.freeze({ challenge, presentation, session });
4358
4611
  }
4612
+ function validatePolicyCredentialAdmissionV4Authority(session, expected) {
4613
+ 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");
4614
+ }
4615
+ async function admitPolicyCredentialV4(input) {
4616
+ const policy = validateUnifiedPolicyV2(input.policy, input.policyCid);
4617
+ const nodeOrigin = policyNodeOrigin(input.nodeOrigin);
4618
+ const fetchFn = input.fetch ?? globalThis.fetch.bind(globalThis);
4619
+ const challenge = await requestPolicyChallengeV3({
4620
+ nodeOrigin,
4621
+ policyRuntimePath: input.policyRuntimePath,
4622
+ policyCid: input.policyCid,
4623
+ recipientDid: input.credential.holderDid,
4624
+ requestedCapabilities: input.requestedCapabilities,
4625
+ fetch: fetchFn,
4626
+ signal: input.signal
4627
+ });
4628
+ const presentation = await buildPolicyCredentialPresentationV4({
4629
+ ...input,
4630
+ policy,
4631
+ challenge
4632
+ });
4633
+ const response = await fetchFn(new URL(`${policyRuntimeBasePath2(input.policyRuntimePath)}/delegations`, nodeOrigin), {
4634
+ method: "POST",
4635
+ redirect: "error",
4636
+ signal: input.signal,
4637
+ headers: { accept: "application/json", "content-type": "application/json" },
4638
+ body: JSON.stringify({
4639
+ policyCid: input.policyCid,
4640
+ challengeId: challenge.challengeId,
4641
+ nonce: challenge.nonce,
4642
+ requirement: validateCredentialRequirement(input.requirement),
4643
+ credential: envelopeFromVerified(input.credential),
4644
+ presentation
4645
+ })
4646
+ });
4647
+ if (!response.ok) throw new Error(`policy delegation rejected (${response.status})`);
4648
+ const value = record(await response.json(), "policy delegation");
4649
+ if (value.admitted !== true || typeof value.sessionCid !== "string" || typeof value.authorization !== "string") throw new Error("policy delegation response is invalid");
4650
+ const session = parsePolicySessionUcan(value.authorization, [
4651
+ input.policyRootCid,
4652
+ input.enforcementRootCid
4653
+ ]);
4654
+ const expectedAttenuation = compactAttenuationForPolicyCapabilities(input.requestedCapabilities);
4655
+ validatePolicyCredentialAdmissionV4Authority(session, {
4656
+ nodeAudience: input.expectedNodeAudience,
4657
+ enforcerDid: input.expectedEnforcerDid
4658
+ });
4659
+ 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");
4660
+ const importResponse = await fetchFn(new URL("/delegate", nodeOrigin), {
4661
+ method: "POST",
4662
+ redirect: "error",
4663
+ signal: input.signal,
4664
+ headers: { Authorization: session.authorization }
4665
+ });
4666
+ if (!importResponse.ok) throw new Error(`delegation import rejected (${importResponse.status})`);
4667
+ input.signal?.throwIfAborted();
4668
+ return Object.freeze({ challenge, presentation, session, delegationImported: true });
4669
+ }
4670
+ function policyRuntimeBasePath2(value) {
4671
+ const path = value ?? "/policy/v3";
4672
+ if (!/^\/policy(?:\/[a-z0-9-]+)*$/.test(path)) {
4673
+ throw new Error("policy runtime path is invalid");
4674
+ }
4675
+ return path;
4676
+ }
4359
4677
 
4360
4678
  // src/requester/index.ts
4361
4679
  var REQUESTER_NEAR_EXPIRY_SECONDS = 30;
@@ -7762,13 +8080,13 @@ function canonicalizeRecapCaveats(caveats) {
7762
8080
  }
7763
8081
  if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
7764
8082
  if (typeof value === "object") {
7765
- const object3 = value;
7766
- const prototype = Object.getPrototypeOf(object3);
8083
+ const object4 = value;
8084
+ const prototype = Object.getPrototypeOf(object4);
7767
8085
  if (prototype !== Object.prototype && prototype !== null) {
7768
8086
  throw new Error("ReCap caveats must contain only JSON values.");
7769
8087
  }
7770
- return `{${Object.keys(object3).sort().map(
7771
- (key) => `${JSON.stringify(key)}:${canonicalize(object3[key])}`
8088
+ return `{${Object.keys(object4).sort().map(
8089
+ (key) => `${JSON.stringify(key)}:${canonicalize(object4[key])}`
7772
8090
  ).join(",")}}`;
7773
8091
  }
7774
8092
  throw new Error("ReCap caveats must contain only JSON values.");
@@ -7856,7 +8174,6 @@ import { ed25519 as ed255195 } from "@noble/curves/ed25519";
7856
8174
  import { blake3 as blake33 } from "@noble/hashes/blake3";
7857
8175
  import { sha256 as sha2567 } from "@noble/hashes/sha256";
7858
8176
  var ENVELOPE_SCHEMA = "xyz.tinycloud.share/envelope";
7859
- var INLINE_PREFIX = "tc2:";
7860
8177
  var RAW_CID_CODEC = 85;
7861
8178
  var BLAKE3_256_CODE = 30;
7862
8179
  var DEFAULT_MAX_INLINE_BYTES = 256 * 1024;
@@ -7903,8 +8220,8 @@ function canonicalJson(value) {
7903
8220
  }
7904
8221
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
7905
8222
  if (typeof value === "object") {
7906
- const object3 = value;
7907
- return `{${Object.keys(object3).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object3[key])}`).join(",")}}`;
8223
+ const object4 = value;
8224
+ return `{${Object.keys(object4).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object4[key])}`).join(",")}}`;
7908
8225
  }
7909
8226
  throw new ShareEnvelopeError("invalid-envelope", "unsupported JSON value");
7910
8227
  }
@@ -8561,25 +8878,8 @@ function parsePublishedShareLink(link, options2 = {}) {
8561
8878
  }
8562
8879
  const origin2 = normalizeOrigin(url.origin);
8563
8880
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
8564
- if (url.search !== "" || url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8565
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
8566
- let payload;
8567
- try {
8568
- const bytes2 = base64UrlToBytes(url.hash.slice("#v=2&p=".length));
8569
- if (bytes2.length > (options2.maxInlineBytes ?? DEFAULT_MAX_INLINE_BYTES) * 2) throw new Error("inline payload too large");
8570
- const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
8571
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("inline payload shape");
8572
- payload = value;
8573
- } catch (error) {
8574
- throw new ShareEnvelopeError("invalid-link", error instanceof Error ? error.message : "invalid inline payload");
8575
- }
8576
- if (payload.v !== 2 || typeof payload.c !== "string" || typeof payload.cid !== "string" || payload.k !== void 0 && typeof payload.k !== "string" || Object.keys(payload).some((key3) => !["v", "c", "cid", "k"].includes(key3))) throw new ShareEnvelopeError("invalid-link", "inline payload fields are invalid");
8577
- const sealed = base64UrlToBytes(payload.c);
8578
- if (!verifyPublishedShareCid(payload.cid, sealed)) throw new ShareEnvelopeError("cid-mismatch", "inline payload CID does not match");
8579
- const key2 = payload.k === void 0 ? void 0 : base64UrlToBytes(payload.k);
8580
- if (key2 !== void 0 && key2.length !== 32) throw new ShareEnvelopeError("invalid-link", "inline payload key is invalid");
8581
- return { origin: origin2, cid: payload.cid, ...key2 === void 0 ? {} : { key: key2 }, sealed };
8582
- }
8881
+ if (url.username !== "" || url.password !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8882
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share link is not canonical");
8583
8883
  const match = /^\/s\/([a-z2-7]+)$/.exec(url.pathname);
8584
8884
  if (match === null || !url.hash.startsWith("#k=")) throw new ShareEnvelopeError("invalid-link", "published share link is invalid");
8585
8885
  const key = base64UrlToBytes(url.hash.slice(3));
@@ -8599,13 +8899,10 @@ function parseShareUrl(link, options2 = {}) {
8599
8899
  } catch {
8600
8900
  throw new ShareEnvelopeError("invalid-link", "share link must be an absolute URL");
8601
8901
  }
8602
- if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
8603
8902
  const origin2 = normalizeOrigin(url.origin);
8604
8903
  if (options2.trustedOrigins !== void 0 && !options2.trustedOrigins.map(normalizeOrigin).includes(origin2)) throw new ShareEnvelopeError("origin-mismatch", "share link origin is not trusted");
8605
- if (url.pathname === "/s/inline" && url.hash.startsWith("#v=2&p=")) {
8606
- const published = parsePublishedShareLink(link, options2);
8607
- return { kind: "inline", origin: origin2, cid: published.cid, key: published.key, url: `${url.origin}${url.pathname}`, protocol: "share-envelope-v2" };
8608
- }
8904
+ if (url.pathname === "/viewer") throw new ShareEnvelopeError("invalid-link", "retired viewer links are not accepted");
8905
+ if (url.search !== "") throw new ShareEnvelopeError("invalid-link", "share links must not contain query parameters");
8609
8906
  const match = url.pathname.match(/^\/s\/(i\/)?([^/]+)$/);
8610
8907
  if (!match) throw new ShareEnvelopeError("invalid-link", "share link path is invalid");
8611
8908
  const fragmentParams = url.hash === "" ? void 0 : new URLSearchParams(url.hash.slice(1));
@@ -8628,9 +8925,10 @@ function parseShareUrl(link, options2 = {}) {
8628
8925
  }
8629
8926
  function isV2ShareLink(link) {
8630
8927
  try {
8631
- return new URL(link).pathname.startsWith("/s/");
8928
+ const url = new URL(link);
8929
+ return url.search === "" && url.pathname.startsWith("/s/");
8632
8930
  } catch {
8633
- return link.startsWith(INLINE_PREFIX);
8931
+ return false;
8634
8932
  }
8635
8933
  }
8636
8934
  async function encryptShareBytes(bytes2, key) {
@@ -9086,8 +9384,8 @@ function canonicalJsonForProof(value) {
9086
9384
  }
9087
9385
  if (Array.isArray(value)) return `[${value.map(canonicalJsonForProof).join(",")}]`;
9088
9386
  if (typeof value === "object") {
9089
- const object3 = value;
9090
- return `{${Object.keys(object3).sort().map((key) => `${JSON.stringify(key)}:${canonicalJsonForProof(object3[key])}`).join(",")}}`;
9387
+ const object4 = value;
9388
+ return `{${Object.keys(object4).sort().map((key) => `${JSON.stringify(key)}:${canonicalJsonForProof(object4[key])}`).join(",")}}`;
9091
9389
  }
9092
9390
  throw new ShareAccessError("SHARE_SESSION_INVALID");
9093
9391
  }
@@ -9796,12 +10094,12 @@ var ShareRecipientClient = class {
9796
10094
  this.assertDetachedProof(request.proof);
9797
10095
  }
9798
10096
  parseSessionCapability(value) {
9799
- const object3 = this.requireObject(value, "session capability");
9800
- if (typeof object3.spaceId !== "string") throw new ShareAccessError("SHARE_SESSION_INVALID", "session capability space is invalid");
9801
- if (!Array.isArray(object3.actions) || object3.actions.some((action) => !["read", "list", "edit"].includes(action))) throw new ShareAccessError("SHARE_SESSION_INVALID", "session capability actions are invalid");
9802
- const resource = this.requireObject(object3.resource, "session capability resource");
10097
+ const object4 = this.requireObject(value, "session capability");
10098
+ if (typeof object4.spaceId !== "string") throw new ShareAccessError("SHARE_SESSION_INVALID", "session capability space is invalid");
10099
+ if (!Array.isArray(object4.actions) || object4.actions.some((action) => !["read", "list", "edit"].includes(action))) throw new ShareAccessError("SHARE_SESSION_INVALID", "session capability actions are invalid");
10100
+ const resource = this.requireObject(object4.resource, "session capability resource");
9803
10101
  if (resource.kind !== "exact" && resource.kind !== "prefix" || typeof resource.path !== "string") throw new ShareAccessError("SHARE_SESSION_INVALID", "session capability resource is invalid");
9804
- return { spaceId: object3.spaceId, actions: [...new Set(object3.actions)], resource: { kind: resource.kind, path: resource.path } };
10102
+ return { spaceId: object4.spaceId, actions: [...new Set(object4.actions)], resource: { kind: resource.kind, path: resource.path } };
9805
10103
  }
9806
10104
  assertProvidedPolicySession(session, envelope) {
9807
10105
  const sessionExpiry = dateValue(session.expiresAt);
@@ -11441,62 +11739,21 @@ function validateOwnerSharePolicyRegistrationBytes(bytesValue, expected) {
11441
11739
  return validateOwnerSharePolicyRegistration(value, expected);
11442
11740
  }
11443
11741
 
11444
- // src/delegations/share-delivery.ts
11742
+ // src/delegations/share-delivery-v3.ts
11445
11743
  import { ed25519 as ed255199 } from "@noble/curves/ed25519";
11446
- var SHARE_DELIVERY_AUTHORIZATION_DOMAIN = "xyz.tinycloud.share/delivery-authorization/v2\0";
11447
- function isCanonicalHttpsOrigin(value) {
11448
- let url;
11449
- try {
11450
- url = new URL(value);
11451
- } catch {
11452
- return false;
11453
- }
11454
- return url.protocol === "https:" && url.origin === value && url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
11455
- }
11456
- var AUTHORIZATION_KEYS = [
11457
- "type",
11458
- "version",
11459
- "jti",
11460
- "shareCid",
11461
- "shareId",
11462
- "registrationCid",
11463
- "delegationCid",
11464
- "enforcementDelegationCid",
11465
- "envelopeCid",
11466
- "policyCid",
11467
- "nodeAudience",
11468
- "targetOrigin",
11469
- "openCredentialsAudience",
11470
- "holder",
11471
- "recipientMatcher",
11472
- "deliveryEmail",
11473
- "shareUrl",
11474
- "returnOrigin",
11475
- "documentName",
11476
- "senderDid",
11477
- "senderTrust",
11478
- "contentSource",
11479
- "contentSourceDigest",
11480
- "shareExpiresAt",
11481
- "issuedAt",
11482
- "reportAbuseToken",
11483
- "actions",
11484
- "resource",
11485
- "authorityMaterialHandle",
11486
- "authorityMaterialDigest",
11487
- "requestBodyDigest",
11488
- "idempotencyKey",
11489
- "expiresAt",
11490
- "dataAuthority"
11491
- ];
11492
- function assertObject2(value, keys, label) {
11744
+ import { sha256 as sha25611 } from "@noble/hashes/sha256";
11745
+ var CREDENTIAL_INVITATION_REQUEST_DOMAIN = "xyz.tinycloud.credentials/invitation-request/v1\0";
11746
+ var DELIVERY_ADMISSION_DOMAIN = "xyz.tinycloud.policy/delivery-admission/v0\0";
11747
+ var REQUEST_KEYS = ["schema", "policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
11748
+ var ADMISSION_KEYS = ["schema", "policyId", "ownerDid", "recipient", "resource", "actions", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "senderKeyDid", "audience", "issuedAt", "expiresAt", "nonce", "signature"];
11749
+ function exactObject2(value, keys, label) {
11493
11750
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
11494
11751
  const record2 = value;
11495
11752
  if (Object.keys(record2).length !== keys.length || keys.some((key) => !Object.prototype.hasOwnProperty.call(record2, key))) throw new Error(`${label} has unknown or missing fields`);
11496
11753
  return record2;
11497
11754
  }
11498
- function fromB642(value) {
11499
- if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
11755
+ function decodeBase64Url3(value) {
11756
+ if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("share delivery value is not canonical base64url");
11500
11757
  if (typeof atob === "function") {
11501
11758
  const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
11502
11759
  return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
@@ -11504,42 +11761,45 @@ function fromB642(value) {
11504
11761
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64url"));
11505
11762
  throw new Error("base64url decoding is unavailable");
11506
11763
  }
11507
- function validateShareDeliveryAuthorizationBytes(bytesValue, expected) {
11508
- let value;
11764
+ function object3(value, label) {
11765
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} is invalid`);
11766
+ return value;
11767
+ }
11768
+ function didKeyBytes2(did) {
11769
+ if (!did.startsWith("did:key:z") || /[#/?]/.test(did.slice("did:key:".length))) throw new Error("v3 delivery admission signer is invalid");
11770
+ const decoded = base58btc.decode(did.slice("did:key:".length));
11771
+ if (decoded.length !== 34 || decoded[0] !== 237 || decoded[1] !== 1) throw new Error("v3 delivery admission signer is invalid");
11772
+ return decoded.slice(2);
11773
+ }
11774
+ function validateShareDeliveryAuthorizationV3Bytes(bytes2, expected) {
11775
+ let parsed;
11509
11776
  try {
11510
- value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytesValue));
11777
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
11511
11778
  } catch {
11512
- throw new Error("share delivery authorization response is not valid UTF-8 JSON");
11513
- }
11514
- const root = assertObject2(value, ["authorization", "proof"], "share delivery authorization response");
11515
- const authorization = assertObject2(root.authorization, AUTHORIZATION_KEYS, "share delivery authorization");
11516
- const proof = assertObject2(root.proof, ["alg", "kid", "signature"], "share delivery authorization proof");
11517
- const request = expected.request;
11518
- if (authorization.type !== "TinyCloudShareDeliveryAuthorization" || authorization.version !== 2 || authorization.dataAuthority !== false || authorization.jti !== request.jti || authorization.shareCid !== request.shareCid || authorization.shareId !== request.shareId || authorization.registrationCid !== request.registrationCid || authorization.delegationCid !== request.delegationCid || authorization.enforcementDelegationCid !== request.enforcementDelegationCid || authorization.envelopeCid !== request.envelopeCid || authorization.policyCid !== request.policyCid || authorization.deliveryEmail !== request.recipientEmail || authorization.shareUrl !== request.shareUrl || authorization.documentName !== request.documentName || authorization.requestBodyDigest !== request.requestBodyDigest || authorization.expiresAt !== request.expiresAt || authorization.idempotencyKey !== request.idempotencyKey || authorization.reportAbuseToken !== request.jti || authorization.authorityMaterialHandle !== request.registrationCid) {
11519
- throw new Error("share delivery authorization is not bound to the submitted request");
11520
- }
11521
- if (typeof authorization.issuedAt !== "string" || Date.parse(authorization.issuedAt) > Date.now() + 6e4) throw new Error("share delivery authorization issuedAt is invalid");
11522
- if (new Date(authorization.expiresAt).toISOString() !== authorization.expiresAt) throw new Error("share delivery authorization expiresAt is non-canonical");
11523
- if (typeof authorization.openCredentialsAudience !== "string" || !isCanonicalHttpsOrigin(authorization.openCredentialsAudience) || authorization.openCredentialsAudience !== expected.credentialsAudience || authorization.openCredentialsAudience === authorization.nodeAudience || authorization.openCredentialsAudience === authorization.returnOrigin) {
11524
- throw new Error("share delivery authorization openCredentialsAudience is missing or untrusted");
11525
- }
11526
- if (proof.alg !== "EdDSA" || typeof proof.kid !== "string" || typeof proof.signature !== "string") throw new Error("share delivery authorization proof is invalid");
11527
- if (proof.kid !== expected.nodeProof.kid) throw new Error("share delivery authorization proof key is not trusted");
11528
- const encodedKid = expected.nodeProof.publicKey;
11529
- const publicKey = encodedKid.length === 34 && encodedKid[0] === 237 && encodedKid[1] === 1 ? encodedKid.slice(2) : encodedKid;
11530
- if (publicKey.length !== 32) throw new Error("share delivery authorization proof key is invalid");
11531
- const signatureBytes = fromB642(proof.signature);
11532
- const signedBytes = new TextEncoder().encode(`${SHARE_DELIVERY_AUTHORIZATION_DOMAIN}${canonicalizeSignedObjectUnsigned(authorization)}`);
11533
- if (signatureBytes.length !== 64 || !ed255199.verify(signatureBytes, signedBytes, publicKey)) throw new Error("share delivery authorization proof signature is invalid");
11534
- return { authorization, proof };
11535
- }
11536
- function shareDeliveryTrustedKid(publicKey) {
11537
- if (publicKey.length !== 32) throw new Error("share delivery node key must be a raw 32-byte Ed25519 public key");
11538
- const prefixed = new Uint8Array(34);
11539
- prefixed.set([237, 1]);
11540
- prefixed.set(publicKey, 2);
11541
- const did = `did:key:${base58btc.encode(prefixed)}`;
11542
- return `${did}#${did.slice("did:key:".length)}`;
11779
+ throw new Error("v3 share delivery response is not valid UTF-8 JSON");
11780
+ }
11781
+ const root = exactObject2(parsed, ["request", "admission"], "v3 share delivery response");
11782
+ const request = exactObject2(root.request, REQUEST_KEYS, "credential invitation request");
11783
+ const admission = exactObject2(root.admission, ADMISSION_KEYS, "delivery admission");
11784
+ const signature = exactObject2(admission.signature, ["suite", "signerDid", "value"], "delivery admission signature");
11785
+ const envelope = object3(expected.request.envelope, "v3 share envelope");
11786
+ const policy = object3(envelope.policy, "v3 share policy");
11787
+ const requirement = object3(policy.credentialRequirement, "v3 credential requirement");
11788
+ const credentialType = object3(requirement.credentialType, "v3 credential type");
11789
+ const contentSource = object3(envelope.contentSource, "v3 content source");
11790
+ const envelopeSignature = object3(envelope.signature, "v3 envelope signature");
11791
+ const target = object3(envelope.target, "v3 target");
11792
+ const binding = object3(envelope.attestedEnforcerBinding, "v3 attested enforcer binding");
11793
+ const fields = ["policyId", "recipient", "resource", "credentialType", "returnLink", "envelopeRef", "label", "shareExpiresAt", "audience", "issuedAt", "expiresAt", "nonce"];
11794
+ if (request.schema !== "xyz.tinycloud.credentials/invitation-request/v1" || admission.schema !== "xyz.tinycloud.policy/delivery-admission/v0" || fields.some((field) => request[field] !== admission[field]) || request.policyId !== envelope.policyCid || request.recipient !== expected.request.recipientEmail || request.resource !== contentSource.kvResource || request.credentialType !== credentialType.id || request.credentialType !== "opencredentials.email/v1" || request.returnLink !== expected.request.shareUrl || request.envelopeRef !== expected.request.shareCid || request.label !== expected.request.documentName || request.shareExpiresAt !== envelope.expiry || request.audience !== expected.deliveryAudience || request.expiresAt !== expected.request.expiresAt || request.nonce !== expected.request.jti || admission.ownerDid !== envelopeSignature.signerDid || admission.senderKeyDid !== expected.senderKeyDid || canonicalizeSignedObjectUnsigned(admission.actions) !== canonicalizeSignedObjectUnsigned(["tinycloud.kv/get"]) || signature.suite !== "eddsa-ed25519-sha256-jcs-v1" || target.nodeAudience !== binding.nodeAudience || signature.signerDid !== binding.nodeAudience) throw new Error("v3 share delivery authorization is not bound to the submitted request");
11795
+ const issuedAt = Date.parse(String(request.issuedAt));
11796
+ const expiresAt = Date.parse(String(request.expiresAt));
11797
+ if (!Number.isFinite(issuedAt) || !Number.isFinite(expiresAt) || expiresAt <= issuedAt || expiresAt - issuedAt > 15 * 6e4) throw new Error("v3 share delivery authorization time is invalid");
11798
+ const admissionSignature = decodeBase64Url3(signature.value);
11799
+ const { signature: _signature, ...unsignedAdmission } = admission;
11800
+ const digest4 = sha25611(new TextEncoder().encode(`${DELIVERY_ADMISSION_DOMAIN}${canonicalizeSignedObjectUnsigned(unsignedAdmission)}`));
11801
+ if (admissionSignature.length !== 64 || !ed255199.verify(admissionSignature, digest4, didKeyBytes2(String(signature.signerDid)))) throw new Error("v3 share delivery admission signature is invalid");
11802
+ return { request, admission };
11543
11803
  }
11544
11804
 
11545
11805
  // src/delegations/sender-share-store.ts
@@ -11662,9 +11922,6 @@ import {
11662
11922
  publishPolicyShare,
11663
11923
  claimShare,
11664
11924
  resumeShareAuthorization,
11665
- isLegacyShareLink,
11666
- receiveLegacyShare,
11667
- migrateShare,
11668
11925
  MemoryEncryptedShareHistoryStorage,
11669
11926
  EncryptedSenderShareHistory
11670
11927
  } from "@tinycloud/share-sdk";
@@ -11672,8 +11929,7 @@ import {
11672
11929
  inspectShare,
11673
11930
  receiveShare,
11674
11931
  ShareReceiveError,
11675
- toShareErrorInfo,
11676
- verifyBearerEnvelope
11932
+ toShareErrorInfo
11677
11933
  } from "@tinycloud/share-sdk";
11678
11934
 
11679
11935
  // src/networkId.ts
@@ -14302,8 +14558,8 @@ function stableJson(value) {
14302
14558
  if (Array.isArray(value)) {
14303
14559
  return `[${value.map(stableJson).join(",")}]`;
14304
14560
  }
14305
- const object3 = value;
14306
- return `{${Object.keys(object3).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object3[key])}`).join(",")}}`;
14561
+ const object4 = value;
14562
+ return `{${Object.keys(object4).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object4[key])}`).join(",")}}`;
14307
14563
  }
14308
14564
  function indexedDelegationFromRow(row) {
14309
14565
  const [
@@ -14406,6 +14662,7 @@ import {
14406
14662
  SecretsService,
14407
14663
  SECRET_NAME_RE as SECRET_NAME_RE2,
14408
14664
  canonicalizeSecretScope,
14665
+ parseSecretCatalogKey,
14409
14666
  resolveSecretListPrefix,
14410
14667
  resolveSecretPath as resolveSecretPath2,
14411
14668
  EncryptionService,
@@ -15048,6 +15305,236 @@ function createOpenKeyCallbackSigningStrategy(options2) {
15048
15305
  }
15049
15306
  var defaultSignStrategy = { type: "auto-sign" };
15050
15307
 
15308
+ // src/authorization/manage-key.ts
15309
+ var TINYCLOUD_MANAGE_KEY_SCOPE = "tinycloud:manage-key";
15310
+ var TINYCLOUD_CANONICAL_IDENTITY_CLAIM = "https://tinycloud.xyz/canonical_identity";
15311
+ function requestTinyCloudManageKeyScope(scopes = []) {
15312
+ const requested = typeof scopes === "string" ? scopes.split(/\s+/u) : scopes;
15313
+ return [
15314
+ ...new Set([...requested, TINYCLOUD_MANAGE_KEY_SCOPE].filter(Boolean))
15315
+ ].sort().join(" ");
15316
+ }
15317
+ function hasTinyCloudManageKeyScope(scopes) {
15318
+ const values = Array.isArray(scopes) ? scopes : typeof scopes === "string" ? scopes.split(/\s+/u) : [];
15319
+ return values.includes(TINYCLOUD_MANAGE_KEY_SCOPE);
15320
+ }
15321
+ var OpenKeyManageKeyError = class extends Error {
15322
+ constructor(code3, message, approvalUrl, retryable = false) {
15323
+ super(message);
15324
+ this.code = code3;
15325
+ this.approvalUrl = approvalUrl;
15326
+ this.retryable = retryable;
15327
+ this.name = "OpenKeyManageKeyError";
15328
+ }
15329
+ };
15330
+ function isRecord2(value) {
15331
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15332
+ }
15333
+ function rejected(message) {
15334
+ return new OpenKeyManageKeyError("MESSAGE_REJECTED", message);
15335
+ }
15336
+ function identityMismatch(message) {
15337
+ return new OpenKeyManageKeyError("IDENTITY_MISMATCH", message);
15338
+ }
15339
+ function parseCanonicalTinyCloudIdentity(value) {
15340
+ if (!isRecord2(value))
15341
+ throw rejected("Canonical TinyCloud identity is missing");
15342
+ const expectedKeys = [
15343
+ "address",
15344
+ "chainId",
15345
+ "did",
15346
+ "keyId",
15347
+ "spaceId",
15348
+ "version"
15349
+ ];
15350
+ const actualKeys = Object.keys(value).sort();
15351
+ if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
15352
+ throw rejected("Canonical TinyCloud identity has an invalid shape");
15353
+ }
15354
+ if (value.version !== "v1")
15355
+ throw rejected("Canonical TinyCloud identity version is unsupported");
15356
+ if (typeof value.keyId !== "string" || value.keyId.trim() === "") {
15357
+ throw rejected("Canonical TinyCloud identity key ID is invalid");
15358
+ }
15359
+ if (typeof value.address !== "string") {
15360
+ throw rejected("Canonical TinyCloud identity address is invalid");
15361
+ }
15362
+ let address;
15363
+ try {
15364
+ address = canonicalizeAddress(value.address);
15365
+ } catch {
15366
+ throw rejected("Canonical TinyCloud identity address is invalid");
15367
+ }
15368
+ if (address !== value.address) {
15369
+ throw rejected(
15370
+ "Canonical TinyCloud identity address must use EIP-55 checksum casing"
15371
+ );
15372
+ }
15373
+ if (typeof value.chainId !== "number" || !Number.isSafeInteger(value.chainId) || value.chainId <= 0) {
15374
+ throw rejected("Canonical TinyCloud identity chain ID is invalid");
15375
+ }
15376
+ const did = pkhDid(address, value.chainId);
15377
+ const spacePrefix = `tinycloud:pkh:eip155:${value.chainId}:${address}:`;
15378
+ if (typeof value.spaceId !== "string" || !value.spaceId.startsWith(spacePrefix)) {
15379
+ throw rejected("Canonical TinyCloud identity space is invalid");
15380
+ }
15381
+ const spaceName = value.spaceId.slice(spacePrefix.length);
15382
+ let spaceId;
15383
+ try {
15384
+ spaceId = makePkhSpaceId(address, value.chainId, spaceName);
15385
+ } catch {
15386
+ throw rejected("Canonical TinyCloud identity space is invalid");
15387
+ }
15388
+ if (value.did !== did || value.spaceId !== spaceId) {
15389
+ throw rejected("Canonical TinyCloud identity does not match its address");
15390
+ }
15391
+ return {
15392
+ version: "v1",
15393
+ keyId: value.keyId,
15394
+ address,
15395
+ chainId: value.chainId,
15396
+ did,
15397
+ spaceId
15398
+ };
15399
+ }
15400
+ function parseCanonicalTinyCloudIdentityClaims(claims) {
15401
+ if (!isRecord2(claims)) throw rejected("OAuth claims are missing");
15402
+ return parseCanonicalTinyCloudIdentity(
15403
+ claims[TINYCLOUD_CANONICAL_IDENTITY_CLAIM]
15404
+ );
15405
+ }
15406
+ function identitiesEqual(left, right) {
15407
+ return left.version === right.version && left.keyId === right.keyId && left.address === right.address && left.chainId === right.chainId && left.did === right.did && left.spaceId === right.spaceId;
15408
+ }
15409
+ function errorForResponse(body, status) {
15410
+ const message = body?.reason ?? body?.error ?? `OpenKey manage-key signing failed with HTTP ${status}`;
15411
+ switch (body?.code ?? body?.error) {
15412
+ case "missing_scope":
15413
+ case "consent_required":
15414
+ return new OpenKeyManageKeyError("CONSENT_REQUIRED", message, body?.approvalUrl);
15415
+ case "client_disabled":
15416
+ case "signing_disabled":
15417
+ case "grant_disabled":
15418
+ return new OpenKeyManageKeyError("GRANT_DISABLED", message, body?.approvalUrl);
15419
+ case "user_exclusive":
15420
+ return new OpenKeyManageKeyError("USER_EXCLUSIVE", message, body?.approvalUrl);
15421
+ case "token_expired":
15422
+ case "token_too_old":
15423
+ case "invalid_token":
15424
+ case "expired_token":
15425
+ return new OpenKeyManageKeyError("TOKEN_EXPIRED", message, body?.approvalUrl);
15426
+ case "canonical_key_unavailable":
15427
+ case "signer_failed":
15428
+ return new OpenKeyManageKeyError(
15429
+ "SIGNER_UNAVAILABLE",
15430
+ "The canonical key signer is temporarily unavailable",
15431
+ void 0,
15432
+ true
15433
+ );
15434
+ default:
15435
+ return new OpenKeyManageKeyError(
15436
+ status === 401 ? "TOKEN_EXPIRED" : "MESSAGE_REJECTED",
15437
+ message,
15438
+ body?.approvalUrl
15439
+ );
15440
+ }
15441
+ }
15442
+ async function resolveToken(token) {
15443
+ const value = typeof token === "function" ? await token() : token;
15444
+ if (typeof value !== "string" || value.trim() === "") {
15445
+ throw new OpenKeyManageKeyError(
15446
+ "CONSENT_REQUIRED",
15447
+ "A manage-key OAuth bearer token is required"
15448
+ );
15449
+ }
15450
+ return value;
15451
+ }
15452
+ function normalizeEndpoint(value) {
15453
+ let endpoint;
15454
+ try {
15455
+ endpoint = new URL(value);
15456
+ } catch {
15457
+ throw rejected("OpenKey manage-key signing endpoint must be an absolute URL");
15458
+ }
15459
+ const rawHttpAuthority = value.match(/^http:\/\/([^/?#]*)/iu)?.[1];
15460
+ const loopback = rawHttpAuthority !== void 0 && /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/u.test(rawHttpAuthority);
15461
+ if (endpoint.protocol !== "https:" && !(endpoint.protocol === "http:" && loopback) || endpoint.username !== "" || endpoint.password !== "" || endpoint.search !== "" || endpoint.hash !== "") {
15462
+ throw rejected("OpenKey manage-key signing endpoint must use HTTPS or exact loopback HTTP");
15463
+ }
15464
+ return endpoint.href;
15465
+ }
15466
+ function assertSignInRequest(request, identity3) {
15467
+ if (request.type !== "siwe" || request.purpose !== "sign-in" || request.chainId !== identity3.chainId || request.address !== identity3.address || typeof request.message !== "string" || request.message.length === 0) {
15468
+ throw rejected(
15469
+ "Manage-key signing only permits the canonical sign-in SIWE request"
15470
+ );
15471
+ }
15472
+ }
15473
+ function createOpenKeyManageKeySigningStrategy(options2) {
15474
+ const identity3 = parseCanonicalTinyCloudIdentity(options2.identity);
15475
+ const endpoint = normalizeEndpoint(options2.endpoint);
15476
+ return {
15477
+ type: "callback",
15478
+ openKeyAutoSign: true,
15479
+ handler: async (request) => {
15480
+ assertSignInRequest(request, identity3);
15481
+ const fetchImpl = options2.fetch ?? globalThis.fetch;
15482
+ if (!fetchImpl)
15483
+ throw rejected("OpenKey manage-key signing requires fetch");
15484
+ if (!hasTinyCloudManageKeyScope(options2.scopes)) {
15485
+ throw new OpenKeyManageKeyError(
15486
+ "CONSENT_REQUIRED",
15487
+ "The OAuth bearer token was not granted tinycloud:manage-key"
15488
+ );
15489
+ }
15490
+ const token = await resolveToken(options2.token);
15491
+ const response = await fetchImpl(endpoint, {
15492
+ method: "POST",
15493
+ credentials: "omit",
15494
+ headers: {
15495
+ authorization: `Bearer ${token}`,
15496
+ "content-type": "application/json"
15497
+ },
15498
+ body: JSON.stringify({
15499
+ address: request.address,
15500
+ chainId: request.chainId,
15501
+ message: request.message,
15502
+ type: "siwe"
15503
+ })
15504
+ });
15505
+ let body;
15506
+ try {
15507
+ const value = await response.json();
15508
+ body = isRecord2(value) ? value : void 0;
15509
+ } catch {
15510
+ body = void 0;
15511
+ }
15512
+ if (!response.ok || body?.approved !== true)
15513
+ throw errorForResponse(body, response.status);
15514
+ if (typeof body.signature !== "string")
15515
+ throw rejected("OpenKey manage-key signer did not return a signature");
15516
+ const responseIdentity = parseCanonicalTinyCloudIdentity(
15517
+ body.canonicalIdentity
15518
+ );
15519
+ if (!identitiesEqual(identity3, responseIdentity)) {
15520
+ throw identityMismatch(
15521
+ "OpenKey manage-key signer returned a different canonical identity"
15522
+ );
15523
+ }
15524
+ if (!await verifyEip191MessageSignature(
15525
+ request.message,
15526
+ body.signature,
15527
+ identity3.address
15528
+ )) {
15529
+ throw rejected(
15530
+ "OpenKey manage-key signer returned an invalid signature"
15531
+ );
15532
+ }
15533
+ return { approved: true, signature: body.signature };
15534
+ }
15535
+ };
15536
+ }
15537
+
15051
15538
  // src/authorization/spaceCreation.ts
15052
15539
  var AutoApproveSpaceCreationHandler = class {
15053
15540
  /**
@@ -17086,6 +17573,7 @@ var DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
17086
17573
  var LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
17087
17574
  var LOCAL_LINK_PROBE_TIMEOUT_MS = 750;
17088
17575
  var LOCAL_LINK_HOST_SUFFIX = ".local.tinycloud.link";
17576
+ var OWNER_NODE_BINDING_TIMEOUT_MS = 5e3;
17089
17577
  function createInMemoryLocalNodeIdentityStore() {
17090
17578
  const pins = /* @__PURE__ */ new Map();
17091
17579
  return {
@@ -17218,21 +17706,80 @@ async function verifyLocationRecord(input) {
17218
17706
  }
17219
17707
  return false;
17220
17708
  }
17221
- async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
17709
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch, signal) {
17222
17710
  const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
17223
- const response = await fetchFn(url);
17711
+ const response = await fetchFn(url, {
17712
+ redirect: "error",
17713
+ headers: { accept: "application/json" },
17714
+ ...signal === void 0 ? {} : { signal }
17715
+ });
17224
17716
  if (response.status === 404) {
17225
17717
  return null;
17226
17718
  }
17227
17719
  if (!response.ok) {
17228
17720
  throw new Error(`location registry returned HTTP ${response.status}`);
17229
17721
  }
17230
- const body = await response.json();
17722
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
17723
+ let body;
17724
+ try {
17725
+ body = JSON.parse(text2);
17726
+ } catch {
17727
+ throw new LocationRecordValidationError("registry response is not JSON");
17728
+ }
17231
17729
  if (body.record === void 0) {
17232
17730
  throw new LocationRecordValidationError("registry response missing record");
17233
17731
  }
17234
17732
  return validateLocationRecord(body.record);
17235
17733
  }
17734
+ async function publishLocationRecord(options2) {
17735
+ validateSubject(options2.subject);
17736
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
17737
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
17738
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
17739
+ const current = await fetchLocationRecord(registryUrl, options2.subject, fetchFn);
17740
+ if (current !== null) {
17741
+ if (current.subject !== options2.subject || !await verifyLocationRecord(current)) {
17742
+ throw new LocationRecordValidationError("existing location record signature is invalid");
17743
+ }
17744
+ if (publishedHttpOrigins(current).includes(nodeOrigin)) {
17745
+ return current;
17746
+ }
17747
+ }
17748
+ const record2 = await signLocationRecord({
17749
+ version: 1,
17750
+ subject: options2.subject,
17751
+ multiaddrs: [...current?.multiaddrs ?? [], httpUrlToMultiaddr(nodeOrigin)],
17752
+ updated_at: (options2.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
17753
+ sequence: (current?.sequence ?? -1) + 1
17754
+ }, options2.signer);
17755
+ const response = await fetchFn(
17756
+ `${registryUrl}/v1/locations/${encodeURIComponent(options2.subject)}`,
17757
+ {
17758
+ method: "PUT",
17759
+ redirect: "error",
17760
+ headers: { accept: "application/json", "content-type": "application/json" },
17761
+ body: JSON.stringify(record2)
17762
+ }
17763
+ );
17764
+ if (!response.ok) {
17765
+ throw new Error(`location registry publish returned HTTP ${response.status}`);
17766
+ }
17767
+ const text2 = await boundedResponseText(response, "location registry", 64 * 1024);
17768
+ let body;
17769
+ try {
17770
+ body = JSON.parse(text2);
17771
+ } catch {
17772
+ throw new LocationRecordValidationError("registry publish response is not JSON");
17773
+ }
17774
+ if (body.record === void 0) {
17775
+ throw new LocationRecordValidationError("registry publish response missing record");
17776
+ }
17777
+ const published = validateLocationRecord(body.record);
17778
+ if (canonicalLocationPayload(locationPayloadForRecord(published)) !== canonicalLocationPayload(locationPayloadForRecord(record2)) || published.signature !== record2.signature || !await verifyLocationRecord(published)) {
17779
+ throw new LocationRecordValidationError("registry publish response does not match the signed record");
17780
+ }
17781
+ return published;
17782
+ }
17236
17783
  async function resolveCloudLocation(subject, options2 = {}) {
17237
17784
  validateSubject(subject);
17238
17785
  const verifyRecords = options2.verifyRecords ?? true;
@@ -17483,6 +18030,123 @@ function multiaddrToHttpUrl(input) {
17483
18030
  }
17484
18031
  return uri;
17485
18032
  }
18033
+ function exactHttpOrigin(value, label) {
18034
+ let url;
18035
+ try {
18036
+ url = new URL(value);
18037
+ } catch {
18038
+ throw new LocationRecordValidationError(`${label} must be an absolute URL origin`);
18039
+ }
18040
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
18041
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
18042
+ throw new LocationRecordValidationError(`${label} must use HTTPS (or loopback HTTP)`);
18043
+ }
18044
+ if (url.username !== "" || url.password !== "" || url.pathname !== "/" || url.search !== "" || url.hash !== "") {
18045
+ throw new LocationRecordValidationError(`${label} must be an origin without credentials, path, query, or fragment`);
18046
+ }
18047
+ return url.origin;
18048
+ }
18049
+ function publishedHttpOrigins(record2) {
18050
+ const origins = [];
18051
+ for (const address of record2.multiaddrs) {
18052
+ try {
18053
+ origins.push(exactHttpOrigin(multiaddrToHttpUrl(address), "published node URL"));
18054
+ } catch {
18055
+ }
18056
+ }
18057
+ return origins;
18058
+ }
18059
+ async function boundedResponseText(response, label, maxBytes) {
18060
+ const declaredLength = response.headers.get("content-length");
18061
+ if (declaredLength !== null && (!/^\d+$/.test(declaredLength) || Number(declaredLength) > maxBytes)) {
18062
+ throw new LocationRecordValidationError(`${label} response is too large`);
18063
+ }
18064
+ if (response.body === null) return "";
18065
+ const reader = response.body.getReader();
18066
+ const chunks = [];
18067
+ let total = 0;
18068
+ try {
18069
+ while (true) {
18070
+ const { done, value } = await reader.read();
18071
+ if (done) break;
18072
+ if (value === void 0) continue;
18073
+ total += value.byteLength;
18074
+ if (total > maxBytes) {
18075
+ await reader.cancel().catch(() => void 0);
18076
+ throw new LocationRecordValidationError(`${label} response is too large`);
18077
+ }
18078
+ chunks.push(value);
18079
+ }
18080
+ } finally {
18081
+ reader.releaseLock();
18082
+ }
18083
+ const bytes2 = new Uint8Array(total);
18084
+ let offset = 0;
18085
+ for (const chunk of chunks) {
18086
+ bytes2.set(chunk, offset);
18087
+ offset += chunk.byteLength;
18088
+ }
18089
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes2);
18090
+ }
18091
+ function boundedSignal(parent, timeoutMs) {
18092
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
18093
+ throw new LocationRecordValidationError("owner-node binding timeout must be positive");
18094
+ }
18095
+ const controller = new AbortController();
18096
+ const abortFromParent = () => controller.abort(parent?.reason);
18097
+ if (parent?.aborted) abortFromParent();
18098
+ else parent?.addEventListener("abort", abortFromParent, { once: true });
18099
+ const timer = setTimeout(() => controller.abort(new DOMException("owner-node binding timed out", "TimeoutError")), timeoutMs);
18100
+ return {
18101
+ signal: controller.signal,
18102
+ dispose() {
18103
+ clearTimeout(timer);
18104
+ parent?.removeEventListener("abort", abortFromParent);
18105
+ }
18106
+ };
18107
+ }
18108
+ async function verifyOwnerNodeBinding(options2) {
18109
+ validateSubject(options2.ownerDid);
18110
+ validateSubject(options2.nodeDid);
18111
+ const registryUrl = exactHttpOrigin(options2.registryUrl, "registry URL");
18112
+ const nodeOrigin = exactHttpOrigin(options2.nodeOrigin, "node origin");
18113
+ const fetchFn = options2.fetch ?? globalThis.fetch.bind(globalThis);
18114
+ const lookup = boundedSignal(options2.signal, options2.timeoutMs ?? OWNER_NODE_BINDING_TIMEOUT_MS);
18115
+ try {
18116
+ const record2 = await fetchLocationRecord(registryUrl, options2.ownerDid, fetchFn, lookup.signal);
18117
+ if (record2 === null) {
18118
+ throw new LocationRecordValidationError("owner has no published location record");
18119
+ }
18120
+ if (record2.subject !== options2.ownerDid || !await verifyLocationRecord(record2)) {
18121
+ throw new LocationRecordValidationError("owner location record signature is invalid");
18122
+ }
18123
+ const publishedOrigins = publishedHttpOrigins(record2);
18124
+ if (!publishedOrigins.includes(nodeOrigin)) {
18125
+ throw new LocationRecordValidationError("share target is not in the owner's signed location record");
18126
+ }
18127
+ const response = await fetchFn(new URL("/info", nodeOrigin), {
18128
+ redirect: "error",
18129
+ headers: { accept: "application/json" },
18130
+ signal: lookup.signal
18131
+ });
18132
+ if (!response.ok) {
18133
+ throw new LocationRecordValidationError(`target node /info returned HTTP ${response.status}`);
18134
+ }
18135
+ const text2 = await boundedResponseText(response, "target node /info", 16 * 1024);
18136
+ let body;
18137
+ try {
18138
+ body = JSON.parse(text2);
18139
+ } catch {
18140
+ throw new LocationRecordValidationError("target node /info response is not JSON");
18141
+ }
18142
+ if (body === null || typeof body !== "object" || Array.isArray(body) || body.nodeId !== options2.nodeDid) {
18143
+ throw new LocationRecordValidationError("target node identity does not match the share attestation");
18144
+ }
18145
+ return Object.freeze({ record: record2, nodeOrigin, nodeDid: options2.nodeDid });
18146
+ } finally {
18147
+ lookup.dispose();
18148
+ }
18149
+ }
17486
18150
  function httpUrlToMultiaddr(input) {
17487
18151
  const url = new URL(input);
17488
18152
  if (url.protocol !== "http:" && url.protocol !== "https:") {
@@ -17644,7 +18308,7 @@ async function verifyPkhSignature(did, payload, signature) {
17644
18308
  }
17645
18309
  function verifyDidKeySignature(did, payload, signature) {
17646
18310
  const publicKey = ed25519PublicKeyFromDidKey2(did);
17647
- const signatureBytes = decodeBase64Url3(signature);
18311
+ const signatureBytes = decodeBase64Url4(signature);
17648
18312
  if (signatureBytes.length !== 64) {
17649
18313
  throw new LocationRecordValidationError(
17650
18314
  "did:key signature must be a base64url Ed25519 signature"
@@ -17697,7 +18361,7 @@ function base64UrlEncode3(bytes2) {
17697
18361
  }
17698
18362
  return output;
17699
18363
  }
17700
- function decodeBase64Url3(value) {
18364
+ function decodeBase64Url4(value) {
17701
18365
  const alphabet2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
17702
18366
  const bytes2 = [];
17703
18367
  let buffer = 0;
@@ -17744,6 +18408,7 @@ export {
17744
18408
  CREDENTIAL_CONTRACT_VERSION,
17745
18409
  CREDENTIAL_ERROR_CODES,
17746
18410
  CREDENTIAL_FORMAT,
18411
+ CREDENTIAL_INVITATION_REQUEST_DOMAIN,
17747
18412
  CREDENTIAL_ACQUISITION_PROTOCOL as CREDENTIAL_PROTOCOL,
17748
18413
  CREDENTIAL_STEP_REGISTRY_VERSION,
17749
18414
  CapabilityKeyRegistry,
@@ -17767,6 +18432,7 @@ export {
17767
18432
  DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
17768
18433
  DEFAULT_TINYCLOUD_FALLBACK_HOST,
17769
18434
  DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
18435
+ DELIVERY_ADMISSION_DOMAIN,
17770
18436
  DUCKDB,
17771
18437
  DataVaultService,
17772
18438
  DatabaseHandle,
@@ -17778,6 +18444,7 @@ export {
17778
18444
  DuckDbService2 as DuckDbService,
17779
18445
  ED25519_JCS_SIGNATURE_SUITE,
17780
18446
  EIP191_JCS_SIGNATURE_SUITE,
18447
+ EMAIL_CREDENTIAL_MAX_AGE_SECONDS,
17781
18448
  ENCRYPTION,
17782
18449
  ENCRYPTION_MANIFEST_SPACE,
17783
18450
  ENCRYPTION_NETWORK_URN_PREFIX,
@@ -17816,6 +18483,8 @@ export {
17816
18483
  NETWORK_NAME_PATTERN,
17817
18484
  NetworkIdError,
17818
18485
  OPENKEY_ACTION_ID_SEPARATOR,
18486
+ OWNER_NODE_BINDING_TIMEOUT_MS,
18487
+ OpenKeyManageKeyError,
17819
18488
  POLICY_CAPABILITY_V1_DOMAIN,
17820
18489
  POLICY_CAPABILITY_V1_SCHEMA,
17821
18490
  POLICY_CREDENTIAL_REQUIREMENT_V1_TYPE,
@@ -17828,6 +18497,8 @@ export {
17828
18497
  POLICY_ENGINE_RESOLVE_REQUEST_SCHEMA,
17829
18498
  POLICY_PRESENTATION_V3_DOMAIN,
17830
18499
  POLICY_PRESENTATION_V3_SCHEMA,
18500
+ POLICY_PRESENTATION_V4_DOMAIN,
18501
+ POLICY_PRESENTATION_V4_SCHEMA,
17831
18502
  POLICY_SCHEMA,
17832
18503
  POLICY_SESSION_UCAN_V1_PROFILE,
17833
18504
  POLICY_STATUS_SCHEMA,
@@ -17852,7 +18523,6 @@ export {
17852
18523
  SECRET_RECORDS_SCHEMA,
17853
18524
  SERVICE_LONG_TO_SHORT,
17854
18525
  SERVICE_SHORT_TO_LONG,
17855
- SHARE_DELIVERY_AUTHORIZATION_DOMAIN,
17856
18526
  SPACE,
17857
18527
  SQL,
17858
18528
  SQLAction,
@@ -17886,7 +18556,9 @@ export {
17886
18556
  SpaceService,
17887
18557
  TINYCLOUD_ACCOUNT_SPACE_MANIFEST,
17888
18558
  TINYCLOUD_APPLICATIONS_SPACE_MANIFEST,
18559
+ TINYCLOUD_CANONICAL_IDENTITY_CLAIM,
17889
18560
  TINYCLOUD_DEFAULT_SPACE_MANIFEST,
18561
+ TINYCLOUD_MANAGE_KEY_SCOPE,
17890
18562
  TINYCLOUD_PUBLIC_SPACE_MANIFEST,
17891
18563
  TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
17892
18564
  TinyCloud,
@@ -17903,6 +18575,7 @@ export {
17903
18575
  activateSessionWithHost,
17904
18576
  addressStorageKey,
17905
18577
  admitPolicyCredentialV3,
18578
+ admitPolicyCredentialV4,
17906
18579
  applyPrefix,
17907
18580
  bootstrapEncryptionNetworkId,
17908
18581
  bootstrapSpaceId,
@@ -17913,6 +18586,7 @@ export {
17913
18586
  buildDecryptInvocation,
17914
18587
  buildNetworkId2 as buildNetworkId,
17915
18588
  buildPolicyCredentialPresentationV3,
18589
+ buildPolicyCredentialPresentationV4,
17916
18590
  buildSpaceUri,
17917
18591
  canonicalDigest,
17918
18592
  canonicalHashHex,
@@ -17943,10 +18617,12 @@ export {
17943
18617
  createAndSignSignedObject,
17944
18618
  createCapabilityKeyRegistry,
17945
18619
  createDelegatedShareKey,
18620
+ createEmailCredentialRequirement,
17946
18621
  createHolderBinding,
17947
18622
  createInMemoryLocalNodeIdentityStore,
17948
18623
  createLocalStorageLocalNodeIdentityStore,
17949
18624
  createOpenKeyCallbackSigningStrategy,
18625
+ createOpenKeyManageKeySigningStrategy,
17950
18626
  createPolicyEnforcementDelegation,
17951
18627
  createShareArtifact,
17952
18628
  createShareArtifactAsync,
@@ -18005,6 +18681,7 @@ export {
18005
18681
  generateRandomReceiverKey,
18006
18682
  getPolicyRootStatusV3,
18007
18683
  getTinyCloudDebugLogs,
18684
+ hasTinyCloudManageKeyScope,
18008
18685
  hexDecode,
18009
18686
  hexEncode,
18010
18687
  holderBindingCanonicalBytes,
@@ -18019,6 +18696,7 @@ export {
18019
18696
  isNetworkId,
18020
18697
  isPlausibleOpenKeyActionId,
18021
18698
  isV2ShareLink,
18699
+ issuedCredentialEnvelopeFromVerified,
18022
18700
  jcsCanonicalize,
18023
18701
  loadManifest,
18024
18702
  locationPayloadForRecord,
@@ -18037,6 +18715,8 @@ export {
18037
18715
  openWrappedKey,
18038
18716
  parseCanonicalNetworkId,
18039
18717
  parseCanonicalRecapResource,
18718
+ parseCanonicalTinyCloudIdentity,
18719
+ parseCanonicalTinyCloudIdentityClaims,
18040
18720
  parseCompactUcanAuthorization,
18041
18721
  parseExpiry,
18042
18722
  parseNetworkId2 as parseNetworkId,
@@ -18045,6 +18725,7 @@ export {
18045
18725
  parsePolicySessionUcan,
18046
18726
  parsePortableDelegation,
18047
18727
  parseRecapCapabilities,
18728
+ parseSecretCatalogKey,
18048
18729
  parseShareArtifact,
18049
18730
  parseShareUrl,
18050
18731
  parseSpaceUri,
@@ -18057,8 +18738,11 @@ export {
18057
18738
  principalDidEquals,
18058
18739
  projectUnifiedPolicyCapabilities,
18059
18740
  projectUnifiedPolicyCapability,
18741
+ publishLocationRecord,
18060
18742
  recapCaveatsEqual,
18743
+ registerPolicyV3,
18061
18744
  renewPolicyRootStatusV3,
18745
+ requestTinyCloudManageKeyScope,
18062
18746
  resolveCloudLocation,
18063
18747
  resolveManifest,
18064
18748
  resolveManifestKnowledgeRoot,
@@ -18074,10 +18758,10 @@ export {
18074
18758
  serviceError5 as serviceError,
18075
18759
  sha256Base64Url,
18076
18760
  shareCapabilityAllows,
18077
- shareDeliveryTrustedKid,
18078
18761
  signCompactUcanAuthorization,
18079
18762
  signCompactUcanRootAuthorization,
18080
18763
  signLocationRecord,
18764
+ signPolicyCredentialPresentationV4,
18081
18765
  signShareEnvelope,
18082
18766
  signedObjectIdFor,
18083
18767
  submitHostDelegation,
@@ -18110,13 +18794,14 @@ export {
18110
18794
  validatePolicyStatusSignedShape,
18111
18795
  validatePolicyStatusUnsigned,
18112
18796
  validatePolicyUnsigned,
18113
- validateShareDeliveryAuthorizationBytes,
18797
+ validateShareDeliveryAuthorizationV3Bytes,
18114
18798
  validateUnifiedPolicyV2,
18115
18799
  verifyDecryptResponse,
18116
18800
  verifyDidKeyEd25519Signature,
18117
18801
  verifyEip191MessageSignature,
18118
18802
  verifyIssuedCredential,
18119
18803
  verifyLocationRecord,
18804
+ verifyOwnerNodeBinding,
18120
18805
  verifyPolicy,
18121
18806
  verifyPolicyEngineRecord,
18122
18807
  verifyPolicyRootRevocationV3,