@opendatalabs/personal-server-ts-server 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -105729,7 +105729,7 @@ var LineageGatewayError = class extends ProtocolError {
105729
105729
  };
105730
105730
  var LineageCascadeUnavailableError = class extends ProtocolError {
105731
105731
  constructor(details) {
105732
- super(501, "LINEAGE_CASCADE_UNAVAILABLE", "DELETE ?cascade=lineage is specified but not implemented yet: it needs the durable (tombstone) deletion of every derivative at the gateway, which this server cannot perform. Delete scopes one at a time.", details);
105732
+ super(501, "LINEAGE_CASCADE_UNAVAILABLE", "DELETE ?cascade=lineage is specified but not implemented yet: the lineage walk that finds every derivative is missing. Delete scopes one at a time; each single-scope delete is durable.", details);
105733
105733
  }
105734
105734
  };
105735
105735
  var InvalidCascadeError = class extends ProtocolError {
@@ -105737,6 +105737,16 @@ var InvalidCascadeError = class extends ProtocolError {
105737
105737
  super(400, "INVALID_CASCADE", 'Unsupported cascade mode; the only supported value is "lineage"', details);
105738
105738
  }
105739
105739
  };
105740
+ var DeleteTombstoneFailedError = class extends ProtocolError {
105741
+ constructor(details) {
105742
+ super(502, "DELETE_TOMBSTONE_FAILED", "Gateway did not acknowledge the deletion tombstone; nothing was deleted", details);
105743
+ }
105744
+ };
105745
+ var DataDeletedError = class extends ProtocolError {
105746
+ constructor(details) {
105747
+ super(410, "DATA_DELETED", "Data point has been deleted", details);
105748
+ }
105749
+ };
105740
105750
 
105741
105751
  // ../core/dist/sync/data-point-id.js
105742
105752
  function computeDataPointId(ownerAddress, scope) {
@@ -109344,6 +109354,7 @@ async function createPersistentPsLiteStorage(adapter, persistence = createIndexe
109344
109354
  schemaId: entry.schemaId ?? null,
109345
109355
  version: version4,
109346
109356
  dataPointId: entry.dataPointId ?? null,
109357
+ afterTombstoneVersion: entry.afterTombstoneVersion ?? null,
109347
109358
  id: state.nextId,
109348
109359
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
109349
109360
  };
@@ -109437,25 +109448,34 @@ async function createPersistentPsLiteStorage(adapter, persistence = createIndexe
109437
109448
  await persist();
109438
109449
  return deleted;
109439
109450
  },
109451
+ async deleteVersion(scope, collectedAt2) {
109452
+ const entry = state.entries.find((e10) => e10.scope === scope && e10.collectedAt === collectedAt2);
109453
+ if (!entry)
109454
+ return false;
109455
+ return removeEntry(entry);
109456
+ },
109440
109457
  async deleteByFileId(fileId) {
109441
109458
  const entry = state.entries.find((e10) => e10.fileId === fileId);
109442
109459
  if (!entry)
109443
109460
  return false;
109444
- const blobPath = envelopePath(entry.scope, entry.collectedAt);
109445
- await Promise.all([
109446
- fileStore.deleteEnvelope(blobPath),
109447
- fallbackStore.deleteEnvelope(blobPath),
109448
- fileStore.deleteBlockTree?.(blockTreePath(entry.scope, entry.collectedAt)) ?? Promise.resolve(),
109449
- fallbackStore.deleteBlockTree?.(blockTreePath(entry.scope, entry.collectedAt)) ?? Promise.resolve()
109450
- ]);
109451
- state = {
109452
- ...state,
109453
- entries: state.entries.filter((e10) => e10 !== entry)
109454
- };
109455
- await persist();
109456
- return true;
109461
+ return removeEntry(entry);
109457
109462
  }
109458
109463
  };
