@tinycloud/sdk-services 3.2.6-beta.4 → 3.3.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{BaseService-1Ug6y7DC.d.cts → BaseService-Cc6Ueicm.d.cts} +1 -1
  2. package/dist/{BaseService-Bn07Vudy.d.ts → BaseService-DOdXd7sr.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +8 -0
  4. package/dist/encryption/index.cjs.map +1 -1
  5. package/dist/encryption/index.d.cts +2 -2
  6. package/dist/encryption/index.d.ts +2 -2
  7. package/dist/encryption/index.js +8 -0
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +345 -203
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +107 -7
  12. package/dist/index.d.ts +107 -7
  13. package/dist/index.js +344 -203
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  16. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  17. package/dist/kv/index.cjs +66 -21
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +4 -4
  20. package/dist/kv/index.d.ts +4 -4
  21. package/dist/kv/index.js +66 -21
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/grantPolicy.cjs +82 -0
  24. package/dist/kv/replication/grantPolicy.cjs.map +1 -0
  25. package/dist/kv/replication/grantPolicy.d.cts +19 -0
  26. package/dist/kv/replication/grantPolicy.d.ts +19 -0
  27. package/dist/kv/replication/grantPolicy.js +50 -0
  28. package/dist/kv/replication/grantPolicy.js.map +1 -0
  29. package/dist/kv/replication/index.cjs +571 -128
  30. package/dist/kv/replication/index.cjs.map +1 -1
  31. package/dist/kv/replication/index.d.cts +3 -3
  32. package/dist/kv/replication/index.d.ts +3 -3
  33. package/dist/kv/replication/index.js +571 -128
  34. package/dist/kv/replication/index.js.map +1 -1
  35. package/dist/{scope-BnyVPZ5E.d.ts → scope-B-m6cKH-.d.ts} +310 -7
  36. package/dist/{scope-CQ5kmHlt.d.cts → scope-B7oeKdLM.d.cts} +310 -7
  37. package/dist/sql/index.cjs +70 -36
  38. package/dist/sql/index.cjs.map +1 -1
  39. package/dist/sql/index.d.cts +2 -2
  40. package/dist/sql/index.d.ts +2 -2
  41. package/dist/sql/index.js +70 -36
  42. package/dist/sql/index.js.map +1 -1
  43. package/dist/{types-gmdUiorz.d.cts → types-BjH0ZtGy.d.cts} +6 -0
  44. package/dist/{types-gmdUiorz.d.ts → types-BjH0ZtGy.d.ts} +6 -0
  45. package/package.json +16 -2
@@ -79,6 +79,14 @@ var ErrorCodes = {
79
79
  ABORTED: "ABORTED",
80
80
  INVALID_INPUT: "INVALID_INPUT",
81
81
  PERMISSION_DENIED: "PERMISSION_DENIED",
82
+ // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
83
+ // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
84
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
85
+ NODE_BUSY: "NODE_BUSY",
86
+ /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
87
+ REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
88
+ /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
89
+ OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
82
90
  // KV-specific errors
83
91
  KV_NOT_FOUND: "KV_NOT_FOUND",
84
92
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
@@ -372,6 +380,35 @@ function emitEvent(emit, scheduler, event) {
372
380
  }
373
381
  }
374
382
 
383
+ // src/kv/replication/grantPolicyConstants.ts
384
+ var HOUR_MS = 60 * 60 * 1e3;
385
+ var DAY_MS = 24 * HOUR_MS;
386
+ var DEFAULT_GRANT_TTL_MS = DAY_MS;
387
+ var MAX_GRANT_TTL_MS = 7 * DAY_MS;
388
+ var CONSENT_WINDOW_MS = 7 * DAY_MS;
389
+ var RENEW_BELOW_FRACTION = 0.25;
390
+
391
+ // src/kv/replication/grantPolicy.ts
392
+ var EXPIRED = Number.NEGATIVE_INFINITY;
393
+ var consentDeadlineMs = (anchor) => Number.isFinite(anchor.at) ? anchor.at + CONSENT_WINDOW_MS : EXPIRED;
394
+ var clampConsentAnchor = (anchor, now) => {
395
+ if (anchor === null || !Number.isFinite(anchor.at) || !Number.isFinite(now)) return null;
396
+ return anchor.at > now ? { at: now, source: anchor.source } : anchor;
397
+ };
398
+ var mintRemainingMs = ({ now, grantTtlMs, consentDeadline }) => {
399
+ if (!Number.isFinite(now) || !Number.isFinite(grantTtlMs) || !Number.isFinite(consentDeadline)) return 0;
400
+ return Math.min(Math.min(grantTtlMs, MAX_GRANT_TTL_MS), consentDeadline - now);
401
+ };
402
+ var needsSilentRenewal = ({ now, installedAt, effectiveExpiresAt, consentDeadline }) => {
403
+ if (!Number.isFinite(now) || !Number.isFinite(installedAt) || !Number.isFinite(effectiveExpiresAt) || !Number.isFinite(consentDeadline)) {
404
+ return false;
405
+ }
406
+ if (!(now < consentDeadline)) return false;
407
+ const remaining = effectiveExpiresAt - now;
408
+ const lifetime = effectiveExpiresAt - installedAt;
409
+ return remaining < lifetime * RENEW_BELOW_FRACTION;
410
+ };
411
+
375
412
  // src/kv/replication/controller.ts
376
413
  var CLOSE_TIMEOUT_MS = 3e3;
377
414
  var STATUS_REFRESH_TIMEOUT_MS = CLOSE_TIMEOUT_MS;
@@ -399,6 +436,128 @@ function createKVReplication(deps) {
399
436
  let previous = deps.previous;
400
437
  let pinnedStateReported = false;
401
438
  const inProcessProof = /* @__PURE__ */ new Set();
439
+ let anchorUnavailable = false;
440
+ let consentAnchor = null;
441
+ try {
442
+ consentAnchor = clampConsentAnchor(authority.consentAnchor?.() ?? null, scheduler.now());
443
+ if (consentAnchor?.source === "first_install") consentAnchor = null;
444
+ } catch {
445
+ anchorUnavailable = true;
446
+ }
447
+ const grantPolicy = { grantTtlMs: options.grantTtlMs ?? DEFAULT_GRANT_TTL_MS, consentAnchor };
448
+ const quota = options.maxBytes === void 0 && options.minFreeBytes === void 0 ? void 0 : { maxBytes: options.maxBytes, minFreeBytes: options.minFreeBytes };
449
+ const policyDeadline = consentAnchor === null ? null : consentDeadlineMs(consentAnchor);
450
+ let scheduledConsentDeadline = null;
451
+ let cancelConsentTimer;
452
+ function durableDeadline() {
453
+ if (storage.kind !== "indexeddb" || !deps.stamp) return null;
454
+ try {
455
+ const deadline = storage.lifecycle?.consentDeadline(deps.stamp);
456
+ return typeof deadline === "number" && Number.isFinite(deadline) ? deadline : null;
457
+ } catch {
458
+ return null;
459
+ }
460
+ }
461
+ function mintDeadline() {
462
+ if (policyDeadline === null) return null;
463
+ if (storage.kind !== "indexeddb") return policyDeadline;
464
+ const durable = durableDeadline();
465
+ return durable === null ? null : Math.min(policyDeadline, durable);
466
+ }
467
+ function scheduleConsentDeadline() {
468
+ const deadline = durableDeadline();
469
+ if (deadline === null || consentDenyStarted || isClosed) return;
470
+ const now = scheduler.now();
471
+ if (!Number.isFinite(now)) return;
472
+ if (scheduledConsentDeadline === deadline && cancelConsentTimer) return;
473
+ scheduledConsentDeadline = deadline;
474
+ cancelConsentTimer?.();
475
+ cancelConsentTimer = scheduler.setTimeout(() => {
476
+ cancelConsentTimer = void 0;
477
+ checkDelivery();
478
+ }, Math.max(0, deadline - now));
479
+ }
480
+ const reportedDeadline = /* @__PURE__ */ new Set();
481
+ let consentDenyStarted = false;
482
+ function checkDelivery() {
483
+ const gate = storage.lifecycle;
484
+ if (!gate) return storage.kind === "indexeddb" || anchorUnavailable || deps.stamp !== void 0 ? { status: "gated", gate: "gate_unavailable" } : { status: "allow" };
485
+ if (!deps.stamp) return { status: "gated", gate: "not_established" };
486
+ if (consentDenyStarted) return { status: "gated", gate: "stamp_stale" };
487
+ try {
488
+ const decision = gate.checkDelivery(deps.stamp);
489
+ if (decision.status === "consent_expired") startConsentDenial();
490
+ if (decision.status !== "allow") return decision;
491
+ if (storage.kind === "indexeddb") {
492
+ const deadline = durableDeadline();
493
+ if (deadline === null) return { status: "gated", gate: "gate_unavailable" };
494
+ const now = scheduler.now();
495
+ if (!Number.isFinite(now)) return { status: "gated", gate: "gate_unavailable" };
496
+ if (now >= deadline) {
497
+ startConsentDenial();
498
+ return { status: "consent_expired", code: "CONSENT_EXPIRED" };
499
+ }
500
+ }
501
+ return anchorUnavailable ? { status: "gated", gate: "gate_unavailable" } : decision;
502
+ } catch {
503
+ return { status: "gated", gate: "gate_unavailable" };
504
+ }
505
+ }
506
+ function markConsentExpired() {
507
+ if (consentDenyStarted || isClosed || purgeStarted) return false;
508
+ cancelConsentTimer?.();
509
+ cancelConsentTimer = void 0;
510
+ consentDenyStarted = true;
511
+ for (const state of replicas.values()) {
512
+ state.renewalTimer?.();
513
+ state.renewalTimer = void 0;
514
+ state.mintAbort?.abort();
515
+ const id = state.handle?.replicaId;
516
+ if (id && !reportedDeadline.has(id)) {
517
+ reportedDeadline.add(id);
518
+ emit({ type: "replication.grant", replicaId: id, action: "consent_deadline_reached", code: "CONSENT_EXPIRED" });
519
+ }
520
+ }
521
+ return true;
522
+ }
523
+ function observeConsentExpired(stamp) {
524
+ if (!deps.stamp || stamp.incarnation !== deps.stamp.incarnation || stamp.lifecycleEpoch !== deps.stamp.lifecycleEpoch) return;
525
+ if (markConsentExpired()) emit({ type: "replication.lifecycle", action: "denied", reason: "consent_expired" });
526
+ }
527
+ function startConsentDenial() {
528
+ const gate = storage.lifecycle;
529
+ if (!gate || !markConsentExpired()) return;
530
+ void gate.deny({ reason: "consent_expired", principal: identity.principal }).then(
531
+ (outcome) => emit({ type: "replication.lifecycle", action: outcome.status === "denied" ? "denied" : outcome.status === "stale" ? "deny_stale" : outcome.status === "deleted" ? "database_deleted" : outcome.status === "marker" ? "marker_set" : "unguarded", reason: "consent_expired" }),
532
+ () => emit({ type: "replication.lifecycle", action: "deny_failed", reason: "consent_expired" })
533
+ );
534
+ }
535
+ function deliveryError(decision) {
536
+ return Object.assign(new Error(decision.status === "consent_expired" ? "Replica consent expired" : "Replica session is gated"), {
537
+ code: decision.status === "consent_expired" ? decision.code : "REPLICA_GATED",
538
+ ...decision.status === "gated" ? { gate: decision.gate } : {}
539
+ });
540
+ }
541
+ function deliveryReason(code) {
542
+ return code === "CONSENT_EXPIRED" || code === "GRANT_EXPIRED" ? "grant_expired" : code === "REPLICA_GATED" ? "replica_gated" : "replica_error";
543
+ }
544
+ function reportGrantExpiry(prefix) {
545
+ const state = replicas.get(prefix);
546
+ if (!state) return;
547
+ state.reason = "grant_expired";
548
+ state.lastError = "GRANT_EXPIRED";
549
+ const replicaId = state.handle?.replicaId;
550
+ if (!replicaId || state.reportedExpired) return;
551
+ state.reportedExpired = true;
552
+ emit({ type: "replication.grant", replicaId, action: "expired", code: "GRANT_EXPIRED" });
553
+ }
554
+ function reportGrantCap(state, handle, grant) {
555
+ if (!grant?.unconstrained || !handle.replicaId || typeof grant.effectiveExpiresAt !== "number" || !Number.isFinite(grant.effectiveExpiresAt) || !(grant.expiresAt === null || grant.effectiveExpiresAt < grant.expiresAt)) return;
556
+ const capKey = `${grant.cid}:${grant.effectiveExpiresAt}`;
557
+ if (state.reportedCappedCid === capKey) return;
558
+ state.reportedCappedCid = capKey;
559
+ emit({ type: "replication.grant", replicaId: handle.replicaId, action: "capped", effectiveExpiresAt: nowDate(grant.effectiveExpiresAt) });
560
+ }
402
561
  const configuredPrefix = (path) => options.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
403
562
  function recordRevocation(prefix, error) {
404
563
  if (errorCode(error) !== "GRANT_REVOKED") return;
@@ -422,7 +581,26 @@ function createKVReplication(deps) {
422
581
  markRootUnsupported(prefix);
423
582
  return Object.assign(new Error("This node doesn't support whole-space sync yet."), { code: "REPLICATION_ROOT_UNSUPPORTED" });
424
583
  }
584
+ function canReuseInstalled(grant, sessionOnly) {
585
+ if (!grant?.unconstrained) return false;
586
+ if (!deps.stamp) return true;
587
+ if (!sessionOnly && consentAnchor === null) return false;
588
+ const now = scheduler.now();
589
+ const deadline = storage.kind === "indexeddb" ? durableDeadline() : policyDeadline ?? grant.consentDeadline;
590
+ return grant.state === "active" && typeof grant.installedAt === "number" && Number.isFinite(grant.installedAt) && typeof grant.effectiveExpiresAt === "number" && Number.isFinite(grant.effectiveExpiresAt) && typeof grant.consentDeadline === "number" && Number.isFinite(grant.consentDeadline) && typeof deadline === "number" && Number.isFinite(deadline) && Number.isFinite(now) && now >= grant.installedAt && (grant.expiresAt === null || typeof grant.expiresAt === "number" && Number.isFinite(grant.expiresAt) && now < grant.expiresAt) && now < Math.min(grant.effectiveExpiresAt, grant.consentDeadline, deadline);
591
+ }
592
+ function sessionRefusal(prefix) {
593
+ try {
594
+ if (authority.sessionOnly !== true) return void 0;
595
+ const grant = authority.sessionGrant(prefix);
596
+ return "refused" in grant ? grant.refused : void 0;
597
+ } catch {
598
+ return "NOT_COVERED";
599
+ }
600
+ }
425
601
  async function open(prefix) {
602
+ const delivery = checkDelivery();
603
+ if (delivery.status !== "allow") throw deliveryError(delivery);
426
604
  if (isClosed || purgeStarted) throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
427
605
  let state = replicas.get(prefix);
428
606
  if (state?.handle) return state.handle;
@@ -443,46 +621,99 @@ function createKVReplication(deps) {
443
621
  await previous;
444
622
  previous = void 0;
445
623
  }
624
+ const delivery2 = checkDelivery();
625
+ if (delivery2.status !== "allow") throw deliveryError(delivery2);
626
+ const sessionOnly = authority.sessionOnly === true;
446
627
  const sessionGrant = authority.sessionGrant(prefix);
447
628
  const delegated = "refused" in sessionGrant ? void 0 : sessionGrant;
448
- const plan = authority.sessionOnly ? void 0 : authority.plan(prefix);
629
+ const plan = sessionOnly ? void 0 : authority.plan(prefix);
449
630
  let refusalCode;
450
631
  if ("refused" in sessionGrant) refusalCode = sessionGrant.refused;
451
632
  if (plan && "refused" in plan) refusalCode ?? (refusalCode = plan.refused);
633
+ if (deps.stamp && consentAnchor === null && !sessionOnly) refusalCode = "REPLICA_GATED";
452
634
  let device;
453
635
  if (delegated) {
454
636
  device = delegated.device;
455
637
  state.strategy = "session";
456
638
  }
457
- const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
639
+ const spec = {
640
+ identity,
641
+ space: session.space,
642
+ prefix,
643
+ allowSecrets: options.allowSecrets,
644
+ grantPolicy,
645
+ ...quota ? { quota } : {},
646
+ ...deps.stamp ? { stamp: deps.stamp } : {},
647
+ ...device ? { device } : {},
648
+ ...deps.fetch ? { fetch: deps.fetch } : {},
649
+ onPeerCommit: (info) => {
650
+ if (isClosed || purgeStarted || state.handle === void 0) return;
651
+ emit({
652
+ type: "replication.sync",
653
+ space: session.space,
654
+ replica: prefix,
655
+ trigger: "peer",
656
+ origin: "peer",
657
+ outcome: "ok",
658
+ durationMs: 0,
659
+ lagMs: null,
660
+ ...info.changes === void 0 ? {} : { changes: info.changes },
661
+ ...info.coverage === void 0 ? {} : { coverage: info.coverage }
662
+ });
663
+ }
664
+ };
458
665
  if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
459
666
  throw unsupportedRoot(prefix);
460
667
  }
461
668
  const handle = await storage.open(spec);
462
669
  state.handle = handle;
670
+ const afterOpen = checkDelivery();
671
+ if (afterOpen.status !== "allow") throw deliveryError(afterOpen);
463
672
  emit({ type: "replication.state", space: session.space, replica: prefix, state: "opened" });
464
- const runtimePlan = !authority.sessionOnly && plan && !("refused" in plan) ? plan : void 0;
465
- let installed = runtimePlan ? await handle.grant() : void 0;
466
- if (delegated) installed = await handle.installGrant(delegated.ucan);
467
- else if (runtimePlan) {
673
+ const runtimePlan = !sessionOnly && plan && !("refused" in plan) ? plan : void 0;
674
+ let installed = runtimePlan || delegated ? await handle.grant() : void 0;
675
+ if (installed && !canReuseInstalled(installed, sessionOnly)) {
676
+ if (installed.unconstrained) refusalCode = consentAnchor === null && !sessionOnly ? "REPLICA_GATED" : "GRANT_EXPIRED";
677
+ installed = void 0;
678
+ }
679
+ let mintedExpiryMs;
680
+ if (delegated && consentAnchor !== null) {
681
+ const beforeInstall = checkDelivery();
682
+ if (beforeInstall.status !== "allow") throw deliveryError(beforeInstall);
683
+ installed = await handle.installGrant(delegated.ucan);
684
+ } else if (runtimePlan) {
468
685
  if (!installed || !installed.unconstrained || installed.parentCid !== runtimePlan.parentCid || installed.expiresAt !== null && runtimePlan.expiresAt > installed.expiresAt + 3e5) {
469
- try {
470
- const mintAbort = new AbortController();
471
- state.mintAbort = mintAbort;
472
- const minted = await authority.mint(handle.deviceDid, prefix, mintAbort.signal);
473
- installed = await handle.installGrant(minted.ucan);
474
- state.strategy = "minted";
475
- } catch (error) {
476
- if (!installed?.unconstrained) throw error;
477
- } finally {
478
- state.mintAbort = void 0;
686
+ const deadline = mintDeadline();
687
+ const expiryMs = mintRemainingMs({ now: scheduler.now(), grantTtlMs: grantPolicy.grantTtlMs, consentDeadline: deadline ?? Number.NaN });
688
+ if (expiryMs > 0) {
689
+ try {
690
+ const mintAbort = new AbortController();
691
+ state.mintAbort = mintAbort;
692
+ const minted = await authority.mint(handle.deviceDid, prefix, mintAbort.signal, { expiryMs });
693
+ const beforeInstall = checkDelivery();
694
+ if (beforeInstall.status !== "allow") throw deliveryError(beforeInstall);
695
+ installed = await handle.installGrant(minted.ucan);
696
+ state.strategy = "minted";
697
+ mintedExpiryMs = expiryMs;
698
+ } catch (error) {
699
+ if (errorCode(error) === "CONSENT_EXPIRED" || errorCode(error) === "REPLICA_GATED" || !installed?.unconstrained) throw error;
700
+ } finally {
701
+ state.mintAbort = void 0;
702
+ }
479
703
  }
480
704
  }
481
705
  if (installed?.unconstrained) {
482
706
  state.strategy ?? (state.strategy = "installed");
483
707
  state.reason = void 0;
484
708
  }
709
+ } else if ((delegated || sessionOnly) && installed?.unconstrained) {
710
+ state.strategy = "installed";
485
711
  }
712
+ const beforeGrantEvent = checkDelivery();
713
+ if (beforeGrantEvent.status !== "allow") throw deliveryError(beforeGrantEvent);
714
+ reportGrantCap(state, handle, installed);
715
+ if (mintedExpiryMs !== void 0) state.reportedExpired = false;
716
+ if (mintedExpiryMs !== void 0 && handle.replicaId) emit({ type: "replication.grant", replicaId: handle.replicaId, action: "minted", expiryMs: mintedExpiryMs });
486
717
  if (!installed?.unconstrained) {
487
718
  state.reason = "grant_missing";
488
719
  state.lastError = refusalCode ?? "SESSION_LACKS_SYNC";
@@ -492,6 +723,7 @@ function createKVReplication(deps) {
492
723
  await handle.close();
493
724
  throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
494
725
  }
726
+ if (installed?.unconstrained) await maybeRenew(prefix, handle, installed, runtimePlan);
495
727
  if (mode === "background" && state.reason !== "grant_missing") {
496
728
  void syncPrefix(prefix, "start").catch(() => void 0);
497
729
  schedule(prefix);
@@ -502,12 +734,14 @@ function createKVReplication(deps) {
502
734
  return await state.opening;
503
735
  } catch (error) {
504
736
  const code = errorCode(error);
737
+ if (code === "CONSENT_EXPIRED") startConsentDenial();
505
738
  recordRevocation(prefix, error);
506
- if (code !== "GRANT_REVOKED") state.reason = "replica_unavailable";
739
+ if (code === "REPLICA_GATED" || code === "CONSENT_EXPIRED") state.reason = deliveryReason(code);
740
+ else if (code !== "GRANT_REVOKED") state.reason = "replica_unavailable";
507
741
  state.lastError = code;
508
742
  state.failures = (state.failures ?? 0) + 1;
509
743
  if (code === "RUNTIME_UNSUPPORTED") state.unsupported = true;
510
- else if (code !== "REPLICA_CLOSED" && code !== "REPLICATION_ROOT_UNSUPPORTED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
744
+ else if (code !== "REPLICA_CLOSED" && code !== "REPLICATION_ROOT_UNSUPPORTED" && code !== "REPLICA_GATED" && code !== "CONSENT_EXPIRED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
511
745
  state.handle?.close().catch(() => void 0);
512
746
  state.handle = void 0;
513
747
  throw error;
@@ -515,6 +749,96 @@ function createKVReplication(deps) {
515
749
  state.opening = void 0;
516
750
  }
517
751
  }
752
+ function scheduleRenewal(prefix, handle, grant, plan) {
753
+ const state = replicas.get(prefix);
754
+ state?.renewalTimer?.();
755
+ if (state) state.renewalTimer = void 0;
756
+ if (!state || isClosed || purgeStarted || plan.path !== "session" || !authority.canRenew) return;
757
+ const { installedAt, effectiveExpiresAt } = grant;
758
+ const deadline = mintDeadline();
759
+ if (typeof installedAt !== "number" || !Number.isFinite(installedAt) || typeof effectiveExpiresAt !== "number" || !Number.isFinite(effectiveExpiresAt) || !(effectiveExpiresAt > installedAt) || deadline === null) return;
760
+ const threshold = installedAt + (effectiveExpiresAt - installedAt) * (1 - RENEW_BELOW_FRACTION);
761
+ const now = scheduler.now();
762
+ if (!Number.isFinite(now) || !(threshold < deadline) || now > threshold) return;
763
+ state.renewalTimer = scheduler.setTimeout(() => {
764
+ state.renewalTimer = void 0;
765
+ void maybeRenew(prefix, handle).catch(() => void 0);
766
+ }, Math.max(0, Math.floor(threshold) + 1 - now));
767
+ }
768
+ async function maybeRenew(prefix, handle, knownGrant, knownPlan) {
769
+ const state = replicas.get(prefix);
770
+ if (!state || state.handle !== handle || isClosed || purgeStarted || !authority.canRenew) return;
771
+ if (state.renewing) return state.renewing;
772
+ const decision = checkDelivery();
773
+ if (decision.status !== "allow") return;
774
+ const grant = knownGrant ?? await handle.grant();
775
+ if (!grant?.unconstrained) return;
776
+ const { installedAt, effectiveExpiresAt } = grant;
777
+ const deadline = mintDeadline();
778
+ if (typeof installedAt !== "number" || !Number.isFinite(installedAt) || typeof effectiveExpiresAt !== "number" || !Number.isFinite(effectiveExpiresAt) || deadline === null) return;
779
+ const now = scheduler.now();
780
+ if (!needsSilentRenewal({ now, installedAt, effectiveExpiresAt, consentDeadline: deadline })) {
781
+ if (knownPlan) scheduleRenewal(prefix, handle, grant, knownPlan);
782
+ return;
783
+ }
784
+ let plan = knownPlan;
785
+ try {
786
+ if (!plan) {
787
+ const selected = authority.plan(prefix);
788
+ if ("refused" in selected) {
789
+ if (handle.replicaId) emit({ type: "replication.grant", replicaId: handle.replicaId, action: "renewal_refused", code: selected.refused });
790
+ return;
791
+ }
792
+ plan = selected;
793
+ }
794
+ } catch {
795
+ if (handle.replicaId) emit({ type: "replication.grant", replicaId: handle.replicaId, action: "renewal_deferred" });
796
+ return;
797
+ }
798
+ if (plan?.path !== "session") {
799
+ if (handle.replicaId && state.reportedRenewalUnqualifiedCid !== grant.cid) {
800
+ state.reportedRenewalUnqualifiedCid = grant.cid;
801
+ emit({ type: "replication.grant", replicaId: handle.replicaId, action: "renewal_deferred" });
802
+ }
803
+ return;
804
+ }
805
+ const expiryMs = mintRemainingMs({ now, grantTtlMs: grantPolicy.grantTtlMs, consentDeadline: deadline });
806
+ if (!(expiryMs > 0)) return;
807
+ state.renewalTimer?.();
808
+ state.renewalTimer = void 0;
809
+ const mintAbort = new AbortController();
810
+ state.mintAbort = mintAbort;
811
+ const job = Promise.resolve().then(async () => {
812
+ const minted = await authority.mint(handle.deviceDid, prefix, mintAbort.signal, { expiryMs, renewalOnly: true });
813
+ const beforeInstall = checkDelivery();
814
+ if (beforeInstall.status !== "allow") throw deliveryError(beforeInstall);
815
+ const installed = await handle.installGrant(minted.ucan);
816
+ const beforeEvent = checkDelivery();
817
+ if (beforeEvent.status !== "allow") throw deliveryError(beforeEvent);
818
+ reportGrantCap(state, handle, installed);
819
+ state.reportedExpired = false;
820
+ if (state.reason === "grant_expired") state.reason = void 0;
821
+ if (state.lastError === "GRANT_EXPIRED") state.lastError = void 0;
822
+ if (handle.replicaId) emit({ type: "replication.grant", replicaId: handle.replicaId, action: "renewed", expiryMs });
823
+ if (typeof installed.effectiveExpiresAt === "number" && installed.effectiveExpiresAt > effectiveExpiresAt) {
824
+ scheduleRenewal(prefix, handle, installed, plan);
825
+ }
826
+ });
827
+ state.renewing = job;
828
+ try {
829
+ await job;
830
+ } catch (error) {
831
+ const code = errorCode(error);
832
+ if (code === "CONSENT_EXPIRED" || code === "REPLICA_GATED") throw error;
833
+ if (!isClosed && handle.replicaId) {
834
+ const refused = typeof error === "object" && error !== null && "refused" in error && typeof error.refused === "string" ? error.refused : null;
835
+ emit({ type: "replication.grant", replicaId: handle.replicaId, action: refused ? "renewal_refused" : "renewal_deferred", code: refused ?? code });
836
+ }
837
+ } finally {
838
+ state.renewing = void 0;
839
+ if (state.mintAbort === mintAbort) state.mintAbort = void 0;
840
+ }
841
+ }
518
842
  function schedule(prefix) {
519
843
  const state = replicas.get(prefix);
520
844
  if (!state || mode !== "background" || isClosed || purgeStarted) return;
@@ -527,6 +851,8 @@ function createKVReplication(deps) {
527
851
  if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
528
852
  throw unsupportedRoot(prefix);
529
853
  }
854
+ const initialRefusal = sessionRefusal(prefix);
855
+ if (initialRefusal) throw Object.assign(new Error("Current session does not cover replica sync"), { code: initialRefusal });
530
856
  const handle = await open(prefix);
531
857
  const state = replicas.get(prefix);
532
858
  if (state.sync) return state.sync;
@@ -544,22 +870,36 @@ function createKVReplication(deps) {
544
870
  job = (async () => {
545
871
  try {
546
872
  const syncStartEpoch = (await pending.read()).committedEpoch;
873
+ const beforeSync = checkDelivery();
874
+ if (beforeSync.status !== "allow") throw deliveryError(beforeSync);
875
+ await maybeRenew(prefix, handle);
876
+ const afterRenewal = checkDelivery();
877
+ if (afterRenewal.status !== "allow") throw deliveryError(afterRenewal);
878
+ const refusal = sessionRefusal(prefix);
879
+ if (refusal) throw Object.assign(new Error("Current session does not cover replica sync"), { code: refusal });
547
880
  const result = await handle.sync({ signal: abort.signal, syncStartEpoch });
881
+ const afterSyncDelivery = checkDelivery();
882
+ if (afterSyncDelivery.status !== "allow") throw deliveryError(afterSyncDelivery);
548
883
  if (result.status === "busy") {
549
884
  emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: "busy", durationMs: scheduler.now() - started, lagMs: null });
550
885
  return result;
551
886
  }
552
887
  inProcessProof.add(prefix);
553
888
  const snapshot = await pending.read();
889
+ const beforePending = checkDelivery();
890
+ if (beforePending.status !== "allow") throw deliveryError(beforePending);
554
891
  const clearedCandidates = snapshot.records.some((r) => r.state === "committed" && r.epoch !== null && r.epoch <= syncStartEpoch && kvPrefixCovers(prefix, r.key));
555
892
  const cleared = clearedCandidates ? await pending.update((s) => afterSync(s, prefix, syncStartEpoch)) : 0;
893
+ const beforeResult = checkDelivery();
894
+ if (beforeResult.status !== "allow") throw deliveryError(beforeResult);
556
895
  emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: "ok", durationMs: scheduler.now() - started, lagMs: null, pendingCleared: cleared, pages: result.pages, changes: result.changes, deleted: result.deleted, fetched: result.fetched, contentMissing: result.contentMissing, coverage: result.coverage });
557
896
  return result;
558
897
  } catch (error) {
559
898
  const code = errorCode(error);
560
899
  state.lastError = code;
561
900
  recordRevocation(prefix, error);
562
- emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: abort.signal.aborted ? "aborted" : "error", class: code === "NETWORK_ERROR" || code === "TIMEOUT" ? "offline" : "node", code, durationMs: scheduler.now() - started, lagMs: null });
901
+ if (code === "CONSENT_EXPIRED") startConsentDenial();
902
+ emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: abort.signal.aborted ? "aborted" : "error", class: code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" ? "lifecycle" : code === "NETWORK_ERROR" || code === "TIMEOUT" ? "offline" : "node", code, durationMs: scheduler.now() - started, lagMs: null });
563
903
  throw error;
564
904
  } finally {
565
905
  signal?.removeEventListener("abort", abortFromCaller);
@@ -715,8 +1055,19 @@ function createKVReplication(deps) {
715
1055
  return { status: currentStatus, syncedBeforeRead: false };
716
1056
  }
717
1057
  function readEvent(op, path, prefix, source, reason, outcome, started, extra = {}) {
1058
+ if (prefix !== null && reason === "grant_expired" && extra.code !== "CONSENT_EXPIRED") reportGrantExpiry(prefix);
718
1059
  emit({ type: "replication.read", op, space: session.space, key: path, replica: prefix, source, reason, outcome, latencyMs: scheduler.now() - started, stalenessMs: null, coverage: null, authority: null, ...extra });
719
1060
  }
1061
+ async function networkRead(network, op, path, prefix, reason, started, extra = {}) {
1062
+ let outcome = "error";
1063
+ try {
1064
+ const result = await network();
1065
+ outcome = result.ok ? "found" : op === "get" && (reason === "pending_write" || reason === "not_covered") && result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error";
1066
+ return result;
1067
+ } finally {
1068
+ readEvent(op, path, prefix, "network", reason, outcome, started, extra);
1069
+ }
1070
+ }
720
1071
  async function verifyGet(r, local, reason, started, extra) {
721
1072
  const prefix = configuredPrefix(r.path) ?? null;
722
1073
  const localFound = local.ok;
@@ -728,6 +1079,16 @@ function createKVReplication(deps) {
728
1079
  readEvent("get", r.path, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
729
1080
  return abortedResult(r.signal);
730
1081
  }
1082
+ const delivery = checkDelivery();
1083
+ if (delivery.status !== "allow") {
1084
+ readEvent("get", r.path, prefix, "network", delivery.status === "consent_expired" ? "grant_expired" : "replica_gated", network.ok ? "found" : "error", started, { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" });
1085
+ return network;
1086
+ }
1087
+ const refusal = prefix === null ? void 0 : sessionRefusal(prefix);
1088
+ if (refusal) {
1089
+ readEvent("get", r.path, prefix, "network", "grant_missing", network.ok ? "found" : "error", started, { code: refusal });
1090
+ return network;
1091
+ }
731
1092
  if (!network.ok && network.error.code !== ErrorCodes.KV_NOT_FOUND) {
732
1093
  readEvent("get", r.path, prefix, "replica", reason, outcome, started, { ...extra, code: network.error.code, verify: "error" });
733
1094
  return local;
@@ -758,6 +1119,16 @@ function createKVReplication(deps) {
758
1119
  readEvent("get", r.path, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
759
1120
  return abortedResult(r.signal);
760
1121
  }
1122
+ const delivery = checkDelivery();
1123
+ if (delivery.status !== "allow") {
1124
+ readEvent("get", r.path, prefix, "network", delivery.status === "consent_expired" ? "grant_expired" : "replica_gated", "error", started, { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" });
1125
+ return err(wrapError("kv", error));
1126
+ }
1127
+ const refusal = prefix === null ? void 0 : sessionRefusal(prefix);
1128
+ if (refusal) {
1129
+ readEvent("get", r.path, prefix, "network", "grant_missing", "error", started, { code: refusal });
1130
+ return err(wrapError("kv", error));
1131
+ }
761
1132
  readEvent("get", r.path, prefix, "replica", reason, outcome, started, { ...extra, code: errorCode(error), verify: "error" });
762
1133
  return local;
763
1134
  }
@@ -770,6 +1141,16 @@ function createKVReplication(deps) {
770
1141
  readEvent("list", r.listPath, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
771
1142
  return abortedResult(r.signal);
772
1143
  }
1144
+ const delivery = checkDelivery();
1145
+ if (delivery.status !== "allow") {
1146
+ readEvent("list", r.listPath, prefix, "network", delivery.status === "consent_expired" ? "grant_expired" : "replica_gated", network.ok ? "found" : "error", started, { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" });
1147
+ return network;
1148
+ }
1149
+ const refusal = prefix === null ? void 0 : sessionRefusal(prefix);
1150
+ if (refusal) {
1151
+ readEvent("list", r.listPath, prefix, "network", "grant_missing", network.ok ? "found" : "error", started, { code: refusal });
1152
+ return network;
1153
+ }
773
1154
  if (!network.ok) {
774
1155
  readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, { ...extra, code: network.error.code, verify: "error" });
775
1156
  return ok(local);
@@ -800,6 +1181,16 @@ function createKVReplication(deps) {
800
1181
  readEvent("list", r.listPath, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
801
1182
  return abortedResult(r.signal);
802
1183
  }
1184
+ const delivery = checkDelivery();
1185
+ if (delivery.status !== "allow") {
1186
+ readEvent("list", r.listPath, prefix, "network", delivery.status === "consent_expired" ? "grant_expired" : "replica_gated", "error", started, { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" });
1187
+ return err(wrapError("kv", error));
1188
+ }
1189
+ const refusal = prefix === null ? void 0 : sessionRefusal(prefix);
1190
+ if (refusal) {
1191
+ readEvent("list", r.listPath, prefix, "network", "grant_missing", "error", started, { code: refusal });
1192
+ return err(wrapError("kv", error));
1193
+ }
803
1194
  readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, { ...extra, code: errorCode(error), verify: "error" });
804
1195
  return ok(local);
805
1196
  }
@@ -828,11 +1219,26 @@ function createKVReplication(deps) {
828
1219
  observeNetworkRequested({ op: "get", space: r.space, path: r.path, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
829
1220
  }
830
1221
  }
831
- if (prefix === void 0) {
832
- const value = await r.network();
833
- readEvent("get", r.path, null, "network", "not_covered", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started);
834
- return value;
1222
+ let networkRejected = false;
1223
+ const fallback = async (replica, reason, extra = {}) => {
1224
+ try {
1225
+ return await networkRead(r.network, "get", r.path, replica, reason, started, extra);
1226
+ } catch (error) {
1227
+ networkRejected = true;
1228
+ throw error;
1229
+ }
1230
+ };
1231
+ if (prefix === void 0) return networkRead(r.network, "get", r.path, null, "not_covered", started);
1232
+ const delivery = checkDelivery();
1233
+ if (delivery.status !== "allow") {
1234
+ return fallback(
1235
+ prefix,
1236
+ delivery.status === "consent_expired" ? "grant_expired" : "replica_gated",
1237
+ { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" }
1238
+ );
835
1239
  }
1240
+ const refusal = sessionRefusal(prefix);
1241
+ if (refusal) return fallback(prefix, "grant_missing", { code: refusal });
836
1242
  try {
837
1243
  await raceSignal(reportExistingPins(), r.signal);
838
1244
  } catch {
@@ -843,86 +1249,63 @@ function createKVReplication(deps) {
843
1249
  handle = await raceSignal(open(prefix), r.signal);
844
1250
  } catch (error) {
845
1251
  if (r.signal.aborted) return abortedResult(r.signal);
846
- const value = await r.network();
847
- readEvent("get", r.path, prefix, "network", errorCode(error) === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : "replica_unavailable", value.ok ? "found" : "error", started, { code: errorCode(error) });
848
- return value;
1252
+ if (errorCode(error) === "CONSENT_EXPIRED") startConsentDenial();
1253
+ const code = errorCode(error);
1254
+ return fallback(prefix, code === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_unavailable", { code });
849
1255
  }
850
1256
  if (replicas.get(prefix)?.reason === "grant_missing") {
851
- const value = await r.network();
852
- readEvent("get", r.path, prefix, "network", "grant_missing", value.ok ? "found" : "error", started, { code: replicas.get(prefix)?.lastError });
853
- return value;
1257
+ const code = replicas.get(prefix)?.lastError;
1258
+ return fallback(prefix, code === "GRANT_EXPIRED" ? "grant_expired" : code === "REPLICA_GATED" ? "replica_gated" : "grant_missing", { code });
854
1259
  }
855
1260
  let fresh;
856
1261
  try {
857
1262
  fresh = await freshness(prefix, handle, r.signal);
858
1263
  } catch (error) {
859
1264
  recordRevocation(prefix, error);
1265
+ if (errorCode(error) === "CONSENT_EXPIRED") startConsentDenial();
860
1266
  if (r.signal.aborted) return abortedResult(r.signal);
861
- const value = await r.network();
862
- readEvent("get", r.path, prefix, "network", "replica_error", value.ok ? "found" : "error", started, { code: errorCode(error) });
863
- return value;
1267
+ const code = errorCode(error);
1268
+ return fallback(prefix, code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_error", { code });
864
1269
  }
865
1270
  if (r.signal.aborted) return abortedResult(r.signal);
866
1271
  if (fresh.failure) {
867
- const value = await r.network();
868
- readEvent("get", r.path, prefix, "network", fresh.failure === "busy" ? "stale" : "stale", value.ok ? "found" : "error", started, { code: fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError, syncedBeforeRead: fresh.syncedBeforeRead });
869
- return value;
1272
+ const code = fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError;
1273
+ return fallback(
1274
+ prefix,
1275
+ code === "GRANT_REVOKED" ? "grant_revoked" : code === "REPLICA_GATED" || code === "CONSENT_EXPIRED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "stale",
1276
+ { code, syncedBeforeRead: fresh.syncedBeforeRead }
1277
+ );
870
1278
  }
871
1279
  let pendingState;
872
1280
  try {
873
1281
  pendingState = await raceSignal(pending.read(), r.signal);
874
1282
  } catch (error) {
875
1283
  if (r.signal.aborted) return abortedResult(r.signal);
876
- const value = await r.network();
877
- readEvent("get", r.path, prefix, "network", "replica_unavailable", value.ok ? "found" : "error", started, { code: errorCode(error) });
878
- return value;
1284
+ const code = errorCode(error);
1285
+ return fallback(prefix, code === "REPLICA_GATED" || code === "CONSENT_EXPIRED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_unavailable", { code });
879
1286
  }
880
1287
  const record = pendingState.records.find((x) => x.key === r.path);
881
- if (record) {
882
- const value = await r.network();
883
- readEvent("get", r.path, prefix, "network", "pending_write", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started, { pendingState: record.state, syncedBeforeRead: fresh.syncedBeforeRead });
884
- return value;
885
- }
886
- if (!pending.durable && !inProcessProof.has(prefix)) {
887
- const value = await r.network();
888
- readEvent("get", r.path, prefix, "network", "REPLICA_UNPROVEN_SINCE_START", value.ok ? "found" : "error", started);
889
- return value;
890
- }
891
- if (fresh.status.syncedThroughEpoch < pendingState.committedEpoch) {
892
- const value = await r.network();
893
- readEvent("get", r.path, prefix, "network", "REPLICA_BEHIND_OWN_WRITES", value.ok ? "found" : "error", started);
894
- return value;
895
- }
896
- if (fresh.status.authority.state !== "valid") {
897
- const value = await r.network();
898
- readEvent("get", r.path, prefix, "network", fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid", value.ok ? "found" : "error", started);
899
- return value;
900
- }
901
- if (fresh.status.coverage !== "complete") {
902
- const value = await r.network();
903
- readEvent("get", r.path, prefix, "network", "coverage_incomplete", value.ok ? "found" : "error", started);
904
- return value;
905
- }
1288
+ if (record) return fallback(prefix, "pending_write", { pendingState: record.state, syncedBeforeRead: fresh.syncedBeforeRead });
1289
+ if (!pending.durable && !inProcessProof.has(prefix)) return fallback(prefix, "REPLICA_UNPROVEN_SINCE_START");
1290
+ if (fresh.status.syncedThroughEpoch < pendingState.committedEpoch) return fallback(prefix, "REPLICA_BEHIND_OWN_WRITES");
1291
+ if (fresh.status.authority.state !== "valid") return fallback(prefix, fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid");
1292
+ if (fresh.status.coverage !== "complete") return fallback(prefix, "coverage_incomplete");
906
1293
  try {
907
1294
  const local = await raceSignal(handle.get(r.path), r.signal);
1295
+ const delivery2 = checkDelivery();
1296
+ if (delivery2.status !== "allow") throw deliveryError(delivery2);
1297
+ const refusal2 = sessionRefusal(prefix);
1298
+ if (refusal2) return await fallback(prefix, "grant_missing", { code: refusal2 });
908
1299
  try {
909
1300
  validateReadMeta(local.meta);
910
1301
  } catch (error) {
911
1302
  const code = errorCode(error);
912
1303
  recordRevocation(prefix, error);
913
- const value = await r.network();
914
- readEvent("get", r.path, prefix, "network", reasonForReadMetaCode(code), value.ok ? "found" : "error", started, { code });
915
- return value;
916
- }
917
- if (local.status === "coverage_incomplete" || local.status === "not_covered" || local.status === "content_missing") {
918
- const value = await r.network();
919
- readEvent("get", r.path, prefix, "network", local.status === "content_missing" ? "content_missing" : local.status === "coverage_incomplete" ? "coverage_incomplete" : "not_covered", value.ok ? "found" : "error", started);
920
- return value;
1304
+ return await fallback(prefix, reasonForReadMetaCode(code), { code });
921
1305
  }
1306
+ if (local.status === "coverage_incomplete" || local.status === "not_covered" || local.status === "content_missing") return await fallback(prefix, local.status === "content_missing" ? "content_missing" : local.status === "coverage_incomplete" ? "coverage_incomplete" : "not_covered");
922
1307
  if (local.status === "present" && r.options?.maxResponseBytes !== void 0 && local.value.byteLength > r.options.maxResponseBytes) {
923
- const value = await r.network();
924
- readEvent("get", r.path, prefix, "network", "unsupported_option", value.ok ? "found" : "error", started);
925
- return value;
1308
+ return await fallback(prefix, "unsupported_option");
926
1309
  }
927
1310
  const result = localGet(local, r.path, r.options ?? {});
928
1311
  const reason = local.status === "present" ? "hit" : local.status;
@@ -938,11 +1321,11 @@ function createKVReplication(deps) {
938
1321
  readEvent("get", r.path, prefix, "replica", reason, result.ok ? "found" : result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started, event);
939
1322
  return result;
940
1323
  } catch (error) {
1324
+ if (networkRejected) throw error;
941
1325
  recordRevocation(prefix, error);
1326
+ if (errorCode(error) === "CONSENT_EXPIRED") startConsentDenial();
942
1327
  if (r.signal.aborted) return abortedResult(r.signal);
943
- const value = await r.network();
944
- readEvent("get", r.path, prefix, "network", "replica_error", value.ok ? "found" : "error", started, { code: errorCode(error) });
945
- return value;
1328
+ return fallback(prefix, deliveryReason(errorCode(error)), { code: errorCode(error) });
946
1329
  }
947
1330
  }
948
1331
  async function list(r) {
@@ -963,17 +1346,46 @@ function createKVReplication(deps) {
963
1346
  readEvent("list", r.listPath, null, "none", "cursor_restart", "error", started);
964
1347
  return cursorRestart();
965
1348
  }
966
- if (cursor && !cursor.startsWith("tcr1.")) {
967
- const result = await r.network();
968
- readEvent("list", r.listPath, null, "network", "network_cursor", result.ok ? "found" : "error", started);
969
- return result;
1349
+ if (cursor && !cursor.startsWith("tcr1.")) return networkRead(r.network, "list", r.listPath, null, "network_cursor", started);
1350
+ const prefix = listPathCovered(options.prefixes, r.listPath);
1351
+ let networkRejected = false;
1352
+ const fallback = async (replica, reason, extra = {}) => {
1353
+ try {
1354
+ return await networkRead(r.network, "list", r.listPath, replica, reason, started, extra);
1355
+ } catch (error) {
1356
+ networkRejected = true;
1357
+ throw error;
1358
+ }
1359
+ };
1360
+ if (prefix !== void 0 && !r.options?.raw) {
1361
+ const delivery = checkDelivery();
1362
+ if (delivery.status !== "allow") {
1363
+ if (decoded) {
1364
+ readEvent("list", r.listPath, prefix, "none", "cursor_restart", "error", started);
1365
+ return cursorRestart();
1366
+ }
1367
+ return fallback(
1368
+ prefix,
1369
+ delivery.status === "consent_expired" ? "grant_expired" : "replica_gated",
1370
+ { code: delivery.status === "consent_expired" ? delivery.code : "REPLICA_GATED" }
1371
+ );
1372
+ }
1373
+ }
1374
+ if (prefix !== void 0 && !r.options?.raw) {
1375
+ const refusal = sessionRefusal(prefix);
1376
+ if (refusal) {
1377
+ if (decoded) {
1378
+ readEvent("list", r.listPath, prefix, "none", "cursor_restart", "error", started);
1379
+ return cursorRestart();
1380
+ }
1381
+ return fallback(prefix, "grant_missing", { code: refusal });
1382
+ }
970
1383
  }
971
1384
  try {
972
1385
  await raceSignal(reportExistingPins(), r.signal);
973
1386
  } catch {
974
1387
  if (r.signal.aborted) return abortedResult(r.signal);
975
1388
  }
976
- const prefix = listPathCovered(options.prefixes, r.listPath);
977
1389
  const restart = () => {
978
1390
  readEvent("list", r.listPath, prefix ?? null, "none", "cursor_restart", "error", started);
979
1391
  return cursorRestart();
@@ -981,36 +1393,33 @@ function createKVReplication(deps) {
981
1393
  if (cursor && decoded && (decoded.space !== r.space || decoded.path !== r.listPath)) return restart();
982
1394
  if (prefix === void 0 || r.options?.raw) {
983
1395
  if (cursor && decoded) return restart();
984
- const result = await r.network();
985
- readEvent("list", r.listPath, prefix ?? null, "network", prefix !== void 0 ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
986
- return result;
1396
+ return networkRead(r.network, "list", r.listPath, prefix ?? null, prefix !== void 0 ? "unsupported_option" : "not_covered", started);
987
1397
  }
988
1398
  let handle;
989
1399
  try {
990
1400
  handle = await raceSignal(open(prefix), r.signal);
991
1401
  } catch (error) {
992
1402
  if (r.signal.aborted) return abortedResult(r.signal);
1403
+ if (errorCode(error) === "CONSENT_EXPIRED") startConsentDenial();
993
1404
  if (cursor && decoded) return restart();
994
- const result = await r.network();
995
- readEvent("list", r.listPath, prefix, "network", errorCode(error) === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : "replica_unavailable", result.ok ? "found" : "error", started, { code: errorCode(error) });
996
- return result;
1405
+ const code = errorCode(error);
1406
+ return fallback(prefix, code === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_unavailable", { code });
997
1407
  }
998
1408
  if (replicas.get(prefix)?.reason === "grant_missing") {
999
1409
  if (cursor && decoded) return restart();
1000
- const result = await r.network();
1001
- readEvent("list", r.listPath, prefix, "network", "grant_missing", result.ok ? "found" : "error", started);
1002
- return result;
1410
+ const code = replicas.get(prefix)?.lastError;
1411
+ return fallback(prefix, code === "GRANT_EXPIRED" ? "grant_expired" : code === "REPLICA_GATED" ? "replica_gated" : "grant_missing", { code });
1003
1412
  }
1004
1413
  let fresh;
1005
1414
  try {
1006
1415
  fresh = await freshness(prefix, handle, r.signal);
1007
1416
  } catch (error) {
1008
1417
  recordRevocation(prefix, error);
1418
+ if (errorCode(error) === "CONSENT_EXPIRED") startConsentDenial();
1009
1419
  if (r.signal.aborted) return abortedResult(r.signal);
1010
1420
  if (cursor && decoded) return restart();
1011
- const result = await r.network();
1012
- readEvent("list", r.listPath, prefix, "network", "replica_error", result.ok ? "found" : "error", started, { code: errorCode(error) });
1013
- return result;
1421
+ const code = errorCode(error);
1422
+ return fallback(prefix, code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_error", { code });
1014
1423
  }
1015
1424
  if (r.signal.aborted) return abortedResult(r.signal);
1016
1425
  let state;
@@ -1019,41 +1428,35 @@ function createKVReplication(deps) {
1019
1428
  } catch (error) {
1020
1429
  if (r.signal.aborted) return abortedResult(r.signal);
1021
1430
  if (cursor && decoded) return restart();
1022
- const result = await r.network();
1023
- readEvent("list", r.listPath, prefix, "network", "replica_unavailable", result.ok ? "found" : "error", started, { code: errorCode(error) });
1024
- return result;
1431
+ const code = errorCode(error);
1432
+ return fallback(prefix, code === "REPLICA_GATED" || code === "CONSENT_EXPIRED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "replica_unavailable", { code });
1025
1433
  }
1026
- const pendingRange = state.records.find((record) => listRangeCovers(r.listPath, record.key) && (!decoded || utf8Compare(record.key, decoded.last) > 0));
1434
+ const pendingRange = state.records.find((record) => listRangeCovers(r.listPath, record.key));
1027
1435
  const behind = fresh.status.syncedThroughEpoch < state.committedEpoch;
1028
1436
  if (cursor && decoded && (fresh.failure || pendingRange || behind || fresh.status.coverage !== "complete" || fresh.status.authority.state !== "valid" || !pending.durable && !inProcessProof.has(prefix))) return restart();
1029
- if (behind) {
1030
- const result = await r.network();
1031
- readEvent("list", r.listPath, prefix, "network", "REPLICA_BEHIND_OWN_WRITES", result.ok ? "found" : "error", started);
1032
- return result;
1033
- }
1437
+ if (behind) return fallback(prefix, "REPLICA_BEHIND_OWN_WRITES");
1034
1438
  if (fresh.failure) {
1035
- const result = await r.network();
1036
- readEvent("list", r.listPath, prefix, "network", "stale", result.ok ? "found" : "error", started, { code: fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError });
1037
- return result;
1038
- }
1039
- if (pendingRange) {
1040
- const result = await r.network();
1041
- readEvent("list", r.listPath, prefix, "network", "pending_write", result.ok ? "found" : "error", started, { pendingState: pendingRange.state });
1042
- return result;
1043
- }
1044
- if (!pending.durable && !inProcessProof.has(prefix)) {
1045
- const result = await r.network();
1046
- readEvent("list", r.listPath, prefix, "network", "REPLICA_UNPROVEN_SINCE_START", result.ok ? "found" : "error", started);
1047
- return result;
1048
- }
1049
- if (fresh.status.coverage !== "complete" || fresh.status.authority.state !== "valid") {
1050
- const result = await r.network();
1051
- readEvent("list", r.listPath, prefix, "network", fresh.status.coverage !== "complete" ? "coverage_incomplete" : fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid", result.ok ? "found" : "error", started);
1052
- return result;
1439
+ const code = fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError;
1440
+ return fallback(prefix, code === "GRANT_REVOKED" ? "grant_revoked" : code === "REPLICA_GATED" || code === "CONSENT_EXPIRED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : "stale", { code });
1053
1441
  }
1442
+ if (pendingRange) return fallback(prefix, "pending_write", { pendingState: pendingRange.state });
1443
+ if (!pending.durable && !inProcessProof.has(prefix)) return fallback(prefix, "REPLICA_UNPROVEN_SINCE_START");
1444
+ if (fresh.status.coverage !== "complete" || fresh.status.authority.state !== "valid") return fallback(prefix, fresh.status.coverage !== "complete" ? "coverage_incomplete" : fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid");
1054
1445
  try {
1055
1446
  const signalHandle = { list: (o) => raceSignal(handle.list(o), r.signal) };
1056
1447
  const page = await raceSignal(localList(signalHandle, r.space, r.listPath, r.options?.limit, cursor, validateReadMeta), r.signal);
1448
+ const latest = await raceSignal(pending.read(), r.signal);
1449
+ const laterPending = latest.records.find((record) => listRangeCovers(r.listPath, record.key));
1450
+ if (cursor && decoded && (laterPending || fresh.status.syncedThroughEpoch < latest.committedEpoch)) return restart();
1451
+ if (fresh.status.syncedThroughEpoch < latest.committedEpoch) return await fallback(prefix, "REPLICA_BEHIND_OWN_WRITES");
1452
+ if (laterPending) return await fallback(prefix, "pending_write", { pendingState: laterPending.state });
1453
+ const delivery = checkDelivery();
1454
+ if (delivery.status !== "allow") throw deliveryError(delivery);
1455
+ const refusal = sessionRefusal(prefix);
1456
+ if (refusal) {
1457
+ if (decoded) return restart();
1458
+ return await fallback(prefix, "grant_missing", { code: refusal });
1459
+ }
1057
1460
  const event = {
1058
1461
  count: page.keys.length,
1059
1462
  syncedBeforeRead: fresh.syncedBeforeRead,
@@ -1064,14 +1467,14 @@ function createKVReplication(deps) {
1064
1467
  readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, event);
1065
1468
  return ok(page);
1066
1469
  } catch (error) {
1470
+ if (networkRejected) throw error;
1067
1471
  if (r.signal.aborted) return abortedResult(r.signal);
1068
1472
  const code = errorCode(error);
1473
+ if (code === "CONSENT_EXPIRED") startConsentDenial();
1069
1474
  recordRevocation(prefix, error);
1070
1475
  if (cursor && decoded) return restart();
1071
- const reason = reasonForReadMetaCode(code);
1072
- const result = await r.network();
1073
- readEvent("list", r.listPath, prefix, "network", reason, result.ok ? "found" : "error", started, { code });
1074
- return result;
1476
+ const reason = code === "CONSENT_EXPIRED" || code === "REPLICA_GATED" || code === "GRANT_EXPIRED" ? deliveryReason(code) : reasonForReadMetaCode(code);
1477
+ return fallback(prefix, reason, { code });
1075
1478
  }
1076
1479
  }
1077
1480
  async function write(r) {
@@ -1080,11 +1483,14 @@ function createKVReplication(deps) {
1080
1483
  return prefix !== void 0 ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1081
1484
  });
1082
1485
  if (covered.length === 0) return r.network();
1486
+ if (checkDelivery().status !== "allow") return r.network();
1083
1487
  await reportExistingPins();
1488
+ if (checkDelivery().status !== "allow") return r.network();
1084
1489
  const opId = globalThis.crypto?.randomUUID?.() ?? `${scheduler.now()}-${Math.random()}`;
1085
1490
  try {
1086
1491
  await pending.update((s) => begin(s, opId, covered, nowDate(scheduler.now())));
1087
1492
  } catch (error) {
1493
+ if (errorCode(error) === "REPLICA_GATED" || errorCode(error) === "CONSENT_EXPIRED" || checkDelivery().status !== "allow") return r.network();
1088
1494
  return err(serviceError(ErrorCodes.KV_WRITE_FAILED, "Unable to persist pending KV write; request was not dispatched", "kv", { cause: error instanceof Error ? error : void 0, meta: { replication: "pending_store_unavailable", requestDispatched: false } }));
1089
1495
  }
1090
1496
  const started = scheduler.now();
@@ -1149,8 +1555,32 @@ function createKVReplication(deps) {
1149
1555
  for (const inspection of active) inspection.abort.abort(Object.assign(new Error("Replica status inspection cancelled"), { code: "ABORTED" }));
1150
1556
  return Promise.all(active.map((inspection) => inspection.settled)).then(() => void 0);
1151
1557
  }
1558
+ function gatedStatus(decision) {
1559
+ return options.prefixes.map((prefix) => ({
1560
+ prefix,
1561
+ state: "gated",
1562
+ reason: "replica_gated",
1563
+ gate: decision.status === "gated" ? decision.gate : "stamp_stale",
1564
+ errorCode: decision.status === "consent_expired" ? decision.code : "REPLICA_GATED",
1565
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
1566
+ pinned: [],
1567
+ lagMs: null,
1568
+ redacted: true
1569
+ }));
1570
+ }
1152
1571
  async function status() {
1153
- const persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
1572
+ const beforeStatus = checkDelivery();
1573
+ if (beforeStatus.status !== "allow") return gatedStatus(beforeStatus);
1574
+ let persistedSnapshot;
1575
+ try {
1576
+ persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
1577
+ } catch (error) {
1578
+ const delivery = checkDelivery();
1579
+ if (delivery.status !== "allow") return gatedStatus(delivery);
1580
+ throw error;
1581
+ }
1582
+ const afterPending = checkDelivery();
1583
+ if (afterPending.status !== "allow") return gatedStatus(afterPending);
1154
1584
  const snapshot = persistedSnapshot ?? emptyPendingState(identity);
1155
1585
  const entries = await Promise.all(options.prefixes.map(async (prefix) => {
1156
1586
  if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
@@ -1172,7 +1602,7 @@ function createKVReplication(deps) {
1172
1602
  const full = local && !("redacted" in local) ? local : void 0;
1173
1603
  const revoked = opened?.authorityRevoked === true || full?.authority.state === "revoked";
1174
1604
  const rootUnsupported = opened?.lastError === "REPLICATION_ROOT_UNSUPPORTED";
1175
- const state = isClosed ? "closed" : revoked ? "revoked" : rootUnsupported ? "unavailable" : opened?.reason === "grant_missing" ? "grant_missing" : full?.coverage === "complete" && full.authority.state === "valid" ? "ready" : opened?.reason || inspected.errorCode ? "unavailable" : "idle";
1605
+ const state = isClosed ? "closed" : revoked ? "revoked" : rootUnsupported || opened?.reason === "grant_expired" ? "unavailable" : opened?.reason === "grant_missing" ? "grant_missing" : full?.coverage === "complete" && full.authority.state === "valid" ? "ready" : opened?.reason || inspected.errorCode ? "unavailable" : "idle";
1176
1606
  return {
1177
1607
  prefix,
1178
1608
  state,
@@ -1189,6 +1619,8 @@ function createKVReplication(deps) {
1189
1619
  lagMs: full?.lastSyncAt ? scheduler.now() - Date.parse(full.lastSyncAt) : null
1190
1620
  };
1191
1621
  }));
1622
+ const beforeReturn = checkDelivery();
1623
+ if (beforeReturn.status !== "allow") return gatedStatus(beforeReturn);
1192
1624
  return entries.filter((entry) => entry !== void 0);
1193
1625
  }
1194
1626
  async function sync(o) {
@@ -1204,11 +1636,15 @@ function createKVReplication(deps) {
1204
1636
  var _a;
1205
1637
  purgeStarted = true;
1206
1638
  purgeInProgress = true;
1639
+ cancelConsentTimer?.();
1640
+ cancelConsentTimer = void 0;
1207
1641
  const hasStatusInspections = statusInspections.size > 0;
1208
1642
  const statusDrain = cancelStatusInspections();
1209
1643
  for (const state of replicas.values()) {
1210
1644
  state.timer?.();
1211
1645
  state.timer = void 0;
1646
+ state.renewalTimer?.();
1647
+ state.renewalTimer = void 0;
1212
1648
  state.syncContext && ((_a = state.syncContext).abortCause ?? (_a.abortCause = "purge"));
1213
1649
  state.abort?.abort();
1214
1650
  state.mintAbort?.abort();
@@ -1232,6 +1668,7 @@ function createKVReplication(deps) {
1232
1668
  ...[...replicas.values()].flatMap((state) => [
1233
1669
  state.opening?.catch(() => void 0),
1234
1670
  state.sync?.catch(() => void 0),
1671
+ state.renewing?.catch(() => void 0),
1235
1672
  state.handle?.close().catch(() => void 0)
1236
1673
  ]),
1237
1674
  ...hasStatusInspections ? [statusDrain] : []
@@ -1269,11 +1706,15 @@ function createKVReplication(deps) {
1269
1706
  var _a;
1270
1707
  if (isClosed) return;
1271
1708
  isClosed = true;
1709
+ cancelConsentTimer?.();
1710
+ cancelConsentTimer = void 0;
1272
1711
  const hasStatusInspections = statusInspections.size > 0;
1273
1712
  const statusDrain = cancelStatusInspections();
1274
1713
  for (const [prefix, state] of replicas) {
1275
1714
  state.timer?.();
1276
1715
  state.timer = void 0;
1716
+ state.renewalTimer?.();
1717
+ state.renewalTimer = void 0;
1277
1718
  state.mintAbort?.abort();
1278
1719
  state.syncContext && ((_a = state.syncContext).abortCause ?? (_a.abortCause = "close"));
1279
1720
  state.abort?.abort();
@@ -1282,6 +1723,7 @@ function createKVReplication(deps) {
1282
1723
  const drain = Promise.all([
1283
1724
  ...[...replicas.values()].map(async (state) => {
1284
1725
  await state.opening?.catch(() => void 0);
1726
+ await state.renewing?.catch(() => void 0);
1285
1727
  await state.sync?.catch(() => void 0);
1286
1728
  await state.handle?.close().catch(() => void 0);
1287
1729
  }),
@@ -1311,7 +1753,8 @@ function createKVReplication(deps) {
1311
1753
  cancel();
1312
1754
  }
1313
1755
  }
1314
- return { get, list, write, observeNetworkRequested, status, sync, purge, clearPending: clearPending2, close };
1756
+ scheduleConsentDeadline();
1757
+ return { get, list, write, observeNetworkRequested, observeConsentExpired, status, sync, purge, clearPending: clearPending2, close };
1315
1758
  }
1316
1759
  function cancellationCode(signal) {
1317
1760
  return wrapError("kv", signal.reason).code === ErrorCodes.TIMEOUT || typeof signal.reason === "object" && signal.reason !== null && "code" in signal.reason && signal.reason.code === ErrorCodes.TIMEOUT ? ErrorCodes.TIMEOUT : ErrorCodes.ABORTED;