109464
+ async function removeEntry(entry) {
109465
+ const blobPath = envelopePath(entry.scope, entry.collectedAt);
109466
+ await Promise.all([
109467
+ fileStore.deleteEnvelope(blobPath),
109468
+ fallbackStore.deleteEnvelope(blobPath),
109469
+ fileStore.deleteBlockTree?.(blockTreePath(entry.scope, entry.collectedAt)) ?? Promise.resolve(),
109470
+ fallbackStore.deleteBlockTree?.(blockTreePath(entry.scope, entry.collectedAt)) ?? Promise.resolve()
109471
+ ]);
109472
+ state = {
109473
+ ...state,
109474
+ entries: state.entries.filter((e10) => e10 !== entry)
109475
+ };
109476
+ await persist();
109477
+ return true;
109478
+ }
109459
109479
  if (fileStore.readEnvelopePreview) {
109460
109480
  storagePort.readEnvelopePreview = async (scope, collectedAt2, { maxBytes }) => {
109461
109481
  const path = envelopePath(scope, collectedAt2);
@@ -110743,15 +110763,44 @@ function normalizeOffset(value) {
110743
110763
  function isRecord3(value) {
110744
110764
  return value !== null && typeof value === "object" && !Array.isArray(value);
110745
110765
  }
110766
+ var VISIBILITY_PAGE_SIZE = 200;
110746
110767
  async function listDataScopesContract(input) {
110747
110768
  const limit = normalizeLimit(input.limit);
110748
110769
  const offset = normalizeOffset(input.offset);
110749
- const result = input.storage.listScopes({
110750
- scopePrefix: input.scopePrefix,
110751
- limit,
110752
- offset
110753
- });
110754
- const scopes = await Promise.all(result.scopes.map(async (summary) => {
110770
+ let page;
110771
+ let total;
110772
+ if (input.isVisible) {
110773
+ const visible = [];
110774
+ for (let scan = 0; ; scan += VISIBILITY_PAGE_SIZE) {
110775
+ const batch = input.storage.listScopes({
110776
+ scopePrefix: input.scopePrefix,
110777
+ limit: VISIBILITY_PAGE_SIZE,
110778
+ offset: scan
110779
+ });
110780
+ for (const summary of batch.scopes) {
110781
+ const latest = input.storage.findEntry({
110782
+ scope: summary.scope,
110783
+ at: summary.latestCollectedAt
110784
+ });
110785
+ if (!latest || await input.isVisible(summary.scope, latest)) {
110786
+ visible.push(summary);
110787
+ }
110788
+ }
110789
+ if (batch.scopes.length < VISIBILITY_PAGE_SIZE)
110790
+ break;
110791
+ }
110792
+ total = visible.length;
110793
+ page = visible.slice(offset, offset + limit);
110794
+ } else {
110795
+ const result = input.storage.listScopes({
110796
+ scopePrefix: input.scopePrefix,
110797
+ limit,
110798
+ offset
110799
+ });
110800
+ page = result.scopes;
110801
+ total = result.total;
110802
+ }
110803
+ const scopes = await Promise.all(page.map(async (summary) => {
110755
110804
  const entry = input.storage.findEntry({
110756
110805
  scope: summary.scope,
110757
110806
  at: summary.latestCollectedAt
@@ -110759,7 +110808,7 @@ async function listDataScopesContract(input) {
110759
110808
  if (!entry) {
110760
110809
  return summary;
110761
110810
  }
110762
- const hasBlocks = typeof input.storage.canReadScopeBlocks === "function" ? await input.storage.canReadScopeBlocks(summary.scope, summary.latestCollectedAt) : typeof input.storage.hasScopeBlocks === "function" ? await input.storage.hasScopeBlocks(summary.scope, summary.latestCollectedAt) : false;
110811
+ const hasBlocks = typeof input.storage.hasScopeBlocks === "function" ? await input.storage.hasScopeBlocks(summary.scope, summary.latestCollectedAt) : false;
110763
110812
  return {
110764
110813
  ...summary,
110765
110814
  dataStatus: hasBlocks ? "ready" : "indexing",
@@ -110770,33 +110819,52 @@ async function listDataScopesContract(input) {
110770
110819
  ok: true,
110771
110820
  response: {
110772
110821
  scopes,
110773
- total: result.total,
110822
+ total,
110774
110823
  limit,
110775
110824
  offset
110776
110825
  }
110777
110826
  };
110778
110827
  }
110779
- function listDataVersionsContract(input) {
110828
+ async function listDataVersionsContract(input) {
110780
110829
  const scopeResult = parseDataScopeContract(input.scopeParam);
110781
110830
  if (!scopeResult.ok)
110782
110831
  return scopeResult;
110783
110832
  const limit = normalizeLimit(input.limit);
110784
110833
  const offset = normalizeOffset(input.offset);
110785
- const entries = input.storage.listVersions(scopeResult.scope, {
110786
- limit,
110787
- offset
110788
- });
110834
+ let page;
110835
+ let total;
110836
+ if (input.isVisible) {
110837
+ const visible = [];
110838
+ for (let scan = 0; ; scan += VISIBILITY_PAGE_SIZE) {
110839
+ const batch = input.storage.listVersions(scopeResult.scope, {
110840
+ limit: VISIBILITY_PAGE_SIZE,
110841
+ offset: scan
110842
+ });
110843
+ for (const entry of batch) {
110844
+ if (await input.isVisible(scopeResult.scope, entry)) {
110845
+ visible.push(entry);
110846
+ }
110847
+ }
110848
+ if (batch.length < VISIBILITY_PAGE_SIZE)
110849
+ break;
110850
+ }
110851
+ total = visible.length;
110852
+ page = visible.slice(offset, offset + limit);
110853
+ } else {
110854
+ page = input.storage.listVersions(scopeResult.scope, { limit, offset });
110855
+ total = input.storage.countVersions(scopeResult.scope);
110856
+ }
110789
110857
  return {
110790
110858
  ok: true,
110791
110859
  scope: scopeResult.scope,
110792
110860
  response: {
110793
110861
  scope: scopeResult.scope,
110794
- versions: entries.map((entry) => ({
110862
+ versions: page.map((entry) => ({
110795
110863
  fileId: entry.fileId,
110796
110864
  schemaId: entry.schemaId,
110797
110865
  collectedAt: entry.collectedAt
110798
110866
  })),
110799
- total: input.storage.countVersions(scopeResult.scope),
110867
+ total,
110800
110868
  limit,
110801
110869
  offset
110802
110870
  }
@@ -110873,7 +110941,8 @@ async function ingestDataContract(input) {
110873
110941
  path: writeResult.relativePath,
110874
110942
  scope: scopeResult.scope,
110875
110943
  collectedAt: input.collectedAt,
110876
- sizeBytes: writeResult.sizeBytes
110944
+ sizeBytes: writeResult.sizeBytes,
110945
+ afterTombstoneVersion: input.afterTombstoneVersion ?? null
110877
110946
  });
110878
110947
  return {
110879
110948
  ok: true,
@@ -110944,7 +111013,8 @@ async function ingestBinaryDataContract(input) {
110944
111013
  path: writeResult.relativePath,
110945
111014
  scope: scopeResult.scope,
110946
111015
  collectedAt: input.collectedAt,
110947
- sizeBytes: input.bytes.length
111016
+ sizeBytes: input.bytes.length,
111017
+ afterTombstoneVersion: input.afterTombstoneVersion ?? null
110948
111018
  });
110949
111019
  return {
110950
111020
  ok: true,
@@ -110954,15 +111024,6 @@ async function ingestBinaryDataContract(input) {
110954
111024
  writeResult
110955
111025
  };
110956
111026
  }
110957
- async function deleteDataScopeContract(input) {
110958
- const scopeResult = parseDataScopeContract(input.scopeParam);
110959
- if (!scopeResult.ok)
110960
- return scopeResult;
110961
- return {
110962
- ok: true,
110963
- deletedCount: await input.storage.deleteScope(scopeResult.scope)
110964
- };
110965
- }
110966
111027
  async function writeBlockSidecars(storage, envelope) {
110967
111028
  if (!storage.writeBlockManifest)
110968
111029
  return;
@@ -111548,6 +111609,548 @@ async function syncFileContract(input) {
111548
111609
  return contractOk({ fileId: input.fileId, status: "started" }, 202);
111549
111610
  }
111550
111611
 
111612
+ // ../core/dist/sync/tombstone.js
111613
+ var TOMBSTONE_DATA_HASH_LABEL = "vana.data-point.tombstone.v1";
111614
+ var TOMBSTONE_METADATA_HASH_LABEL = "vana.data-point.tombstone.metadata.v1";
111615
+ var TOMBSTONE_DATA_HASH = keccak256(stringToHex(TOMBSTONE_DATA_HASH_LABEL));
111616
+ var TOMBSTONE_METADATA_HASH = keccak256(stringToHex(TOMBSTONE_METADATA_HASH_LABEL));
111617
+ function isTombstoneRecord(record2) {
111618
+ return record2.dataHash.toLowerCase() === TOMBSTONE_DATA_HASH && record2.metadataHash.toLowerCase() === TOMBSTONE_METADATA_HASH;
111619
+ }
111620
+
111621
+ // ../core/dist/sync/scope-deletions.js
111622
+ var DEFAULT_SCOPE_DELETION_MAX_STALENESS_MS = 12e4;
111623
+ var DEFAULT_SCOPE_DELETION_GATEWAY_RETRY_MS = 15e3;
111624
+ var DEFAULT_MAX_LIVE_ENTRIES = 1e4;
111625
+ function createScopeDeletionTracker(options = {}) {
111626
+ const maxStalenessMs = options.maxStalenessMs ?? DEFAULT_SCOPE_DELETION_MAX_STALENESS_MS;
111627
+ const gatewayRetryMs = options.gatewayRetryMs ?? DEFAULT_SCOPE_DELETION_GATEWAY_RETRY_MS;
111628
+ const maxLiveEntries = options.maxLiveEntries ?? DEFAULT_MAX_LIVE_ENTRIES;
111629
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
111630
+ const nowMs = () => now().getTime();
111631
+ const deleted = /* @__PURE__ */ new Map();
111632
+ const live = /* @__PURE__ */ new Map();
111633
+ let lastFeedSyncMs = null;
111634
+ let lastGatewayFailureMs = null;
111635
+ const inflight = /* @__PURE__ */ new Map();
111636
+ function rememberLive(scope, at3) {
111637
+ deleted.delete(scope);
111638
+ live.delete(scope);
111639
+ live.set(scope, at3);
111640
+ while (live.size > maxLiveEntries) {
111641
+ const oldest = live.keys().next().value;
111642
+ if (oldest === void 0)
111643
+ break;
111644
+ live.delete(oldest);
111645
+ }
111646
+ }
111647
+ function rememberDeleted(scope, tombstone, source) {
111648
+ deleted.set(scope, {
111649
+ deletedAt: tombstone.deletedAt,
111650
+ version: normalizeVersion(tombstone.version),
111651
+ source,
111652
+ verifiedAtMs: nowMs()
111653
+ });
111654
+ live.delete(scope);
111655
+ }
111656
+ function isFresh(at3) {
111657
+ return at3 !== null && nowMs() - at3 <= maxStalenessMs;
111658
+ }
111659
+ function verdictFromRecord(scope, record2) {
111660
+ const deletedAt = deletionTimestamp(record2);
111661
+ if (deletedAt !== null) {
111662
+ const version4 = tombstoneVersion(record2);
111663
+ rememberDeleted(scope, { deletedAt, version: version4 }, "gateway");
111664
+ return {
111665
+ deleted: true,
111666
+ deletedAt,
111667
+ version: version4,
111668
+ source: "gateway",
111669
+ verified: true
111670
+ };
111671
+ }
111672
+ rememberLive(scope, nowMs());
111673
+ return { deleted: false, source: "gateway", verified: true };
111674
+ }
111675
+ async function lookup2(scope) {
111676
+ const feed = options.feed;
111677
+ const owner = options.serverOwner;
111678
+ if (!feed || !owner)
111679
+ return null;
111680
+ if (lastGatewayFailureMs !== null && nowMs() - lastGatewayFailureMs < gatewayRetryMs) {
111681
+ return null;
111682
+ }
111683
+ const pending = inflight.get(scope);
111684
+ if (pending)
111685
+ return pending;
111686
+ const request2 = (async () => {
111687
+ try {
111688
+ const record2 = await feed.getDataPoint({
111689
+ ownerAddress: owner,
111690
+ scope
111691
+ });
111692
+ lastGatewayFailureMs = null;
111693
+ return verdictFromRecord(scope, record2);
111694
+ } catch (err2) {
111695
+ lastGatewayFailureMs = nowMs();
111696
+ options.logger?.warn?.({
111697
+ scope,
111698
+ error: err2 instanceof Error ? err2.message : String(err2),
111699
+ retryAfterMs: gatewayRetryMs
111700
+ }, "Could not check gateway deletion state; serving last known state");
111701
+ return null;
111702
+ } finally {
111703
+ inflight.delete(scope);
111704
+ }
111705
+ })();
111706
+ inflight.set(scope, request2);
111707
+ return request2;
111708
+ }
111709
+ return {
111710
+ maxStalenessMs,
111711
+ markDeleted(scope, tombstone, source = "feed") {
111712
+ rememberDeleted(scope, tombstone, source);
111713
+ },
111714
+ markLive(scope) {
111715
+ rememberLive(scope, nowMs());
111716
+ },
111717
+ noteFeedSynced(at3, options2) {
111718
+ lastFeedSyncMs = (at3 ?? now()).getTime();
111719
+ if (!options2?.full)
111720
+ return;
111721
+ for (const tombstone of deleted.values()) {
111722
+ tombstone.verifiedAtMs = Math.max(tombstone.verifiedAtMs, lastFeedSyncMs);
111723
+ }
111724
+ },
111725
+ knownDeletion(scope) {
111726
+ const known = deleted.get(scope);
111727
+ return known === void 0 ? null : { deletedAt: known.deletedAt, version: known.version };
111728
+ },
111729
+ feedAgeMs() {
111730
+ return lastFeedSyncMs === null ? null : nowMs() - lastFeedSyncMs;
111731
+ },
111732
+ async resolve(scope, resolveOptions) {
111733
+ const known = deleted.get(scope);
111734
+ if (known !== void 0) {
111735
+ if (isFresh(known.verifiedAtMs)) {
111736
+ return {
111737
+ deleted: true,
111738
+ deletedAt: known.deletedAt,
111739
+ version: known.version,
111740
+ source: known.source,
111741
+ verified: true
111742
+ };
111743
+ }
111744
+ const rechecked = await lookup2(scope);
111745
+ if (rechecked !== null)
111746
+ return rechecked;
111747
+ return {
111748
+ deleted: true,
111749
+ deletedAt: known.deletedAt,
111750
+ version: known.version,
111751
+ source: known.source,
111752
+ verified: false
111753
+ };
111754
+ }
111755
+ if (isFresh(live.get(scope) ?? null)) {
111756
+ return { deleted: false, source: "gateway", verified: true };
111757
+ }
111758
+ const consult = resolveOptions?.consultGateway ?? "if-stale";
111759
+ if (consult === "if-stale" && isFresh(lastFeedSyncMs)) {
111760
+ return { deleted: false, source: "feed", verified: true };
111761
+ }
111762
+ return await lookup2(scope) ?? {
111763
+ deleted: false,
111764
+ source: "assumed-live",
111765
+ verified: false
111766
+ };
111767
+ }
111768
+ };
111769
+ }
111770
+ function deletionTimestamp(record2) {
111771
+ if (!record2)
111772
+ return null;
111773
+ if (record2.deletedAt)
111774
+ return record2.deletedAt;
111775
+ return isTombstoneRecord(record2) ? record2.addedAt : null;
111776
+ }
111777
+ function tombstoneVersion(record2) {
111778
+ return normalizeVersion(record2?.expectedVersion ?? null);
111779
+ }
111780
+ function normalizeVersion(value) {
111781
+ if (typeof value !== "string" || !/^\d+$/.test(value))
111782
+ return null;
111783
+ return BigInt(value) > 0n ? BigInt(value).toString() : null;
111784
+ }
111785
+ function isEntryCoveredByTombstone(entry, tombstone) {
111786
+ const version4 = normalizeVersion(tombstone.version);
111787
+ if (version4 === null)
111788
+ return true;
111789
+ const tombstoned = BigInt(version4);
111790
+ if (entry.dataPointId !== null)
111791
+ return BigInt(entry.version) <= tombstoned;
111792
+ const marker = entry.afterTombstoneVersion;
111793
+ if (marker === null || marker === void 0 || !Number.isSafeInteger(marker)) {
111794
+ return true;
111795
+ }
111796
+ return BigInt(marker) < tombstoned;
111797
+ }
111798
+
111799
+ // ../core/dist/sync/workers/delete.js
111800
+ var BLOB_DELETE_BATCH_SIZE = 15;
111801
+ function planBlobDeletions(storage, scope, tombstoneVersionValue) {
111802
+ const tombstone = { version: tombstoneVersionValue };
111803
+ const last2 = tombstoneVersionValue === null ? null : BigInt(tombstoneVersionValue);
111804
+ const keys = /* @__PURE__ */ new Set();
111805
+ const PAGE_SIZE = 500;
111806
+ for (let offset = 0; ; offset += PAGE_SIZE) {
111807
+ const entries = storage.listVersions(scope, { limit: PAGE_SIZE, offset });
111808
+ for (const entry of entries) {
111809
+ if (!isEntryCoveredByTombstone(entry, tombstone))
111810
+ continue;
111811
+ const version4 = BigInt(entry.version);
111812
+ if (last2 !== null && version4 >= 1n && version4 <= last2)
111813
+ continue;
111814
+ keys.add(version4.toString());
111815
+ }
111816
+ if (entries.length < PAGE_SIZE)
111817
+ break;
111818
+ }
111819
+ return {
111820
+ keys: [...keys].sort((a10, b10) => BigInt(a10) < BigInt(b10) ? -1 : 1),
111821
+ range: last2 === null || last2 < 1n ? null : { from: "1", to: last2.toString() }
111822
+ };
111823
+ }
111824
+ function takeFromRange(range, count) {
111825
+ const from = BigInt(range.from);
111826
+ const to3 = BigInt(range.to);
111827
+ const versions = [];
111828
+ let cursor = from;
111829
+ while (cursor <= to3 && versions.length < count) {
111830
+ versions.push(cursor.toString());
111831
+ cursor += 1n;
111832
+ }
111833
+ return {
111834
+ versions,
111835
+ rest: cursor <= to3 ? { from: cursor.toString(), to: range.to } : null
111836
+ };
111837
+ }
111838
+ function rangeSize(range) {
111839
+ return BigInt(range.to) - BigInt(range.from) + 1n;
111840
+ }
111841
+ function countPendingKeys(markers) {
111842
+ let total = 0n;
111843
+ for (const marker of markers) {
111844
+ if (marker.version !== null)
111845
+ total += 1n;
111846
+ else if (marker.range)
111847
+ total += rangeSize(marker.range);
111848
+ else
111849
+ total += 1n;
111850
+ }
111851
+ return total > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(total);
111852
+ }
111853
+ async function deleteScope(deps, scope) {
111854
+ const { storage, deleteData, pendingBlobDeletions, scopeDeletions, logger } = deps;
111855
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
111856
+ const dataPointId = deps.serverOwner ? computeDataPointId(deps.serverOwner, scope) : null;
111857
+ const result = {
111858
+ scope,
111859
+ dataPointId,
111860
+ durable: false,
111861
+ steps: {
111862
+ gateway: { status: "skipped", reason: "sync-disabled" },
111863
+ storage: { status: "skipped", reason: "sync-disabled" },
111864
+ local: { status: "skipped" }
111865
+ },
111866
+ pendingBlobDeletion: false
111867
+ };
111868
+ if (deleteData) {
111869
+ let tombstoneVersionValue = null;
111870
+ let tombstoneKnown = false;
111871
+ try {
111872
+ let outcome = await deleteData.tombstone(scope);
111873
+ if (outcome.status === "not-registered") {
111874
+ let registry2 = await registryState(deps, scope);
111875
+ if (registry2.status === "live") {
111876
+ outcome = await deleteData.tombstone(scope);
111877
+ registry2 = await registryState(deps, scope);
111878
+ }
111879
+ if (outcome.status === "not-registered" && registry2.status !== "deleted-or-absent") {
111880
+ throw registry2.status === "unknown" ? registry2.error : new Error("Scope was registered concurrently while it was being deleted; retry the delete");
111881
+ }
111882
+ }
111883
+ if (outcome.status === "not-registered") {
111884
+ result.steps.gateway = { status: "skipped", reason: "not-registered" };
111885
+ } else {
111886
+ tombstoneKnown = true;
111887
+ tombstoneVersionValue = outcome.version === null ? null : tombstoneVersion({ expectedVersion: outcome.version });
111888
+ result.steps.gateway = {
111889
+ status: "ok",
111890
+ ...outcome.status === "already-deleted" && {
111891
+ reason: "already-deleted"
111892
+ },
111893
+ version: outcome.version,
111894
+ deletedAt: outcome.deletedAt
111895
+ };
111896
+ scopeDeletions?.markDeleted(scope, {
111897
+ deletedAt: outcome.deletedAt ?? now().toISOString(),
111898
+ version: outcome.version
111899
+ }, "local-delete");
111900
+ }
111901
+ result.durable = true;
111902
+ } catch (err2) {
111903
+ const message = errorMessage2(err2);
111904
+ result.steps.gateway = { status: "failed", error: message };
111905
+ result.steps.storage = { status: "skipped", reason: "gateway-failed" };
111906
+ result.steps.local = { status: "skipped", reason: "gateway-failed" };
111907
+ logger.error({ scope, dataPointId, error: message }, "Gateway tombstone failed; scope NOT deleted (local copy kept so sync cannot resurrect a half-deleted scope)");
111908
+ return result;
111909
+ }
111910
+ const plan = planBlobDeletions(storage, scope, tombstoneVersionValue);
111911
+ const batch = plan.keys.slice(0, BLOB_DELETE_BATCH_SIZE);
111912
+ const leftovers = plan.keys.slice(BLOB_DELETE_BATCH_SIZE).map((version4) => ({ scope, version: version4 }));
111913
+ if (plan.range) {
111914
+ const taken = takeFromRange(plan.range, BLOB_DELETE_BATCH_SIZE - batch.length);
111915
+ batch.push(...taken.versions);
111916
+ if (taken.rest)
111917
+ leftovers.push({ scope, version: null, range: taken.rest });
111918
+ }
111919
+ if (tombstoneKnown && tombstoneVersionValue === null) {
111920
+ leftovers.push({ scope, version: null });
111921
+ }
111922
+ const storageStep = await deleteBlobKeys({ deleteData, pendingBlobDeletions, logger }, scope, batch, leftovers);
111923
+ result.steps.storage = storageStep.step;
111924
+ result.pendingBlobDeletion = storageStep.pending > 0;
111925
+ }
111926
+ try {
111927
+ const deletedCount = await storage.deleteScope(scope);
111928
+ result.steps.local = { status: "ok", deletedCount };
111929
+ } catch (err2) {
111930
+ const message = errorMessage2(err2);
111931
+ result.steps.local = { status: "failed", error: message };
111932
+ logger.error({ scope, error: message }, "Local scope deletion failed");
111933
+ }
111934
+ logger.info({
111935
+ scope,
111936
+ dataPointId,
111937
+ durable: result.durable,
111938
+ gateway: result.steps.gateway.status,
111939
+ storage: result.steps.storage.status,
111940
+ local: result.steps.local.status
111941
+ }, "Scope deletion finished");
111942
+ return result;
111943
+ }
111944
+ async function deleteBlobKeys(deps, scope, batch, leftovers) {
111945
+ const { deleteData, pendingBlobDeletions, logger } = deps;
111946
+ let outcome;
111947
+ try {
111948
+ outcome = batch.length > 0 && deleteData ? await deleteData.deleteBlobVersions(scope, batch) : { deleted: [], missing: [], failed: [] };
111949
+ } catch (err2) {
111950
+ outcome = {
111951
+ deleted: [],
111952
+ missing: [],
111953
+ failed: batch.map((version4) => ({ version: version4, error: errorMessage2(err2) }))
111954
+ };
111955
+ }
111956
+ const completed = [...outcome.deleted, ...outcome.missing].map((version4) => ({ scope, version: version4 }));
111957
+ const remaining = [
111958
+ ...outcome.failed.map(({ version: version4 }) => ({ scope, version: version4 })),
111959
+ ...leftovers
111960
+ ];
111961
+ const remainingKeys = countPendingKeys(remaining);
111962
+ let recorded = remainingKeys;
111963
+ if (pendingBlobDeletions) {
111964
+ try {
111965
+ if (completed.length > 0)
111966
+ await pendingBlobDeletions.remove(completed);
111967
+ if (remaining.length > 0)
111968
+ await pendingBlobDeletions.add(remaining);
111969
+ } catch (markerErr) {
111970
+ recorded = 0;
111971
+ logger.error({ scope, error: errorMessage2(markerErr), keys: remainingKeys }, "Could not record pending blob deletion markers");
111972
+ }
111973
+ } else if (remaining.length > 0) {
111974
+ recorded = 0;
111975
+ logger.error({ scope, keys: remainingKeys }, "Blob deletions left unfinished with no marker store to retry them");
111976
+ }
111977
+ const counts = {
111978
+ blobsDeleted: outcome.deleted.length,
111979
+ blobsMissing: outcome.missing.length,
111980
+ blobsPending: remainingKeys
111981
+ };
111982
+ if (outcome.failed.length > 0) {
111983
+ const first = outcome.failed[0];
111984
+ logger.warn({
111985
+ scope,
111986
+ failed: outcome.failed.length,
111987
+ pending: recorded,
111988
+ error: first.error
111989
+ }, "Storage blob deletion failed for some keys after gateway tombstone; will retry");
111990
+ return {
111991
+ step: {
111992
+ status: "failed",
111993
+ error: `${outcome.failed.length} blob delete(s) failed: ${first.error}`,
111994
+ ...counts
111995
+ },
111996
+ pending: recorded
111997
+ };
111998
+ }
111999
+ if (remaining.length > 0) {
112000
+ logger.info({ scope, ...counts }, "Storage blob deletion continues on later sync cycles (rate-limited batch)");
112001
+ return { step: { status: "deferred", ...counts }, pending: recorded };
112002
+ }
112003
+ return { step: { status: "ok", ...counts }, pending: 0 };
112004
+ }
112005
+ async function retryPendingBlobDeletions(deps) {
112006
+ const { deleteData, pendingBlobDeletions, logger } = deps;
112007
+ const result = {
112008
+ completed: [],
112009
+ superseded: [],
112010
+ failed: [],
112011
+ remaining: 0
112012
+ };
112013
+ if (!deleteData || !pendingBlobDeletions)
112014
+ return result;
112015
+ let markers = await pendingBlobDeletions.list();
112016
+ if (markers.length === 0)
112017
+ return result;
112018
+ for (const marker of markers.filter((key) => key.version === null && !key.range)) {
112019
+ const registry2 = await registryState(deps, marker.scope);
112020
+ if (registry2.status === "unknown") {
112021
+ result.failed.push({
112022
+ scope: marker.scope,
112023
+ version: null,
112024
+ error: registry2.error.message
112025
+ });
112026
+ continue;
112027
+ }
112028
+ if (registry2.status === "live") {
112029
+ await pendingBlobDeletions.remove([marker]);
112030
+ result.superseded.push(marker.scope);
112031
+ logger.warn({
112032
+ scope: marker.scope,
112033
+ dataPointId: registry2.record.id,
112034
+ version: registry2.record.expectedVersion
112035
+ }, "Scope was re-added after its tombstone; dropping the unexpanded blob deletion marker so the live version's ciphertext survives");
112036
+ continue;
112037
+ }
112038
+ const plan = planBlobDeletions(deps.storage ?? { listVersions: () => [] }, marker.scope, registry2.status === "deleted" ? registry2.version : null);
112039
+ const expanded = plan.keys.map((version4) => ({
112040
+ scope: marker.scope,
112041
+ version: version4
112042
+ }));
112043
+ if (plan.range) {
112044
+ expanded.push({ scope: marker.scope, version: null, range: plan.range });
112045
+ }
112046
+ await pendingBlobDeletions.remove([marker]);
112047
+ await pendingBlobDeletions.add(expanded);
112048
+ }
112049
+ markers = await pendingBlobDeletions.list();
112050
+ let budget = BLOB_DELETE_BATCH_SIZE;
112051
+ const byScope = /* @__PURE__ */ new Map();
112052
+ const exactOrigin = /* @__PURE__ */ new Set();
112053
+ const advancedRanges = [];
112054
+ const enqueue = (scope, version4) => {
112055
+ const versions = byScope.get(scope) ?? [];
112056
+ versions.push(version4);
112057
+ byScope.set(scope, versions);
112058
+ };
112059
+ for (const marker of markers) {
112060
+ if (budget === 0)
112061
+ break;
112062
+ if (marker.version !== null) {
112063
+ enqueue(marker.scope, marker.version);
112064
+ exactOrigin.add(`${marker.scope}\0${marker.version}`);
112065
+ budget -= 1;
112066
+ }
112067
+ }
112068
+ for (const marker of markers) {
112069
+ if (budget === 0)
112070
+ break;
112071
+ if (marker.version === null && marker.range) {
112072
+ const taken = takeFromRange(marker.range, budget);
112073
+ for (const version4 of taken.versions)
112074
+ enqueue(marker.scope, version4);
112075
+ budget -= taken.versions.length;
112076
+ advancedRanges.push({
112077
+ old: marker,
112078
+ next: taken.rest ? { scope: marker.scope, version: null, range: taken.rest } : null
112079
+ });
112080
+ }
112081
+ }
112082
+ for (const [scope, versions] of byScope) {
112083
+ let outcome;
112084
+ try {
112085
+ outcome = await deleteData.deleteBlobVersions(scope, versions);
112086
+ } catch (err2) {
112087
+ const message = errorMessage2(err2);
112088
+ outcome = {
112089
+ deleted: [],
112090
+ missing: [],
112091
+ failed: versions.map((version4) => ({ version: version4, error: message }))
112092
+ };
112093
+ }
112094
+ const completed = [...outcome.deleted, ...outcome.missing].filter((version4) => exactOrigin.has(`${scope}\0${version4}`)).map((version4) => ({ scope, version: version4 }));
112095
+ if (completed.length > 0)
112096
+ await pendingBlobDeletions.remove(completed);
112097
+ result.completed.push(...[...outcome.deleted, ...outcome.missing].map((version4) => ({ scope, version: version4 })));
112098
+ const failedFromRange = outcome.failed.filter(({ version: version4 }) => !exactOrigin.has(`${scope}\0${version4}`)).map(({ version: version4 }) => ({ scope, version: version4 }));
112099
+ if (failedFromRange.length > 0) {
112100
+ await pendingBlobDeletions.add(failedFromRange);
112101
+ }
112102
+ for (const failure of outcome.failed) {
112103
+ result.failed.push({ scope, ...failure });
112104
+ }
112105
+ if (outcome.deleted.length + outcome.missing.length > 0) {
112106
+ logger.info({
112107
+ scope,
112108
+ deleted: outcome.deleted.length,
112109
+ missing: outcome.missing.length
112110
+ }, "Completed pending blob deletions");
112111
+ }
112112
+ if (outcome.failed.length > 0) {
112113
+ logger.warn({
112114
+ scope,
112115
+ failed: outcome.failed.length,
112116
+ error: outcome.failed[0].error
112117
+ }, "Pending blob deletion failed again");
112118
+ }
112119
+ }
112120
+ for (const { old, next } of advancedRanges) {
112121
+ await pendingBlobDeletions.remove([old]);
112122
+ if (next)
112123
+ await pendingBlobDeletions.add([next]);
112124
+ }
112125
+ result.remaining = (await pendingBlobDeletions.list()).length;
112126
+ return result;
112127
+ }
112128
+ async function registryState(deps, scope) {
112129
+ if (!deps.dataPointFeed || !deps.serverOwner) {
112130
+ return { status: "deleted-or-absent" };
112131
+ }
112132
+ let record2;
112133
+ try {
112134
+ record2 = await deps.dataPointFeed.getDataPoint({
112135
+ ownerAddress: deps.serverOwner,
112136
+ scope
112137
+ });
112138
+ } catch (err2) {
112139
+ return {
112140
+ status: "unknown",
112141
+ error: err2 instanceof Error ? err2 : new Error(String(err2))
112142
+ };
112143
+ }
112144
+ if (record2 === null)
112145
+ return { status: "deleted-or-absent" };
112146
+ if (deletionTimestamp(record2) === null)
112147
+ return { status: "live", record: record2 };
112148
+ return { status: "deleted", version: tombstoneVersion(record2) };
112149
+ }
112150
+ function errorMessage2(err2) {
112151
+ return err2 instanceof Error ? err2.message : String(err2);
112152
+ }
112153
+
111551
112154
  // ../core/dist/payment/x402.js
111552
112155
  function generateRecordId() {
111553
112156
  const bytes2 = new Uint8Array(32);
@@ -112028,6 +112631,51 @@ async function handleX402Cycle(input) {
112028
112631
  function collectedAt(now) {
112029
112632
  return now().toISOString().replace(/\.\d{3}Z$/, "Z");
112030
112633
  }
112634
+ async function resolveReadDeletion(deps, scope, entry) {
112635
+ if (!deps.scopeDeletions)
112636
+ return null;
112637
+ const verdict = await deps.scopeDeletions.resolve(scope, {
112638
+ consultGateway: entry ? "if-stale" : "always"
112639
+ });
112640
+ if (!verdict.deleted)
112641
+ return null;
112642
+ if (entry && !isEntryCoveredByTombstone(entry, verdict)) {
112643
+ return null;
112644
+ }
112645
+ return {
112646
+ scope,
112647
+ dataPointId: deps.serverOwner ? computeDataPointId(deps.serverOwner, scope) : null,
112648
+ deletedAt: verdict.deletedAt
112649
+ };
112650
+ }
112651
+ async function assertScopeNotDeleted(deps, scope, entry) {
112652
+ const deletion = await resolveReadDeletion(deps, scope, entry);
112653
+ if (deletion)
112654
+ throw new DataDeletedError(deletion);
112655
+ }
112656
+ function discoveryVisibility(deps) {
112657
+ if (!deps.scopeDeletions)
112658
+ return void 0;
112659
+ return async (scope, entry) => await resolveReadDeletion(deps, scope, entry) === null;
112660
+ }
112661
+ async function ingestTombstoneMarker(deps, scope) {
112662
+ if (!deps.scopeDeletions)
112663
+ return null;
112664
+ const verdict = await deps.scopeDeletions.resolve(scope);
112665
+ if (!verdict.deleted || verdict.version === null)
112666
+ return null;
112667
+ const version4 = Number(verdict.version);
112668
+ return Number.isSafeInteger(version4) ? version4 : null;
112669
+ }
112670
+ function apiLoggerAsLogger(logger) {
112671
+ const noop = () => void 0;
112672
+ return {
112673
+ debug: (payload, message) => (logger?.debug ?? noop)(payload, message ?? ""),
112674
+ info: (payload, message) => (logger?.info ?? noop)(payload, message ?? ""),
112675
+ warn: (payload, message) => (logger?.warn ?? noop)(payload, message ?? ""),
112676
+ error: (payload, message) => (logger?.error ?? noop)(payload, message ?? "")
112677
+ };
112678
+ }
112031
112679
  function notifyNewData(syncManager) {
112032
112680
  if (!syncManager)
112033
112681
  return;
@@ -112083,24 +112731,6 @@ function resolveLineageGrantView(authResult) {
112083
112731
  }
112084
112732
  return { grantId };
112085
112733
  }
112086
- async function deleteOneScope(deps, scope) {
112087
- if (deps.syncManager?.deleteScopeRemote) {
112088
- try {
112089
- await deps.syncManager.deleteScopeRemote(scope);
112090
- } catch (err2) {
112091
- deps.logger?.info?.({ scope, error: err2.message }, "Remote scope deletion failed; proceeding with local delete");
112092
- }
112093
- }
112094
- const result = await deleteDataScopeContract({
112095
- storage: deps.storage,
112096
- scopeParam: scope
112097
- });
112098
- if (!result.ok) {
112099
- throw new ProtocolError(result.status, result.body.error, result.body.message);
112100
- }
112101
- deps.logger?.info?.({ scope, deletedCount: result.deletedCount }, "Scope deleted");
112102
- return result.deletedCount;
112103
- }
112104
112734
  async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112105
112735
  return withApiErrors(async () => {
112106
112736
  const url2 = new URL(request2.url);
@@ -112113,7 +112743,8 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112113
112743
  storage: deps.storage,
112114
112744
  scopePrefix: url2.searchParams.get("scopePrefix") ?? void 0,
112115
112745
  limit: normalizeLimit2(url2.searchParams.get("limit"), 20),
112116
- offset: normalizeLimit2(url2.searchParams.get("offset"), 0)
112746
+ offset: normalizeLimit2(url2.searchParams.get("offset"), 0),
112747
+ isVisible: discoveryVisibility(deps)
112117
112748
  });
112118
112749
  return jsonResponse(result.response);
112119
112750
  }
@@ -112122,14 +112753,18 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112122
112753
  if (request2.method !== "GET")
112123
112754
  return methodNotAllowed();
112124
112755
  await deps.auth.authorizeBuilderList(request2);
112125
- const result = listDataVersionsContract({
112756
+ const result = await listDataVersionsContract({
112126
112757
  storage: deps.storage,
112127
112758
  scopeParam: decodePathPart(parts[0]),
112128
112759
  limit: normalizeLimit2(url2.searchParams.get("limit"), 20),
112129
- offset: normalizeLimit2(url2.searchParams.get("offset"), 0)
112760
+ offset: normalizeLimit2(url2.searchParams.get("offset"), 0),
112761
+ isVisible: discoveryVisibility(deps)
112130
112762
  });
112131
112763
  if (!result.ok)
112132
112764
  return contractErrorResponse(result);
112765
+ if (result.response.total === 0) {
112766
+ await assertScopeNotDeleted(deps, result.scope, void 0);
112767
+ }
112133
112768
  return jsonResponse(result.response);
112134
112769
  }
112135
112770
  if ((parts.length === 2 || parts.length === 3) && parts[1] === "lineage") {
@@ -112211,6 +112846,7 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112211
112846
  fileId: url2.searchParams.get("fileId") ?? selectedEntry?.fileId ?? void 0,
112212
112847
  at: url2.searchParams.get("at") ?? void 0
112213
112848
  });
112849
+ await assertScopeNotDeleted(deps, scopeResult.scope, selectedEntry);
112214
112850
  const isOwnerSignal = authResult?.grantId === "owner" || authResult?.grantId === "policy-bypass";
112215
112851
  const builder = authResult?.builder;
112216
112852
  const resolvedGrantId = !isOwnerSignal && authResult?.grantId ? authResult.grantId : void 0;
@@ -112265,8 +112901,12 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112265
112901
  fileId: url2.searchParams.get("fileId") ?? void 0,
112266
112902
  at: url2.searchParams.get("at") ?? void 0
112267
112903
  });
112268
- if (!result.ok)
112904
+ if (!result.ok) {
112905
+ if (result.status === 404) {
112906
+ await assertScopeNotDeleted(deps, scopeResult.scope, void 0);
112907
+ }
112269
112908
  return contractErrorResponse(result);
112909
+ }
112270
112910
  const logId = deps.createLogId?.() ?? crypto.randomUUID();
112271
112911
  const timestamp = (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
112272
112912
  const ipAddress = request2.headers.get("x-forwarded-for") ?? request2.headers.get("x-real-ip") ?? "unknown";
@@ -112346,6 +112986,7 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112346
112986
  return failWrite(contractErrorResponse(scopeResult));
112347
112987
  const collectedAtValue = collectedAt(deps.now ?? (() => /* @__PURE__ */ new Date()));
112348
112988
  const status2 = deps.syncManager ? "syncing" : "stored";
112989
+ const afterTombstoneVersion = await ingestTombstoneMarker(deps, scopeResult.scope);
112349
112990
  const logBuilderWrite = async () => {
112350
112991
  if (!writeAuth)
112351
112992
  return;
@@ -112383,7 +113024,8 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112383
113024
  collectedAt: collectedAtValue,
112384
113025
  status: status2,
112385
113026
  attribution: writeAuth?.attribution,
112386
- lineage: lineage2
113027
+ lineage: lineage2,
113028
+ afterTombstoneVersion
112387
113029
  });
112388
113030
  if (!result2.ok)
112389
113031
  return failWrite(contractErrorResponse(result2));
@@ -112411,7 +113053,8 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112411
113053
  collectedAt: collectedAtValue,
112412
113054
  status: status2,
112413
113055
  attribution: writeAuth?.attribution,
112414
- lineage
113056
+ lineage,
113057
+ afterTombstoneVersion
112415
113058
  });
112416
113059
  if (!result.ok)
112417
113060
  return failWrite(contractErrorResponse(result));
@@ -112450,8 +113093,44 @@ async function handlePersonalServerDataRequest(request2, deps, options = {}) {
112450
113093
  if (cascade === "lineage") {
112451
113094
  throw new LineageCascadeUnavailableError({ scope: parsed.scope });
112452
113095
  }
112453
- await deleteOneScope(deps, parsed.scope);
112454
- return new Response(null, { status: 204 });
113096
+ const result = deps.syncManager?.deleteScope ? await deps.syncManager.deleteScope(parsed.scope) : await deleteScope({
113097
+ storage: deps.storage,
113098
+ serverOwner: deps.serverOwner,
113099
+ deleteData: null,
113100
+ logger: apiLoggerAsLogger(deps.logger)
113101
+ }, parsed.scope);
113102
+ if (result.steps.gateway.status === "failed") {
113103
+ throw new DeleteTombstoneFailedError({
113104
+ scope: parsed.scope,
113105
+ result
113106
+ });
113107
+ }
113108
+ try {
113109
+ await deps.accessLogWriter.write({
113110
+ logId: deps.createLogId?.() ?? crypto.randomUUID(),
113111
+ grantId: "owner",
113112
+ builder: deps.serverOwner ?? "owner",
113113
+ action: "delete",
113114
+ scope: parsed.scope,
113115
+ timestamp: (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
113116
+ ipAddress: request2.headers.get("x-forwarded-for") ?? request2.headers.get("x-real-ip") ?? "unknown",
113117
+ userAgent: request2.headers.get("user-agent") ?? "unknown"
113118
+ });
113119
+ } catch (err2) {
113120
+ deps.logger?.warn?.({
113121
+ scope: parsed.scope,
113122
+ error: err2 instanceof Error ? err2.message : String(err2)
113123
+ }, "Delete access-log entry failed; scope already deleted");
113124
+ }
113125
+ deps.logger?.info?.({
113126
+ scope: parsed.scope,
113127
+ durable: result.durable,
113128
+ gateway: result.steps.gateway.status,
113129
+ storage: result.steps.storage.status,
113130
+ local: result.steps.local.status,
113131
+ deletedCount: result.steps.local.deletedCount
113132
+ }, "Scope deleted");
113133
+ return jsonResponse(result, { status: 200 });
112455
113134
  }
112456
113135
  return methodNotAllowed();
112457
113136
  });
@@ -112761,6 +113440,16 @@ var McpDataReadError = class extends Error {
112761
113440
  };
112762
113441
  function createMcpDataReadClient(options) {
112763
113442
  const basePath = options.basePath ?? "/v1/data";
113443
+ async function assertScopeReadable(scope, entry) {
113444
+ try {
113445
+ await assertScopeNotDeleted(options.dataApiDeps, scope, entry);
113446
+ } catch (err2) {
113447
+ if (err2 instanceof ProtocolError) {
113448
+ throw new McpDataReadError(err2.code, err2.toJSON());
113449
+ }
113450
+ throw err2;
113451
+ }
113452
+ }
112764
113453
  async function authorizeScopeRead(params) {
112765
113454
  const safeScope = encodeURIComponent(params.scope);
112766
113455
  const signingUri = `${basePath}/${safeScope}`;
@@ -112848,6 +113537,9 @@ function createMcpDataReadClient(options) {
112848
113537
  const entry = storage.findEntry({ scope });
112849
113538
  if (!entry)
112850
113539
  return null;
113540
+ if (await resolveReadDeletion(options.dataApiDeps, scope, entry)) {
113541
+ return null;
113542
+ }
112851
113543
  const hasBlocks = typeof storage.hasScopeBlocks === "function" ? await storage.hasScopeBlocks(scope, entry.collectedAt) : false;
112852
113544
  return {
112853
113545
  scope,
@@ -112876,6 +113568,7 @@ function createMcpDataReadClient(options) {
112876
113568
  message: `No data found for scope "${scope}"`
112877
113569
  });
112878
113570
  }
113571
+ await assertScopeReadable(scope, selectedEntry);
112879
113572
  const { request: request2, authResult } = await authorizeScopeRead({
112880
113573
  scope,
112881
113574
  grantId,
@@ -112933,6 +113626,7 @@ function createMcpDataReadClient(options) {
112933
113626
  message: `No data found for scope "${scope}"`
112934
113627
  });
112935
113628
  }
113629
+ await assertScopeReadable(scope, selectedEntry);
112936
113630
  const { request: request2, authResult } = await authorizeScopeRead({
112937
113631
  scope,
112938
113632
  grantId,
@@ -116596,15 +117290,15 @@ var makeIssue = (params) => {
116596
117290
  message: issueData.message
116597
117291
  };
116598
117292
  }
116599
- let errorMessage2 = "";
117293
+ let errorMessage3 = "";
116600
117294
  const maps = errorMaps.filter((m10) => !!m10).slice().reverse();
116601
117295
  for (const map2 of maps) {
116602
- errorMessage2 = map2(fullIssue, { data, defaultError: errorMessage2 }).message;
117296
+ errorMessage3 = map2(fullIssue, { data, defaultError: errorMessage3 }).message;
116603
117297
  }
116604
117298
  return {
116605
117299
  ...issueData,
116606
117300
  path: fullPath,
116607
- message: errorMessage2
117301
+ message: errorMessage3
116608
117302
  };
116609
117303
  };
116610
117304
  function addIssueToContext(ctx, issueData) {
@@ -121884,19 +122578,19 @@ var getRefs = (options) => {
121884
122578
  };
121885
122579
 
121886
122580
  // ../../node_modules/zod-to-json-schema/dist/esm/errorMessages.js
121887
- function addErrorMessage(res, key, errorMessage2, refs) {
122581
+ function addErrorMessage(res, key, errorMessage3, refs) {
121888
122582
  if (!refs?.errorMessages)
121889
122583
  return;
121890
- if (errorMessage2) {
122584
+ if (errorMessage3) {
121891
122585
  res.errorMessage = {
121892
122586
  ...res.errorMessage,
121893
- [key]: errorMessage2
122587
+ [key]: errorMessage3
121894
122588
  };
121895
122589
  }
121896
122590
  }
121897
- function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
122591
+ function setResponseValueAndErrors(res, key, value, errorMessage3, refs) {
121898
122592
  res[key] = value;
121899
- addErrorMessage(res, key, errorMessage2, refs);
122593
+ addErrorMessage(res, key, errorMessage3, refs);
121900
122594
  }
121901
122595
 
121902
122596
  // ../../node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
@@ -123207,8 +123901,8 @@ var Protocol = class {
123207
123901
  if (queuedMessage.type === "response") {
123208
123902
  resolver(message);
123209
123903
  } else {
123210
- const errorMessage2 = message;
123211
- const error51 = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
123904
+ const errorMessage3 = message;
123905
+ const error51 = new McpError(errorMessage3.error.code, errorMessage3.error.message, errorMessage3.error.data);
123212
123906
  resolver(error51);
123213
123907
  }
123214
123908
  } else {
@@ -124508,23 +125202,23 @@ var Server = class extends Protocol {
124508
125202
  const wrappedHandler = async (request2, extra) => {
124509
125203
  const validatedRequest = safeParse3(CallToolRequestSchema, request2);
124510
125204
  if (!validatedRequest.success) {
124511
- const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
124512
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
125205
+ const errorMessage3 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
125206
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage3}`);
124513
125207
  }
124514
125208
  const { params } = validatedRequest.data;
124515
125209
  const result = await Promise.resolve(handler(request2, extra));
124516
125210
  if (params.task) {
124517
125211
  const taskValidationResult = safeParse3(CreateTaskResultSchema, result);
124518
125212
  if (!taskValidationResult.success) {
124519
- const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
124520
- throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
125213
+ const errorMessage3 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
125214
+ throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage3}`);
124521
125215
  }
124522
125216
  return taskValidationResult.data;
124523
125217
  }
124524
125218
  const validationResult = safeParse3(CallToolResultSchema, result);
124525
125219
  if (!validationResult.success) {
124526
- const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
124527
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
125220
+ const errorMessage3 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
125221
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage3}`);
124528
125222
  }
124529
125223
  return validationResult.data;
124530
125224
  };
@@ -125240,12 +125934,12 @@ var McpServer = class {
125240
125934
  * @param errorMessage - The error message.
125241
125935
  * @returns The tool error result.
125242
125936
  */
125243
- createToolError(errorMessage2) {
125937
+ createToolError(errorMessage3) {
125244
125938
  return {
125245
125939
  content: [
125246
125940
  {
125247
125941
  type: "text",
125248
- text: errorMessage2
125942
+ text: errorMessage3
125249
125943
  }
125250
125944
  ],
125251
125945
  isError: true
@@ -125263,8 +125957,8 @@ var McpServer = class {
125263
125957
  const parseResult = await safeParseAsync3(schemaToParse, args);
125264
125958
  if (!parseResult.success) {
125265
125959
  const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
125266
- const errorMessage2 = getParseErrorMessage(error51);
125267
- throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage2}`);
125960
+ const errorMessage3 = getParseErrorMessage(error51);
125961
+ throw new McpError(ErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage3}`);
125268
125962
  }
125269
125963
  return parseResult.data;
125270
125964
  }
@@ -125288,8 +125982,8 @@ var McpServer = class {
125288
125982
  const parseResult = await safeParseAsync3(outputObj, result.structuredContent);
125289
125983
  if (!parseResult.success) {
125290
125984
  const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
125291
- const errorMessage2 = getParseErrorMessage(error51);
125292
- throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage2}`);
125985
+ const errorMessage3 = getParseErrorMessage(error51);
125986
+ throw new McpError(ErrorCode.InvalidParams, `Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage3}`);
125293
125987
  }
125294
125988
  }
125295
125989
  /**
@@ -125501,8 +126195,8 @@ var McpServer = class {
125501
126195
  const parseResult = await safeParseAsync3(argsObj, request2.params.arguments);
125502
126196
  if (!parseResult.success) {
125503
126197
  const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
125504
- const errorMessage2 = getParseErrorMessage(error51);
125505
- throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request2.params.name}: ${errorMessage2}`);
126198
+ const errorMessage3 = getParseErrorMessage(error51);
126199
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request2.params.name}: ${errorMessage3}`);
125506
126200
  }
125507
126201
  const args = parseResult.data;
125508
126202
  const cb2 = prompt.callback;
@@ -127598,6 +128292,7 @@ function createPsLiteRuntime(options) {
127598
128292
  accessLogWriter,
127599
128293
  readFulfillmentReporter: options.readFulfillmentReporter,
127600
128294
  syncManager: options.syncManager ?? null,
128295
+ scopeDeletions: options.scopeDeletions,
127601
128296
  now,
127602
128297
  createLogId,
127603
128298
  // x402 payment enforcement for builder reads. Only engages with a
@@ -128074,6 +128769,45 @@ var OrphanedEntryError = class extends Error {
128074
128769
  this.name = "OrphanedEntryError";
128075
128770
  }
128076
128771
  };
128772
+ var DeletedScopeEntryError = class extends Error {
128773
+ constructor(path, deletedAt) {
128774
+ super(`Dropped local entry for a scope deleted at ${deletedAt}: ${path}`);
128775
+ this.name = "DeletedScopeEntryError";
128776
+ }
128777
+ };
128778
+ async function dropIfCoveredByDeletion(deps, entry, tombstone, deletedAt, uploaded) {
128779
+ const version4 = tombstoneVersion(tombstone);
128780
+ if (!isEntryCoveredByTombstone(entry, { version: version4 }))
128781
+ return;
128782
+ if (uploaded) {
128783
+ try {
128784
+ await deps.storageAdapter.delete(uploaded.url);
128785
+ } catch (err2) {
128786
+ const message = err2 instanceof Error ? err2.message : String(err2);
128787
+ if (deps.pendingBlobDeletions) {
128788
+ await deps.pendingBlobDeletions.add([
128789
+ { scope: entry.scope, version: String(entry.version) }
128790
+ ]);
128791
+ }
128792
+ deps.logger.warn({
128793
+ scope: entry.scope,
128794
+ url: uploaded.url,
128795
+ error: message,
128796
+ queuedForCleanup: Boolean(deps.pendingBlobDeletions)
128797
+ }, "Could not delete the ciphertext uploaded for an entry the tombstone covers");
128798
+ }
128799
+ }
128800
+ await deps.storage.deleteVersion(entry.scope, entry.collectedAt);
128801
+ deps.logger.warn({
128802
+ path: entry.path,
128803
+ scope: entry.scope,
128804
+ deletedAt,
128805
+ tombstoneVersion: version4,
128806
+ entryVersion: entry.version,
128807
+ afterTombstoneVersion: entry.afterTombstoneVersion ?? null
128808
+ }, "Dropped unsynced local entry: the gateway reports its scope as deleted");
128809
+ throw new DeletedScopeEntryError(entry.path, deletedAt);
128810
+ }
128077
128811
  function isMissingPayloadError(err2) {
128078
128812
  return err2 instanceof Error && err2.code === "ENOENT";
128079
128813
  }
@@ -128092,6 +128826,20 @@ async function uploadOne(deps, entry) {
128092
128826
  }
128093
128827
  throw err2;
128094
128828
  }
128829
+ let registerVersion = BigInt(entry.version);
128830
+ if (!entry.dataPointId && deps.dataPointFeed) {
128831
+ const remote = await deps.dataPointFeed.getDataPoint({
128832
+ ownerAddress: serverOwner,
128833
+ scope: entry.scope
128834
+ });
128835
+ const deletedAt = deletionTimestamp(remote);
128836
+ if (remote && deletedAt !== null) {
128837
+ await dropIfCoveredByDeletion(deps, entry, remote, deletedAt);
128838
+ const afterTombstone = BigInt(remote.expectedVersion) + 1n;
128839
+ if (afterTombstone > registerVersion)
128840
+ registerVersion = afterTombstone;
128841
+ }
128842
+ }
128095
128843
  const scopeKey = deriveScopeKey(masterKey, entry.scope);
128096
128844
  const scopeKeyHex = uint8ToHex(scopeKey);
128097
128845
  const plaintext = new TextEncoder().encode(JSON.stringify(envelope));
@@ -128102,7 +128850,7 @@ async function uploadOne(deps, entry) {
128102
128850
  collectedAt: entry.collectedAt,
128103
128851
  sizeBytes: encrypted.byteLength
128104
128852
  })));
128105
- const storageKey = `${entry.scope}/${entry.version}`;
128853
+ const storageKey = `${entry.scope}/${registerVersion}`;
128106
128854
  let url2 = await storageAdapter.upload(storageKey, encrypted);
128107
128855
  let dataPointId;
128108
128856
  if (entry.dataPointId) {
@@ -128152,11 +128900,23 @@ async function uploadOne(deps, entry) {
128152
128900
  return id2;
128153
128901
  };
128154
128902
  try {
128155
- dataPointId = await registerAt(BigInt(entry.version));
128903
+ dataPointId = await registerAt(registerVersion);
128904
+ if (registerVersion !== BigInt(entry.version)) {
128905
+ await storage.updateEntryVersion(entry.path, Number(registerVersion));
128906
+ }
128156
128907
  } catch (err2) {
128157
128908
  if (!isStaleVersionConflict(err2))
128158
128909
  throw err2;
128159
- const record2 = await gateway.getDataPoint(computeDataPointId(serverOwner, entry.scope));
128910
+ const record2 = deps.dataPointFeed ? await deps.dataPointFeed.getDataPoint({
128911
+ ownerAddress: serverOwner,
128912
+ scope: entry.scope
128913
+ }) : await gateway.getDataPoint(computeDataPointId(serverOwner, entry.scope));
128914
+ const conflictDeletedAt = record2 ? deletionTimestamp(record2) : null;
128915
+ if (record2 && conflictDeletedAt !== null) {
128916
+ await dropIfCoveredByDeletion(deps, entry, record2, conflictDeletedAt, {
128917
+ url: url2
128918
+ });
128919
+ }
128160
128920
  if (record2 && record2.dataHash.toLowerCase() === dataHash.toLowerCase()) {
128161
128921
  dataPointId = record2.id;
128162
128922
  const adoptedVersion = Number(record2.expectedVersion);
@@ -128182,6 +128942,7 @@ async function uploadOne(deps, entry) {
128182
128942
  }
128183
128943
  }
128184
128944
  await storage.updateDataPointId(entry.path, dataPointId);
128945
+ deps.scopeDeletions?.markLive(entry.scope);
128185
128946
  }
128186
128947
  logger.info({
128187
128948
  path: entry.path,
@@ -128201,7 +128962,7 @@ async function uploadAll(deps, options) {
128201
128962
  const result = await uploadOne(deps, entry);
128202
128963
  results.push(result);
128203
128964
  } catch (err2) {
128204
- if (err2 instanceof OrphanedEntryError) {
128965
+ if (err2 instanceof OrphanedEntryError || err2 instanceof DeletedScopeEntryError) {
128205
128966
  continue;
128206
128967
  }
128207
128968
  const error51 = err2;
@@ -128230,6 +128991,101 @@ function parseGatewayNextVersion(message) {
128230
128991
  return null;
128231
128992
  }
128232
128993
 
128994
+ // ../core/dist/sync/data-point-feed.js
128995
+ function createGatewayDataPointFeed(options) {
128996
+ const base = options.gatewayUrl.replace(/\/+$/, "");
128997
+ const fetchImpl = options.fetch ?? globalThis.fetch;
128998
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
128999
+ return {
129000
+ async listDataPointsByOwner(owner, cursor, listOptions) {
129001
+ const params = new URLSearchParams({ user: owner });
129002
+ if (cursor !== null)
129003
+ params.set("cursor", cursor);
129004
+ if (listOptions?.since)
129005
+ params.set("since", listOptions.since);
129006
+ if (listOptions?.limit !== void 0) {
129007
+ params.set("limit", String(listOptions.limit));
129008
+ }
129009
+ if (listOptions?.includeDeleted)
129010
+ params.set("includeDeleted", "true");
129011
+ const res = await fetchImpl(`${base}/v1/data?${params.toString()}`);
129012
+ if (!res.ok) {
129013
+ throw new Error(`Gateway error: ${res.status} ${res.statusText}`);
129014
+ }
129015
+ const envelope = await res.json();
129016
+ const nextCursor = envelope.pagination?.hasMore === false ? null : envelope.pagination?.nextCursor ?? null;
129017
+ const rows = envelope.data?.dataPoints ?? [];
129018
+ return {
129019
+ dataPoints: rows.map((row) => normalizeRecord(row)),
129020
+ cursor: nextCursor
129021
+ };
129022
+ },
129023
+ async getDataPoint(input) {
129024
+ const dataPointId = computeDataPointId(input.ownerAddress, input.scope);
129025
+ const res = await fetchImpl(`${base}/v1/data/${dataPointId}?includeDeleted=true`);
129026
+ if (res.status === 404)
129027
+ return null;
129028
+ if (res.status === 410) {
129029
+ const body2 = await res.json().catch(() => null);
129030
+ const echoed = unwrap(body2);
129031
+ const deletedAt = stringField(echoed, "deletedAt") ?? now().toISOString();
129032
+ return {
129033
+ id: dataPointId,
129034
+ ownerAddress: input.ownerAddress,
129035
+ scope: input.scope,
129036
+ dataHash: stringField(echoed, "dataHash") ?? TOMBSTONE_DATA_HASH,
129037
+ metadataHash: stringField(echoed, "metadataHash") ?? TOMBSTONE_METADATA_HASH,
129038
+ expectedVersion: stringField(echoed, "expectedVersion") ?? "0",
129039
+ addedAt: stringField(echoed, "addedAt") ?? deletedAt,
129040
+ deletedAt
129041
+ };
129042
+ }
129043
+ if (!res.ok) {
129044
+ throw new Error(`Gateway error: ${res.status} ${res.statusText}`);
129045
+ }
129046
+ const body = await res.json();
129047
+ return normalizeRecord(unwrap(body));
129048
+ }
129049
+ };
129050
+ }
129051
+ function unwrap(body) {
129052
+ if (typeof body !== "object" || body === null)
129053
+ return null;
129054
+ const record2 = body;
129055
+ if (typeof record2.data === "object" && record2.data !== null) {
129056
+ return record2.data;
129057
+ }
129058
+ return record2;
129059
+ }
129060
+ function stringField(record2, key) {
129061
+ const value = record2?.[key];
129062
+ return typeof value === "string" ? value : void 0;
129063
+ }
129064
+ function normalizeRecord(row) {
129065
+ const record2 = row ?? {};
129066
+ const deletedAt = record2.deletedAt;
129067
+ return {
129068
+ ...record2,
129069
+ deletedAt: typeof deletedAt === "string" ? deletedAt : null
129070
+ };
129071
+ }
129072
+ function feedFromGatewayClient(gateway) {
129073
+ return {
129074
+ async listDataPointsByOwner(owner, cursor, listOptions) {
129075
+ const { includeDeleted: _includeDeleted, ...sdkOptions } = listOptions ?? {};
129076
+ const result = Object.keys(sdkOptions).length > 0 ? await gateway.listDataPointsByOwner(owner, cursor, sdkOptions) : await gateway.listDataPointsByOwner(owner, cursor);
129077
+ return {
129078
+ dataPoints: result.dataPoints.map((row) => normalizeRecord(row)),
129079
+ cursor: result.cursor
129080
+ };
129081
+ },
129082
+ async getDataPoint(input) {
129083
+ const row = await gateway.getDataPoint(computeDataPointId(input.ownerAddress, input.scope));
129084
+ return row === null ? null : normalizeRecord(row);
129085
+ }
129086
+ };
129087
+ }
129088
+
128233
129089
  // ../core/dist/sync/issues.js
128234
129090
  var DETERMINISTIC_STAGES = /* @__PURE__ */ new Set([
128235
129091
  "openpgp_parse",
@@ -128567,10 +129423,43 @@ async function downloadAll(deps, options = {}) {
128567
129423
  if (options.fullReconcile || repairSummary.missingEnvelopeEntries > 0) {
128568
129424
  options.retryMemory?.onListingReset();
128569
129425
  }
128570
- const { dataPoints, cursor: nextCursor } = await gateway.listDataPointsByOwner(serverOwner, lastCursor);
129426
+ const feed = deps.dataPointFeed ?? feedFromGatewayClient(gateway);
129427
+ const { dataPoints, cursor: nextCursor } = await feed.listDataPointsByOwner(serverOwner, lastCursor, { includeDeleted: true });
129428
+ if (deps.scopeDeletions) {
129429
+ for (const dataPoint of dataPoints) {
129430
+ const deletedAt = deletionTimestamp(dataPoint);
129431
+ if (deletedAt !== null) {
129432
+ deps.scopeDeletions.markDeleted(dataPoint.scope, {
129433
+ deletedAt,
129434
+ version: tombstoneVersion(dataPoint)
129435
+ });
129436
+ } else {
129437
+ deps.scopeDeletions.markLive(dataPoint.scope);
129438
+ }
129439
+ }
129440
+ if (nextCursor === null) {
129441
+ deps.scopeDeletions.noteFeedSynced(void 0, {
129442
+ full: lastCursor === null
129443
+ });
129444
+ }
129445
+ }
128571
129446
  const results = [];
128572
129447
  let failed = false;
128573
129448
  for (const dataPoint of dataPoints) {
129449
+ const deletedAt = deletionTimestamp(dataPoint);
129450
+ if (deletedAt !== null) {
129451
+ try {
129452
+ await reconcileDeletedDataPoint(deps, dataPoint, deletedAt);
129453
+ } catch (err2) {
129454
+ logger.error({
129455
+ dataPointId: dataPoint.id,
129456
+ scope: dataPoint.scope,
129457
+ error: err2.message
129458
+ }, "Failed to reconcile deleted data point locally");
129459
+ failed = true;
129460
+ }
129461
+ continue;
129462
+ }
128574
129463
  const retryKey = downloadRetryKey(dataPoint);
128575
129464
  const decision = options.retryMemory?.decide(retryKey) ?? "attempt";
128576
129465
  if (decision === "give-up") {
@@ -128626,6 +129515,49 @@ async function downloadAll(deps, options = {}) {
128626
129515
  }
128627
129516
  return results;
128628
129517
  }
129518
+ async function reconcileDeletedDataPoint(deps, record2, deletedAt) {
129519
+ const tombstone = { version: tombstoneVersion(record2) };
129520
+ const tombstoned = tombstone.version === null ? null : BigInt(tombstone.version);
129521
+ const orphanKeys = [];
129522
+ const { storage, logger } = deps;
129523
+ const PAGE_SIZE = 500;
129524
+ const stale = [];
129525
+ let kept = 0;
129526
+ for (let offset = 0; ; offset += PAGE_SIZE) {
129527
+ const entries = storage.listVersions(record2.scope, {
129528
+ limit: PAGE_SIZE,
129529
+ offset
129530
+ });
129531
+ for (const entry of entries) {
129532
+ if (isEntryCoveredByTombstone(entry, tombstone)) {
129533
+ stale.push({ scope: entry.scope, collectedAt: entry.collectedAt });
129534
+ if (entry.dataPointId === null && (tombstoned === null || BigInt(entry.version) > tombstoned)) {
129535
+ orphanKeys.push({
129536
+ scope: entry.scope,
129537
+ version: String(entry.version)
129538
+ });
129539
+ }
129540
+ } else {
129541
+ kept += 1;
129542
+ }
129543
+ }
129544
+ if (entries.length < PAGE_SIZE)
129545
+ break;
129546
+ }
129547
+ let removed = 0;
129548
+ for (const version4 of stale) {
129549
+ if (await storage.deleteVersion(version4.scope, version4.collectedAt)) {
129550
+ removed += 1;
129551
+ }
129552
+ }
129553
+ if (orphanKeys.length > 0 && deps.pendingBlobDeletions) {
129554
+ await deps.pendingBlobDeletions.add(orphanKeys);
129555
+ }
129556
+ if (removed > 0 || kept > 0) {
129557
+ logger.info({ dataPointId: record2.id, scope: record2.scope, deletedAt, removed, kept }, "Reconciled gateway deletion against local index");
129558
+ }
129559
+ return { scope: record2.scope, deletedAt, removed, kept };
129560
+ }
128629
129561
  async function repairLocalMissingBlockSidecars(deps) {
128630
129562
  const { storage, logger, diagnostics } = deps;
128631
129563
  if (!storage.writeBlockManifest || !storage.hasScopeBlocks) {
@@ -128784,28 +129716,6 @@ async function repairMissingBlockSidecars(storage, logger, ctx, entry, diagnosti
128784
129716
  }
128785
129717
  }
128786
129718
 
128787
- // ../core/dist/sync/workers/delete.js
128788
- async function deleteScopeRemote(deps, scope) {
128789
- const { storage, logger } = deps;
128790
- let syncedVersions = 0;
128791
- const PAGE_SIZE = 1e3;
128792
- for (let offset = 0; ; offset += PAGE_SIZE) {
128793
- const entries = storage.listVersions(scope, { limit: PAGE_SIZE, offset });
128794
- syncedVersions += entries.filter((e10) => e10.dataPointId !== null).length;
128795
- if (entries.length < PAGE_SIZE)
128796
- break;
128797
- }
128798
- if (syncedVersions > 0) {
128799
- logger.warn({ scope, syncedVersions }, "Remote scope deletion is not supported on the DPv2 gateway (no de-registration endpoint); local data was removed but the remote data point(s) and ciphertext blob(s) remain");
128800
- }
128801
- return {
128802
- scope,
128803
- filesDeregistered: 0,
128804
- blobsDeleted: 0,
128805
- errors: []
128806
- };
128807
- }
128808
-
128809
129719
  // ../core/dist/sync/engine/sync-manager.js
128810
129720
  var MAX_ERRORS = 10;
128811
129721
  function createSyncManager(uploadDeps, downloadDeps, options) {
@@ -128823,12 +129733,28 @@ function createSyncManager(uploadDeps, downloadDeps, options) {
128823
129733
  let rerunRequested = false;
128824
129734
  let needsFullReconcile = true;
128825
129735
  const downloadRetryMemory = createDownloadRetryMemory();
129736
+ const dataPointFeed = downloadDeps.dataPointFeed ?? uploadDeps.dataPointFeed;
129737
+ const scopeDeletions = uploadDeps.scopeDeletions ?? downloadDeps.scopeDeletions;
129738
+ const workerUploadDeps = {
129739
+ ...uploadDeps,
129740
+ pendingBlobDeletions: uploadDeps.pendingBlobDeletions ?? options?.pendingBlobDeletions
129741
+ };
129742
+ const workerDownloadDeps = {
129743
+ ...downloadDeps,
129744
+ pendingBlobDeletions: downloadDeps.pendingBlobDeletions ?? options?.pendingBlobDeletions
129745
+ };
129746
+ let mutationQueue = Promise.resolve();
129747
+ function exclusive(operation) {
129748
+ const run = mutationQueue.then(operation, operation);
129749
+ mutationQueue = run.catch(() => void 0);
129750
+ return run;
129751
+ }
128826
129752
  async function runCycle() {
128827
129753
  if (cycleInFlight) {
128828
129754
  rerunRequested = true;
128829
129755
  return cycleInFlight;
128830
129756
  }
128831
- cycleInFlight = (async () => {
129757
+ cycleInFlight = exclusive(async () => {
128832
129758
  do {
128833
129759
  rerunRequested = false;
128834
129760
  const canRun = await (options?.canSync?.() ?? { ok: true });
@@ -128839,7 +129765,19 @@ function createSyncManager(uploadDeps, downloadDeps, options) {
128839
129765
  }
128840
129766
  blocked = null;
128841
129767
  try {
128842
- const uploadResults = await uploadAll(uploadDeps, {
129768
+ await retryPendingBlobDeletions({
129769
+ deleteData: options?.deleteData,
129770
+ pendingBlobDeletions: options?.pendingBlobDeletions,
129771
+ dataPointFeed,
129772
+ serverOwner: uploadDeps.serverOwner,
129773
+ storage: uploadDeps.storage,
129774
+ logger: uploadDeps.logger
129775
+ });
129776
+ } catch (err2) {
129777
+ uploadDeps.logger.warn({ error: err2.message }, "Pending blob deletion retry failed");
129778
+ }
129779
+ try {
129780
+ const uploadResults = await uploadAll(workerUploadDeps, {
128843
129781
  batchSize: uploadBatchSize,
128844
129782
  onError(entry, error51) {
128845
129783
  pushError({
@@ -128863,7 +129801,7 @@ function createSyncManager(uploadDeps, downloadDeps, options) {
128863
129801
  }
128864
129802
  try {
128865
129803
  const fullReconcile = needsFullReconcile;
128866
- const downloadResults = await downloadAll(downloadDeps, {
129804
+ const downloadResults = await downloadAll(workerDownloadDeps, {
128867
129805
  fullReconcile,
128868
129806
  retryMemory: downloadRetryMemory
128869
129807
  });
@@ -128885,7 +129823,7 @@ function createSyncManager(uploadDeps, downloadDeps, options) {
128885
129823
  }
128886
129824
  lastSync = (/* @__PURE__ */ new Date()).toISOString();
128887
129825
  } while (rerunRequested && isRunning);
128888
- })();
129826
+ });
128889
129827
  try {
128890
129828
  await cycleInFlight;
128891
129829
  } finally {
@@ -128974,13 +129912,291 @@ function createSyncManager(uploadDeps, downloadDeps, options) {
128974
129912
  uploadDeps.logger.debug("New data notification received");
128975
129913
  scheduleNotifiedCycle();
128976
129914
  },
128977
- async deleteScopeRemote(scope) {
128978
- await deleteScopeRemote(uploadDeps, scope);
129915
+ deleteScope(scope) {
129916
+ return exclusive(() => deleteScope({
129917
+ storage: uploadDeps.storage,
129918
+ serverOwner: uploadDeps.serverOwner,
129919
+ deleteData: options?.deleteData,
129920
+ pendingBlobDeletions: options?.pendingBlobDeletions,
129921
+ scopeDeletions,
129922
+ dataPointFeed,
129923
+ logger: uploadDeps.logger
129924
+ }, scope));
128979
129925
  }
128980
129926
  };
128981
129927
  return manager;
128982
129928
  }
128983
129929
 
129930
+ // ../core/dist/sync/delete-data-port.js
129931
+ function createGatewayDeleteDataPort(options) {
129932
+ const gatewayBase = options.gatewayUrl.replace(/\/+$/, "");
129933
+ const storageBase = options.storage.endpoint.replace(/\/+$/, "");
129934
+ const fetchImpl = options.fetch ?? globalThis.fetch;
129935
+ const owner = options.serverOwner.toLowerCase();
129936
+ async function sendTombstone(scope, dataPointId, version4) {
129937
+ const signature = await options.signer.signAddData({
129938
+ ownerAddress: options.serverOwner,
129939
+ scope,
129940
+ dataHash: TOMBSTONE_DATA_HASH,
129941
+ metadataHash: TOMBSTONE_METADATA_HASH,
129942
+ expectedVersion: version4
129943
+ });
129944
+ return fetchImpl(`${gatewayBase}/v1/data/${dataPointId}`, {
129945
+ method: "DELETE",
129946
+ headers: {
129947
+ "Content-Type": "application/json",
129948
+ Authorization: `Web3Signed ${signature}`
129949
+ },
129950
+ body: JSON.stringify({
129951
+ ownerAddress: options.serverOwner,
129952
+ scope,
129953
+ expectedVersion: String(version4),
129954
+ signature
129955
+ })
129956
+ });
129957
+ }
129958
+ return {
129959
+ async tombstone(scope) {
129960
+ const dataPointId = computeDataPointId(options.serverOwner, scope);
129961
+ const current = await options.dataPointFeed.getDataPoint({
129962
+ ownerAddress: options.serverOwner,
129963
+ scope
129964
+ });
129965
+ if (current === null) {
129966
+ return { status: "not-registered", dataPointId };
129967
+ }
129968
+ if (current.deletedAt !== null) {
129969
+ return {
129970
+ status: "already-deleted",
129971
+ dataPointId,
129972
+ version: current.expectedVersion,
129973
+ deletedAt: current.deletedAt
129974
+ };
129975
+ }
129976
+ let version4 = BigInt(current.expectedVersion) + 1n;
129977
+ let res = await sendTombstone(scope, dataPointId, version4);
129978
+ if (res.status === 409) {
129979
+ const body2 = await res.json().catch(() => null);
129980
+ const conflict = unwrap2(body2);
129981
+ const nextExplicit = integerField(conflict, "nextExpectedVersion") ?? integerField(body2, "nextExpectedVersion");
129982
+ const currentExpected = integerField(conflict, "currentExpectedVersion") ?? integerField(body2, "currentExpectedVersion");
129983
+ const legacyNext = parseGatewayNextVersion(detailFromBody(body2, res.statusText));
129984
+ const next = nextExplicit !== null ? nextExplicit : currentExpected !== null ? currentExpected + 1n : legacyNext !== null ? BigInt(legacyNext) : null;
129985
+ if (next === null) {
129986
+ throw new Error(`Gateway error: 409 ${detailFromBody(body2, res.statusText)}`);
129987
+ }
129988
+ version4 = next;
129989
+ res = await sendTombstone(scope, dataPointId, version4);
129990
+ }
129991
+ if (res.status === 404) {
129992
+ return { status: "not-registered", dataPointId };
129993
+ }
129994
+ if (res.status === 410) {
129995
+ const body2 = await res.json().catch(() => null);
129996
+ const echoed = unwrap2(body2);
129997
+ let winning = normalizeVersionString(stringField2(echoed, "expectedVersion"));
129998
+ let deletedAt = stringField2(echoed, "deletedAt");
129999
+ if (winning === null) {
130000
+ const reread = await options.dataPointFeed.getDataPoint({ ownerAddress: options.serverOwner, scope }).catch(() => null);
130001
+ if (reread && reread.deletedAt !== null) {
130002
+ winning = normalizeVersionString(reread.expectedVersion);
130003
+ deletedAt = deletedAt ?? reread.deletedAt;
130004
+ }
130005
+ }
130006
+ return {
130007
+ status: "already-deleted",
130008
+ dataPointId,
130009
+ version: winning,
130010
+ deletedAt
130011
+ };
130012
+ }
130013
+ if (!res.ok) {
130014
+ throw new Error(`Gateway error: ${res.status} ${await errorDetail(res)}`);
130015
+ }
130016
+ const body = await res.json().catch(() => null);
130017
+ const row = unwrap2(body);
130018
+ return {
130019
+ status: "tombstoned",
130020
+ dataPointId: stringField2(row, "dataPointId") ?? dataPointId,
130021
+ version: stringField2(row, "expectedVersion") ?? String(version4),
130022
+ deletedAt: stringField2(row, "deletedAt")
130023
+ };
130024
+ },
130025
+ async deleteBlobVersions(scope, versions) {
130026
+ const outcome = {
130027
+ deleted: [],
130028
+ missing: [],
130029
+ failed: []
130030
+ };
130031
+ for (const version4 of versions) {
130032
+ const path = `/v1/chains/${options.storage.chainId}/blobs/${owner}/${encodeURIComponent(scope)}/${encodeURIComponent(version4)}`;
130033
+ try {
130034
+ const authorization = await buildWeb3SignedHeader({
130035
+ signMessage: (message) => options.storage.signMessage(message),
130036
+ aud: storageBase,
130037
+ method: "DELETE",
130038
+ uri: path
130039
+ });
130040
+ const res = await fetchImpl(`${storageBase}${path}`, {
130041
+ method: "DELETE",
130042
+ headers: { authorization }
130043
+ });
130044
+ if (res.status === 404) {
130045
+ outcome.missing.push(version4);
130046
+ } else if (res.ok) {
130047
+ outcome.deleted.push(version4);
130048
+ } else {
130049
+ outcome.failed.push({
130050
+ version: version4,
130051
+ error: `vana-storage delete failed: ${res.status} ${res.statusText}`
130052
+ });
130053
+ }
130054
+ } catch (err2) {
130055
+ outcome.failed.push({
130056
+ version: version4,
130057
+ error: err2 instanceof Error ? err2.message : String(err2)
130058
+ });
130059
+ }
130060
+ }
130061
+ return outcome;
130062
+ }
130063
+ };
130064
+ }
130065
+ async function errorDetail(res) {
130066
+ const body = await res.json().catch(() => null);
130067
+ return detailFromBody(body, res.statusText);
130068
+ }
130069
+ function detailFromBody(body, fallback) {
130070
+ if (typeof body === "object" && body !== null) {
130071
+ const record2 = body;
130072
+ if (typeof record2.error === "string")
130073
+ return record2.error;
130074
+ if (typeof record2.message === "string")
130075
+ return record2.message;
130076
+ if (typeof record2.error === "object" && record2.error !== null) {
130077
+ const nested = record2.error;
130078
+ if (typeof nested.message === "string")
130079
+ return nested.message;
130080
+ }
130081
+ }
130082
+ return fallback;
130083
+ }
130084
+ function normalizeVersionString(value) {
130085
+ if (value === null || !/^\d+$/.test(value))
130086
+ return null;
130087
+ const parsed = BigInt(value);
130088
+ return parsed > 0n ? parsed.toString() : null;
130089
+ }
130090
+ function integerField(record2, key) {
130091
+ if (typeof record2 !== "object" || record2 === null)
130092
+ return null;
130093
+ const value = record2[key];
130094
+ if (typeof value === "number" && Number.isSafeInteger(value)) {
130095
+ return BigInt(value);
130096
+ }
130097
+ if (typeof value === "string" && /^\d+$/.test(value))
130098
+ return BigInt(value);
130099
+ return null;
130100
+ }
130101
+ function unwrap2(body) {
130102
+ if (typeof body !== "object" || body === null)
130103
+ return null;
130104
+ const record2 = body;
130105
+ if (typeof record2.data === "object" && record2.data !== null) {
130106
+ return record2.data;
130107
+ }
130108
+ return record2;
130109
+ }
130110
+ function stringField2(record2, key) {
130111
+ const value = record2?.[key];
130112
+ return typeof value === "string" ? value : null;
130113
+ }
130114
+
130115
+ // ../core/dist/sync/pending-blob-deletions.js
130116
+ function markerId(key) {
130117
+ const range = key.range ? `${key.range.from}-${key.range.to}` : "";
130118
+ return `${key.scope}\0${key.version ?? ""}\0${range}`;
130119
+ }
130120
+ function normalizeRange(value) {
130121
+ if (typeof value !== "object" || value === null)
130122
+ return void 0;
130123
+ const { from, to: to3 } = value;
130124
+ if (typeof from !== "string" || typeof to3 !== "string" || !/^\d+$/.test(from) || !/^\d+$/.test(to3) || BigInt(from) > BigInt(to3)) {
130125
+ return void 0;
130126
+ }
130127
+ return { from, to: to3 };
130128
+ }
130129
+ function normalizePendingBlobDeletions(stored) {
130130
+ if (!Array.isArray(stored))
130131
+ return [];
130132
+ const keys = [];
130133
+ for (const item of stored) {
130134
+ if (typeof item === "string") {
130135
+ keys.push({ scope: item, version: null });
130136
+ } else if (typeof item === "object" && item !== null && typeof item.scope === "string") {
130137
+ const version4 = item.version;
130138
+ const range = typeof version4 === "string" ? void 0 : normalizeRange(item.range);
130139
+ keys.push({
130140
+ scope: item.scope,
130141
+ version: typeof version4 === "string" ? version4 : null,
130142
+ ...range ? { range } : {}
130143
+ });
130144
+ }
130145
+ }
130146
+ return keys;
130147
+ }
130148
+ function createPendingBlobDeletionStore(kv) {
130149
+ let queue = Promise.resolve();
130150
+ function serialized(operation) {
130151
+ const run = queue.then(operation, operation);
130152
+ queue = run.catch(() => void 0);
130153
+ return run;
130154
+ }
130155
+ async function current() {
130156
+ return normalizePendingBlobDeletions(await kv.read());
130157
+ }
130158
+ return {
130159
+ list() {
130160
+ return serialized(current);
130161
+ },
130162
+ add(keys) {
130163
+ return serialized(async () => {
130164
+ if (keys.length === 0)
130165
+ return;
130166
+ const existing = await current();
130167
+ const known = new Set(existing.map(markerId));
130168
+ const next = [...existing];
130169
+ for (const key of keys) {
130170
+ const id2 = markerId(key);
130171
+ if (known.has(id2))
130172
+ continue;
130173
+ known.add(id2);
130174
+ next.push({
130175
+ scope: key.scope,
130176
+ version: key.version,
130177
+ ...key.range ? { range: { ...key.range } } : {}
130178
+ });
130179
+ }
130180
+ if (next.length === existing.length)
130181
+ return;
130182
+ await kv.write(next);
130183
+ });
130184
+ },
130185
+ remove(keys) {
130186
+ return serialized(async () => {
130187
+ if (keys.length === 0)
130188
+ return;
130189
+ const existing = await current();
130190
+ const gone = new Set(keys.map(markerId));
130191
+ const next = existing.filter((key) => !gone.has(markerId(key)));
130192
+ if (next.length === existing.length)
130193
+ return;
130194
+ await kv.write(next);
130195
+ });
130196
+ }
130197
+ };
130198
+ }
130199
+
128984
130200
  // ../core/dist/storage/adapters/sdk.js
128985
130201
  function createSdkStorageAdapter(providerOrFactory, options) {
128986
130202
  let cachedProvider;
@@ -129041,8 +130257,11 @@ function copyBytes(data) {
129041
130257
 
129042
130258
  // ../core/dist/storage/adapters/vana.js
129043
130259
  var DEFAULT_VANA_STORAGE_ENDPOINT = "https://storage.vana.org";
130260
+ function resolveVanaStorageEndpoint(config2) {
130261
+ return (config2.storage.config.vana?.apiUrl ?? DEFAULT_VANA_STORAGE_ENDPOINT).replace(/\/+$/, "");
130262
+ }
129044
130263
  function createVanaSyncStorageAdapter(params) {
129045
- const endpoint = (params.config.storage.config.vana?.apiUrl ?? DEFAULT_VANA_STORAGE_ENDPOINT).replace(/\/+$/, "");
130264
+ const endpoint = resolveVanaStorageEndpoint(params.config);
129046
130265
  const owner = params.serverOwner.toLowerCase();
129047
130266
  const chainId = params.config.gateway.chainId;
129048
130267
  return createSdkStorageAdapter(createVanaStorageProvider({
@@ -129097,6 +130316,7 @@ async function resolvePsLiteOwner(input) {
129097
130316
 
129098
130317
  // ../lite/dist/sync.js
129099
130318
  var SYNC_CURSOR_KEY = "sync-cursor-v1";
130319
+ var PENDING_BLOB_DELETIONS_KEY = "pending-blob-deletions-v1";
129100
130320
  function createBrowserLogger(logger) {
129101
130321
  const fallback = {
129102
130322
  info: console.info.bind(console),
@@ -129127,6 +130347,19 @@ function createPsLiteSyncCursor(stateStore) {
129127
130347
  }
129128
130348
  };
129129
130349
  }
130350
+ function createPsLitePendingBlobDeletionStore(stateStore) {
130351
+ return createPendingBlobDeletionStore({
130352
+ async read() {
130353
+ const state = await stateStore.get(PENDING_BLOB_DELETIONS_KEY);
130354
+ if (!state)
130355
+ return null;
130356
+ return normalizePendingBlobDeletions(state.keys ?? state.scopes);
130357
+ },
130358
+ async write(keys) {
130359
+ await stateStore.set(PENDING_BLOB_DELETIONS_KEY, { keys });
130360
+ }
130361
+ });
130362
+ }
129130
130363
  function buildDownloadDiagnosticsHook(recorder) {
129131
130364
  return {
129132
130365
  onDownloadStart(fileId) {
@@ -129212,6 +130445,20 @@ async function createPsLiteSyncManager(options) {
129212
130445
  });
129213
130446
  const cursor = createPsLiteSyncCursor(options.stateStore);
129214
130447
  const logger = createBrowserLogger(options.logger);
130448
+ const dataPointFeed = options.dataPointFeed ?? createGatewayDataPointFeed({ gatewayUrl: options.config.gateway.url });
130449
+ const scopeDeletions = options.scopeDeletions ?? createScopeDeletionTracker({ feed: dataPointFeed, serverOwner, logger });
130450
+ const deleteData = createGatewayDeleteDataPort({
130451
+ gatewayUrl: options.config.gateway.url,
130452
+ dataPointFeed,
130453
+ serverOwner,
130454
+ signer,
130455
+ storage: {
130456
+ endpoint: resolveVanaStorageEndpoint(options.config),
130457
+ chainId: options.config.gateway.chainId,
130458
+ signMessage: (message) => options.serverAccount.signMessage(message)
130459
+ }
130460
+ });
130461
+ const pendingBlobDeletions = createPsLitePendingBlobDeletionStore(options.stateStore);
129215
130462
  const downloadDiagnostics = options.diagnostics ? buildDownloadDiagnosticsHook(options.diagnostics) : void 0;
129216
130463
  const syncManager = createSyncManager({
129217
130464
  storage: options.storage,
@@ -129221,7 +130468,9 @@ async function createPsLiteSyncManager(options) {
129221
130468
  masterKey,
129222
130469
  serverOwner,
129223
130470
  logger,
129224
- lineageGateway: options.lineageGateway
130471
+ lineageGateway: options.lineageGateway,
130472
+ dataPointFeed,
130473
+ scopeDeletions
129225
130474
  }, {
129226
130475
  storage: options.storage,
129227
130476
  storageAdapter,
@@ -129230,8 +130479,12 @@ async function createPsLiteSyncManager(options) {
129230
130479
  masterKey,
129231
130480
  serverOwner,
129232
130481
  logger,
129233
- diagnostics: downloadDiagnostics
130482
+ diagnostics: downloadDiagnostics,
130483
+ dataPointFeed,
130484
+ scopeDeletions
129234
130485
  }, {
130486
+ deleteData,
130487
+ pendingBlobDeletions,
129235
130488
  async canSync() {
129236
130489
  try {
129237
130490
  const serverInfo = await gateway.getServer(options.serverAccount.address);
@@ -129253,7 +130506,7 @@ async function createPsLiteSyncManager(options) {
129253
130506
  }
129254
130507
  });
129255
130508
  syncManager.start();
129256
- return { syncManager, serverOwner };
130509
+ return { syncManager, serverOwner, dataPointFeed, scopeDeletions };
129257
130510
  }
129258
130511
 
129259
130512
  // ../lite/dist/persistence.js
@@ -129319,8 +130572,9 @@ async function createIndexedDbPsLiteRuntime(options) {
129319
130572
  requestSigner: createRequestSigner(identity.account)
129320
130573
  });
129321
130574
  let syncManager = options.syncManager ?? null;
130575
+ let scopeDeletions = options.scopeDeletions;
129322
130576
  if (!syncManager && config2.sync.enabled) {
129323
- syncManager = (await createPsLiteSyncManager({
130577
+ const sync = await createPsLiteSyncManager({
129324
130578
  config: config2,
129325
130579
  stateStore,
129326
130580
  storage,
@@ -129328,10 +130582,14 @@ async function createIndexedDbPsLiteRuntime(options) {
129328
130582
  ownerAddress: options.ownerAddress,
129329
130583
  serverAccount: identity.account,
129330
130584
  gateway,
130585
+ dataPointFeed: options.dataPointFeed,
130586
+ scopeDeletions,
129331
130587
  diagnostics,
129332
130588
  logger: options.logger,
129333
130589
  lineageGateway
129334
- })).syncManager;
130590
+ });
130591
+ syncManager = sync.syncManager;
130592
+ scopeDeletions = sync.scopeDeletions;
129335
130593
  }
129336
130594
  let runtimeRef = null;
129337
130595
  const auth = options.auth ?? createWeb3SignedPsLiteAuth({
@@ -129360,6 +130618,7 @@ async function createIndexedDbPsLiteRuntime(options) {
129360
130618
  serverOwner,
129361
130619
  serverSigner,
129362
130620
  syncManager,
130621
+ scopeDeletions,
129363
130622
  diagnostics,
129364
130623
  lineageGateway,
129365
130624
  saveConfig: async (nextConfig) => {