@opendatalabs/personal-server-ts-core 0.0.1-canary.01ca694 → 0.0.1-canary.052d47e

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 (322) hide show
  1. package/dist/api/index.d.ts +282 -9
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +867 -163
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/auth/request.d.ts.map +1 -1
  6. package/dist/auth/request.js.map +1 -1
  7. package/dist/client.d.ts +8 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +21 -3
  10. package/dist/client.js.map +1 -1
  11. package/dist/contracts/binary.d.ts +12 -0
  12. package/dist/contracts/binary.d.ts.map +1 -1
  13. package/dist/contracts/binary.js +31 -0
  14. package/dist/contracts/binary.js.map +1 -1
  15. package/dist/contracts/data.d.ts +51 -5
  16. package/dist/contracts/data.d.ts.map +1 -1
  17. package/dist/contracts/data.js +166 -30
  18. package/dist/contracts/data.js.map +1 -1
  19. package/dist/contracts/grants.d.ts +7 -5
  20. package/dist/contracts/grants.d.ts.map +1 -1
  21. package/dist/contracts/grants.js +60 -37
  22. package/dist/contracts/grants.js.map +1 -1
  23. package/dist/contracts/index.d.ts +1 -1
  24. package/dist/contracts/index.d.ts.map +1 -1
  25. package/dist/contracts/index.js +1 -1
  26. package/dist/contracts/index.js.map +1 -1
  27. package/dist/derivatives/api.d.ts +48 -0
  28. package/dist/derivatives/api.d.ts.map +1 -0
  29. package/dist/derivatives/api.js +366 -0
  30. package/dist/derivatives/api.js.map +1 -0
  31. package/dist/derivatives/compute.d.ts +106 -0
  32. package/dist/derivatives/compute.d.ts.map +1 -0
  33. package/dist/derivatives/compute.js +414 -0
  34. package/dist/derivatives/compute.js.map +1 -0
  35. package/dist/derivatives/e2ee/aad.d.ts +22 -0
  36. package/dist/derivatives/e2ee/aad.d.ts.map +1 -0
  37. package/dist/derivatives/e2ee/aad.js +32 -0
  38. package/dist/derivatives/e2ee/aad.js.map +1 -0
  39. package/dist/derivatives/e2ee/attestation.d.ts +134 -0
  40. package/dist/derivatives/e2ee/attestation.d.ts.map +1 -0
  41. package/dist/derivatives/e2ee/attestation.js +248 -0
  42. package/dist/derivatives/e2ee/attestation.js.map +1 -0
  43. package/dist/derivatives/e2ee/index.d.ts +6 -0
  44. package/dist/derivatives/e2ee/index.d.ts.map +1 -0
  45. package/dist/derivatives/e2ee/index.js +6 -0
  46. package/dist/derivatives/e2ee/index.js.map +1 -0
  47. package/dist/derivatives/e2ee/jcs.d.ts +17 -0
  48. package/dist/derivatives/e2ee/jcs.d.ts.map +1 -0
  49. package/dist/derivatives/e2ee/jcs.js +55 -0
  50. package/dist/derivatives/e2ee/jcs.js.map +1 -0
  51. package/dist/derivatives/e2ee/phala.d.ts +62 -0
  52. package/dist/derivatives/e2ee/phala.d.ts.map +1 -0
  53. package/dist/derivatives/e2ee/phala.js +168 -0
  54. package/dist/derivatives/e2ee/phala.js.map +1 -0
  55. package/dist/derivatives/e2ee/suite.d.ts +56 -0
  56. package/dist/derivatives/e2ee/suite.d.ts.map +1 -0
  57. package/dist/derivatives/e2ee/suite.js +164 -0
  58. package/dist/derivatives/e2ee/suite.js.map +1 -0
  59. package/dist/derivatives/index.d.ts +10 -0
  60. package/dist/derivatives/index.d.ts.map +1 -0
  61. package/dist/derivatives/index.js +10 -0
  62. package/dist/derivatives/index.js.map +1 -0
  63. package/dist/derivatives/inference.d.ts +151 -0
  64. package/dist/derivatives/inference.d.ts.map +1 -0
  65. package/dist/derivatives/inference.js +262 -0
  66. package/dist/derivatives/inference.js.map +1 -0
  67. package/dist/derivatives/prompt.d.ts +64 -0
  68. package/dist/derivatives/prompt.d.ts.map +1 -0
  69. package/dist/derivatives/prompt.js +193 -0
  70. package/dist/derivatives/prompt.js.map +1 -0
  71. package/dist/derivatives/registration.d.ts +54 -0
  72. package/dist/derivatives/registration.d.ts.map +1 -0
  73. package/dist/derivatives/registration.js +180 -0
  74. package/dist/derivatives/registration.js.map +1 -0
  75. package/dist/derivatives/scheduler.d.ts +84 -0
  76. package/dist/derivatives/scheduler.d.ts.map +1 -0
  77. package/dist/derivatives/scheduler.js +257 -0
  78. package/dist/derivatives/scheduler.js.map +1 -0
  79. package/dist/derivatives/store.d.ts +14 -0
  80. package/dist/derivatives/store.d.ts.map +1 -0
  81. package/dist/derivatives/store.js +77 -0
  82. package/dist/derivatives/store.js.map +1 -0
  83. package/dist/derivatives/types.d.ts +92 -0
  84. package/dist/derivatives/types.d.ts.map +1 -0
  85. package/dist/derivatives/types.js +27 -0
  86. package/dist/derivatives/types.js.map +1 -0
  87. package/dist/errors/catalog.d.ts +75 -0
  88. package/dist/errors/catalog.d.ts.map +1 -1
  89. package/dist/errors/catalog.js +104 -0
  90. package/dist/errors/catalog.js.map +1 -1
  91. package/dist/grants/index.d.ts +2 -1
  92. package/dist/grants/index.d.ts.map +1 -1
  93. package/dist/grants/index.js +1 -1
  94. package/dist/grants/index.js.map +1 -1
  95. package/dist/keys/index.d.ts +1 -0
  96. package/dist/keys/index.d.ts.map +1 -1
  97. package/dist/keys/index.js +1 -1
  98. package/dist/keys/index.js.map +1 -1
  99. package/dist/keys/public-only-account.d.ts +8 -0
  100. package/dist/keys/public-only-account.d.ts.map +1 -0
  101. package/dist/keys/public-only-account.js +21 -0
  102. package/dist/keys/public-only-account.js.map +1 -0
  103. package/dist/keys/server-account.d.ts +2 -2
  104. package/dist/keys/server-account.d.ts.map +1 -1
  105. package/dist/lineage/attestation.d.ts +42 -0
  106. package/dist/lineage/attestation.d.ts.map +1 -0
  107. package/dist/lineage/attestation.js +25 -0
  108. package/dist/lineage/attestation.js.map +1 -0
  109. package/dist/lineage/gateway.d.ts +105 -0
  110. package/dist/lineage/gateway.d.ts.map +1 -0
  111. package/dist/lineage/gateway.js +242 -0
  112. package/dist/lineage/gateway.js.map +1 -0
  113. package/dist/lineage/index.d.ts +5 -0
  114. package/dist/lineage/index.d.ts.map +1 -0
  115. package/dist/lineage/index.js +5 -0
  116. package/dist/lineage/index.js.map +1 -0
  117. package/dist/lineage/lineage.d.ts +141 -0
  118. package/dist/lineage/lineage.d.ts.map +1 -0
  119. package/dist/lineage/lineage.js +255 -0
  120. package/dist/lineage/lineage.js.map +1 -0
  121. package/dist/logging/access-log.d.ts +10 -1
  122. package/dist/logging/access-log.d.ts.map +1 -1
  123. package/dist/mcp/activity.d.ts.map +1 -1
  124. package/dist/mcp/activity.js.map +1 -1
  125. package/dist/mcp/connection-api.d.ts.map +1 -1
  126. package/dist/mcp/connection-api.js +0 -1
  127. package/dist/mcp/connection-api.js.map +1 -1
  128. package/dist/mcp/index.d.ts +1 -0
  129. package/dist/mcp/index.d.ts.map +1 -1
  130. package/dist/mcp/index.js +1 -0
  131. package/dist/mcp/index.js.map +1 -1
  132. package/dist/mcp/read-client.d.ts +61 -1
  133. package/dist/mcp/read-client.d.ts.map +1 -1
  134. package/dist/mcp/read-client.js +397 -42
  135. package/dist/mcp/read-client.js.map +1 -1
  136. package/dist/mcp/resources.d.ts +37 -0
  137. package/dist/mcp/resources.d.ts.map +1 -0
  138. package/dist/mcp/resources.js +114 -0
  139. package/dist/mcp/resources.js.map +1 -0
  140. package/dist/mcp/search/index.d.ts.map +1 -1
  141. package/dist/mcp/search/index.js +108 -21
  142. package/dist/mcp/search/index.js.map +1 -1
  143. package/dist/mcp/server.d.ts.map +1 -1
  144. package/dist/mcp/server.js +27 -9
  145. package/dist/mcp/server.js.map +1 -1
  146. package/dist/mcp/session.d.ts +160 -0
  147. package/dist/mcp/session.d.ts.map +1 -0
  148. package/dist/mcp/session.js +243 -0
  149. package/dist/mcp/session.js.map +1 -0
  150. package/dist/mcp/tools.d.ts +29 -3
  151. package/dist/mcp/tools.d.ts.map +1 -1
  152. package/dist/mcp/tools.js +515 -48
  153. package/dist/mcp/tools.js.map +1 -1
  154. package/dist/mcp/types.d.ts.map +1 -1
  155. package/dist/payment/index.d.ts +2 -0
  156. package/dist/payment/index.d.ts.map +1 -0
  157. package/dist/payment/index.js +2 -0
  158. package/dist/payment/index.js.map +1 -0
  159. package/dist/payment/x402.d.ts +155 -0
  160. package/dist/payment/x402.d.ts.map +1 -0
  161. package/dist/payment/x402.js +402 -0
  162. package/dist/payment/x402.js.map +1 -0
  163. package/dist/policy/data-read.d.ts +18 -2
  164. package/dist/policy/data-read.d.ts.map +1 -1
  165. package/dist/policy/data-read.js +80 -49
  166. package/dist/policy/data-read.js.map +1 -1
  167. package/dist/policy/data-write.d.ts +69 -0
  168. package/dist/policy/data-write.d.ts.map +1 -0
  169. package/dist/policy/data-write.js +131 -0
  170. package/dist/policy/data-write.js.map +1 -0
  171. package/dist/policy/index.d.ts +3 -1
  172. package/dist/policy/index.d.ts.map +1 -1
  173. package/dist/policy/index.js +3 -1
  174. package/dist/policy/index.js.map +1 -1
  175. package/dist/policy/signed-artifacts.d.ts +17 -0
  176. package/dist/policy/signed-artifacts.d.ts.map +1 -0
  177. package/dist/policy/signed-artifacts.js +110 -0
  178. package/dist/policy/signed-artifacts.js.map +1 -0
  179. package/dist/ports/index.d.ts +159 -32
  180. package/dist/ports/index.d.ts.map +1 -1
  181. package/dist/ports/index.js +6 -5
  182. package/dist/ports/index.js.map +1 -1
  183. package/dist/schemas/server-config.d.ts +49 -4
  184. package/dist/schemas/server-config.d.ts.map +1 -1
  185. package/dist/schemas/server-config.js +95 -13
  186. package/dist/schemas/server-config.js.map +1 -1
  187. package/dist/signing/index.d.ts +2 -2
  188. package/dist/signing/index.d.ts.map +1 -1
  189. package/dist/signing/index.js +1 -1
  190. package/dist/signing/index.js.map +1 -1
  191. package/dist/signing/request-signer.d.ts +2 -0
  192. package/dist/signing/request-signer.d.ts.map +1 -1
  193. package/dist/signing/request-signer.js +1 -0
  194. package/dist/signing/request-signer.js.map +1 -1
  195. package/dist/signing/signer.d.ts +27 -25
  196. package/dist/signing/signer.d.ts.map +1 -1
  197. package/dist/signing/signer.js +27 -51
  198. package/dist/signing/signer.js.map +1 -1
  199. package/dist/storage/adapters/index.d.ts +1 -1
  200. package/dist/storage/adapters/index.d.ts.map +1 -1
  201. package/dist/storage/adapters/index.js +1 -1
  202. package/dist/storage/adapters/index.js.map +1 -1
  203. package/dist/storage/adapters/interface.d.ts +10 -0
  204. package/dist/storage/adapters/interface.d.ts.map +1 -1
  205. package/dist/storage/adapters/sdk.d.ts +11 -1
  206. package/dist/storage/adapters/sdk.d.ts.map +1 -1
  207. package/dist/storage/adapters/sdk.js +38 -3
  208. package/dist/storage/adapters/sdk.js.map +1 -1
  209. package/dist/storage/adapters/vana.d.ts +3 -0
  210. package/dist/storage/adapters/vana.d.ts.map +1 -1
  211. package/dist/storage/adapters/vana.js +95 -2
  212. package/dist/storage/adapters/vana.js.map +1 -1
  213. package/dist/storage/blocks/errors.d.ts.map +1 -1
  214. package/dist/storage/blocks/errors.js.map +1 -1
  215. package/dist/storage/blocks/index.d.ts +2 -0
  216. package/dist/storage/blocks/index.d.ts.map +1 -1
  217. package/dist/storage/blocks/index.js +2 -0
  218. package/dist/storage/blocks/index.js.map +1 -1
  219. package/dist/storage/blocks/select.d.ts +29 -0
  220. package/dist/storage/blocks/select.d.ts.map +1 -0
  221. package/dist/storage/blocks/select.js +120 -0
  222. package/dist/storage/blocks/select.js.map +1 -0
  223. package/dist/storage/blocks/types.d.ts +7 -0
  224. package/dist/storage/blocks/types.d.ts.map +1 -1
  225. package/dist/storage/index/manager.d.ts +30 -2
  226. package/dist/storage/index/manager.d.ts.map +1 -1
  227. package/dist/storage/index/schema.d.ts +1 -1
  228. package/dist/storage/index/schema.js +1 -1
  229. package/dist/storage/index/types.d.ts +6 -1
  230. package/dist/storage/index/types.d.ts.map +1 -1
  231. package/dist/sync/data-point-feed.d.ts +35 -0
  232. package/dist/sync/data-point-feed.d.ts.map +1 -0
  233. package/dist/sync/data-point-feed.js +122 -0
  234. package/dist/sync/data-point-feed.js.map +1 -0
  235. package/dist/sync/data-point-id.d.ts +7 -0
  236. package/dist/sync/data-point-id.d.ts.map +1 -0
  237. package/dist/sync/data-point-id.js +16 -0
  238. package/dist/sync/data-point-id.js.map +1 -0
  239. package/dist/sync/delete-data-port.d.ts +50 -0
  240. package/dist/sync/delete-data-port.d.ts.map +1 -0
  241. package/dist/sync/delete-data-port.js +244 -0
  242. package/dist/sync/delete-data-port.js.map +1 -0
  243. package/dist/sync/engine/sync-manager.d.ts +16 -4
  244. package/dist/sync/engine/sync-manager.d.ts.map +1 -1
  245. package/dist/sync/engine/sync-manager.js +93 -32
  246. package/dist/sync/engine/sync-manager.js.map +1 -1
  247. package/dist/sync/index.d.ts +7 -0
  248. package/dist/sync/index.d.ts.map +1 -1
  249. package/dist/sync/index.js +7 -0
  250. package/dist/sync/index.js.map +1 -1
  251. package/dist/sync/issues.d.ts.map +1 -1
  252. package/dist/sync/issues.js +21 -3
  253. package/dist/sync/issues.js.map +1 -1
  254. package/dist/sync/pending-blob-deletions.d.ts +29 -0
  255. package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
  256. package/dist/sync/pending-blob-deletions.js +116 -0
  257. package/dist/sync/pending-blob-deletions.js.map +1 -0
  258. package/dist/sync/retry-memory.d.ts +33 -0
  259. package/dist/sync/retry-memory.d.ts.map +1 -0
  260. package/dist/sync/retry-memory.js +68 -0
  261. package/dist/sync/retry-memory.js.map +1 -0
  262. package/dist/sync/scope-deletions.d.ts +155 -0
  263. package/dist/sync/scope-deletions.d.ts.map +1 -0
  264. package/dist/sync/scope-deletions.js +225 -0
  265. package/dist/sync/scope-deletions.js.map +1 -0
  266. package/dist/sync/tombstone.d.ts +27 -0
  267. package/dist/sync/tombstone.d.ts.map +1 -0
  268. package/dist/sync/tombstone.js +28 -0
  269. package/dist/sync/tombstone.js.map +1 -0
  270. package/dist/sync/workers/delete.d.ts +152 -31
  271. package/dist/sync/workers/delete.d.ts.map +1 -1
  272. package/dist/sync/workers/delete.js +457 -55
  273. package/dist/sync/workers/delete.js.map +1 -1
  274. package/dist/sync/workers/download.d.ts +108 -27
  275. package/dist/sync/workers/download.d.ts.map +1 -1
  276. package/dist/sync/workers/download.js +321 -126
  277. package/dist/sync/workers/download.js.map +1 -1
  278. package/dist/sync/workers/upload.d.ts +65 -10
  279. package/dist/sync/workers/upload.d.ts.map +1 -1
  280. package/dist/sync/workers/upload.js +371 -42
  281. package/dist/sync/workers/upload.js.map +1 -1
  282. package/dist/test-utils/e2ee-fixtures.d.ts +94 -0
  283. package/dist/test-utils/e2ee-fixtures.d.ts.map +1 -0
  284. package/dist/test-utils/e2ee-fixtures.js +116 -0
  285. package/dist/test-utils/e2ee-fixtures.js.map +1 -0
  286. package/dist/test-utils/e2ee-gateway.d.ts +61 -0
  287. package/dist/test-utils/e2ee-gateway.d.ts.map +1 -0
  288. package/dist/test-utils/e2ee-gateway.js +248 -0
  289. package/dist/test-utils/e2ee-gateway.js.map +1 -0
  290. package/dist/test-utils/index.d.ts +3 -0
  291. package/dist/test-utils/index.d.ts.map +1 -1
  292. package/dist/test-utils/index.js +3 -0
  293. package/dist/test-utils/index.js.map +1 -1
  294. package/dist/test-utils/memory-storage.d.ts +12 -0
  295. package/dist/test-utils/memory-storage.d.ts.map +1 -0
  296. package/dist/test-utils/memory-storage.js +146 -0
  297. package/dist/test-utils/memory-storage.js.map +1 -0
  298. package/dist/test-utils/wallet.d.ts +6 -0
  299. package/dist/test-utils/wallet.d.ts.map +1 -1
  300. package/dist/test-utils/wallet.js +46 -0
  301. package/dist/test-utils/wallet.js.map +1 -1
  302. package/dist/write/attribution.d.ts +185 -0
  303. package/dist/write/attribution.d.ts.map +1 -0
  304. package/dist/write/attribution.js +450 -0
  305. package/dist/write/attribution.js.map +1 -0
  306. package/dist/write/authorize.d.ts +77 -0
  307. package/dist/write/authorize.d.ts.map +1 -0
  308. package/dist/write/authorize.js +109 -0
  309. package/dist/write/authorize.js.map +1 -0
  310. package/dist/write/index.d.ts +5 -0
  311. package/dist/write/index.d.ts.map +1 -0
  312. package/dist/write/index.js +5 -0
  313. package/dist/write/index.js.map +1 -0
  314. package/dist/write/route.d.ts +61 -0
  315. package/dist/write/route.d.ts.map +1 -0
  316. package/dist/write/route.js +132 -0
  317. package/dist/write/route.js.map +1 -0
  318. package/dist/write/session.d.ts +96 -0
  319. package/dist/write/session.d.ts.map +1 -0
  320. package/dist/write/session.js +136 -0
  321. package/dist/write/session.js.map +1 -0
  322. package/package.json +19 -3
@@ -1,57 +1,71 @@
1
1
  import { DataFileEnvelopeSchema, decryptWithPassword, deriveScopeKey, } from "@opendatalabs/vana-sdk/browser";
2
+ import { feedFromGatewayClient } from "../data-point-feed.js";
3
+ import { deletionTimestamp, isEntryCoveredByTombstone, tombstoneVersion, } from "../scope-deletions.js";
2
4
  import { buildDataBlocksAsync } from "../../storage/blocks/build.js";
3
5
  import { classifySyncFailure, inferPayloadKind, } from "../issues.js";
6
+ import { downloadRetryKey } from "../retry-memory.js";
7
+ import { readStoredLineage } from "../../lineage/lineage.js";
4
8
  /**
5
- * Download and process a single file record from the storage backend:
6
- * 1. Check dedup: skip if fileId already in local index
7
- * 2. Download OpenPGP encrypted binary from storage backend
8
- * 3. Resolve schemaId → scope via Gateway getSchema
9
+ * Download and process a single DPv2 data point from the storage backend:
10
+ * 1. Check dedup: skip if dataPointId already in local index
11
+ * 2. Reconstruct the blob URL from (scope, expectedVersion) — DataPointRecords
12
+ * carry no URL, so we rebuild the version-keyed `{scope}/{version}` key the
13
+ * upload worker wrote under and resolve it via storageAdapter.urlForKey
14
+ * 3. Download the OpenPGP encrypted binary from storage
9
15
  * 4. Derive scope key from master key → hex-encode as OpenPGP password
16
+ * (scope comes straight off the record — no schema lookup needed)
10
17
  * 5. Decrypt with OpenPGP password-based decryption → plaintext JSON
11
18
  * 6. Parse as DataFileEnvelope (validate)
12
- * 7. Write to local filesystem via hierarchy manager
13
- * 8. Insert into local index (with fileId)
19
+ * 7. Write to local filesystem + bounded block sidecars
20
+ * 8. Insert into local index (with dataPointId + version)
14
21
  */
15
22
  export async function downloadOne(deps, record) {
16
- const { storage, storageAdapter, gateway, masterKey, logger, diagnostics } = deps;
17
- // 1. Check dedup: skip if fileId already in local index
18
- const existing = storage.findByFileId(record.fileId);
23
+ const { storage, storageAdapter, masterKey, logger, diagnostics } = deps;
24
+ const ctx = { dataPointId: record.id, schemaId: null };
25
+ // 1. Check dedup: skip if dataPointId already in local index
26
+ const existing = storage.findByDataPointId(record.id);
19
27
  if (existing) {
20
- await repairMissingBlockSidecars(storage, logger, record, existing, diagnostics);
21
- logger.debug({ fileId: record.fileId }, "File already in index, skipping");
22
- return null;
23
- }
24
- // 2. Download OpenPGP encrypted binary from storage backend.
25
- // A download failure (404 or transient) throws and blocks the cursor — and that is correct here:
26
- // the delete cascade de-registers a file BEFORE deleting its blob, so any genuinely-deleted file
27
- // has deletedAt set and is excluded from this default (excludeDeleted) listing. Thus a 404 only
28
- // happens for an in-flight cross-device delete, which self-heals on the next cycle once the
29
- // de-register propagates and the file drops out of the list; a transient error blocks-and-retries
30
- // without losing data. We deliberately do NOT skip-on-404 here: the storage layer can't reliably
31
- // distinguish "gone" from "transiently unavailable", so skipping would risk a silent data gap
32
- // during an outage. (Cross-device removal of already-downloaded copies is slice 3b.)
33
- diagnostics?.onDownloadStart(record.fileId);
28
+ const repairResult = await repairMissingBlockSidecars(storage, logger, { dataPointId: record.id, schemaId: existing.schemaId }, existing, diagnostics);
29
+ if (repairResult === "missing-envelope") {
30
+ logger.warn({
31
+ dataPointId: record.id,
32
+ scope: existing.scope,
33
+ collectedAt: existing.collectedAt,
34
+ }, "Local index entry was missing its envelope; re-downloading data point");
35
+ }
36
+ else {
37
+ logger.debug({ dataPointId: record.id }, "Data point already in index, skipping");
38
+ return null;
39
+ }
40
+ }
41
+ // 2. Reconstruct the version-keyed storage URL.
42
+ const storageKey = `${record.scope}/${record.expectedVersion}`;
43
+ const url = storageAdapter.urlForKey(storageKey);
44
+ // 3. Download OpenPGP encrypted binary from storage backend. A download
45
+ // failure throws and blocks the cursor (correct: a transient error retries
46
+ // next cycle rather than risk a silent gap). Stamp the stage so the
47
+ // classifier can tell a deterministic 404 (quarantine, never retry) from a
48
+ // transient storage error — without it the failure infers stage "unknown"
49
+ // and every 404 is retried forever.
50
+ diagnostics?.onDownloadStart(record.id);
34
51
  let encrypted;
35
52
  try {
36
- encrypted = await storageAdapter.download(record.url);
53
+ encrypted = await storageAdapter.download(url);
37
54
  }
38
55
  catch (err) {
39
56
  const detail = err.message;
40
- diagnostics?.onDownloadError(record.fileId, detail);
41
- throw err;
42
- }
43
- // 3. Resolve schemaId → scope via Gateway getSchema
44
- const schema = await gateway.getSchema(record.schemaId);
45
- if (!schema) {
46
- diagnostics?.onDownloadError(record.fileId, `No schema found for schemaId: ${record.schemaId}`);
47
- throw new Error(`No schema found for schemaId: ${record.schemaId}`);
57
+ diagnostics?.onDownloadError(record.id, detail);
58
+ throw withSyncFailureMetadata(err, {
59
+ stage: "download",
60
+ scope: record.scope,
61
+ });
48
62
  }
49
- diagnostics?.onDownloadEnd(record.fileId, schema.scope);
50
- // 4. Derive scope key → hex-encode as OpenPGP password
51
- const scopeKey = deriveScopeKey(masterKey, schema.scope);
63
+ diagnostics?.onDownloadEnd(record.id, record.scope);
64
+ // 4. Derive scope key → hex-encode as OpenPGP password (scope off the record)
65
+ const scopeKey = deriveScopeKey(masterKey, record.scope);
52
66
  const scopeKeyHex = uint8ToHex(scopeKey);
53
67
  // 5. Decrypt with OpenPGP password-based decryption
54
- diagnostics?.onDecryptStart(record.fileId, schema.scope);
68
+ diagnostics?.onDecryptStart(record.id, record.scope);
55
69
  let plaintext;
56
70
  try {
57
71
  plaintext = await decryptWithPassword(encrypted, scopeKeyHex);
@@ -64,9 +78,9 @@ export async function downloadOne(deps, record) {
64
78
  `payloadKind=${payloadKind}`,
65
79
  `encryptedSizeBytes=${encryptedSizeBytes}`,
66
80
  ].join("; ");
67
- diagnostics?.onDecryptError(record.fileId, schema.scope, detail);
81
+ diagnostics?.onDecryptError(record.id, record.scope, detail);
68
82
  throw withSyncFailureMetadata(err, {
69
- scope: schema.scope,
83
+ scope: record.scope,
70
84
  payloadKind,
71
85
  encryptedSizeBytes,
72
86
  });
@@ -79,97 +93,198 @@ export async function downloadOne(deps, record) {
79
93
  }
80
94
  catch (err) {
81
95
  const detail = err.message;
82
- diagnostics?.onDecryptError(record.fileId, schema.scope, detail);
96
+ diagnostics?.onDecryptError(record.id, record.scope, detail);
83
97
  throw err;
84
98
  }
85
- diagnostics?.onDecryptEnd(record.fileId, envelope.scope);
99
+ ctx.schemaId = envelope.schemaId ?? null;
100
+ diagnostics?.onDecryptEnd(record.id, envelope.scope);
86
101
  const existingByVersion = storage.findEntry({
87
102
  scope: envelope.scope,
88
103
  at: envelope.collectedAt,
89
104
  });
90
105
  if (existingByVersion !== undefined &&
91
106
  existingByVersion.collectedAt === envelope.collectedAt) {
92
- if (existingByVersion.fileId !== record.fileId) {
93
- await storage.updateFileId(existingByVersion.path, record.fileId);
107
+ if (existingByVersion.dataPointId !== record.id) {
108
+ await storage.updateDataPointId(existingByVersion.path, record.id);
94
109
  }
95
- await repairMissingBlockSidecars(storage, logger, record, {
96
- ...existingByVersion,
97
- fileId: record.fileId,
98
- schemaId: existingByVersion.schemaId ?? record.schemaId,
99
- }, diagnostics);
100
- logger.debug({
101
- fileId: record.fileId,
110
+ const repairResult = await repairMissingBlockSidecars(storage, logger, {
111
+ dataPointId: record.id,
112
+ schemaId: existingByVersion.schemaId ?? ctx.schemaId,
113
+ }, existingByVersion, diagnostics);
114
+ if (repairResult !== "missing-envelope") {
115
+ logger.debug({
116
+ dataPointId: record.id,
117
+ scope: envelope.scope,
118
+ collectedAt: envelope.collectedAt,
119
+ }, "Data version already exists locally, skipping");
120
+ return null;
121
+ }
122
+ logger.warn({
123
+ dataPointId: record.id,
102
124
  scope: envelope.scope,
103
125
  collectedAt: envelope.collectedAt,
104
- }, "File version already exists locally, skipping");
105
- return null;
126
+ }, "Local version entry was missing its envelope; rewriting downloaded file");
106
127
  }
107
- // 7. Write to local storage via the runtime storage port
108
- diagnostics?.onIndexStart(record.fileId, envelope.scope);
128
+ // 7. Write to local storage + bounded block sidecars
129
+ diagnostics?.onIndexStart(record.id, envelope.scope);
109
130
  let relativePath;
110
131
  let sizeBytes;
111
132
  try {
112
133
  ({ relativePath, sizeBytes } = await storage.writeEnvelope(envelope));
113
- await writeBlockSidecars(storage, envelope, logger, record, relativePath, diagnostics);
134
+ await writeBlockSidecars(storage, envelope, logger, ctx, relativePath, diagnostics);
114
135
  }
115
136
  catch (err) {
116
137
  const detail = err.message;
117
- diagnostics?.onIndexError(record.fileId, envelope.scope, detail);
138
+ diagnostics?.onIndexError(record.id, envelope.scope, detail);
118
139
  throw err;
119
140
  }
120
- // 8. Insert into local index (with fileId)
141
+ // 8. Insert into local index (with dataPointId + version)
121
142
  try {
122
143
  await storage.insertEntry({
123
- fileId: record.fileId,
124
- schemaId: record.schemaId,
144
+ fileId: null,
145
+ schemaId: ctx.schemaId,
125
146
  path: relativePath,
126
147
  scope: envelope.scope,
127
148
  collectedAt: envelope.collectedAt,
128
149
  sizeBytes,
150
+ version: Number(record.expectedVersion),
151
+ dataPointId: record.id,
129
152
  });
130
153
  }
131
154
  catch (err) {
132
155
  const detail = err.message;
133
- diagnostics?.onIndexError(record.fileId, envelope.scope, detail);
156
+ diagnostics?.onIndexError(record.id, envelope.scope, detail);
134
157
  throw err;
135
158
  }
136
- diagnostics?.onIndexEnd(record.fileId, envelope.scope);
137
- logger.info({ fileId: record.fileId, scope: envelope.scope, path: relativePath }, "Downloaded and indexed file");
159
+ diagnostics?.onIndexEnd(record.id, envelope.scope);
160
+ logger.info({ dataPointId: record.id, scope: envelope.scope, path: relativePath }, "Downloaded and indexed data point");
161
+ if (deps.onDataPointIndexed) {
162
+ try {
163
+ let lineageSources;
164
+ try {
165
+ lineageSources = readStoredLineage(envelope.data)?.sources;
166
+ }
167
+ catch {
168
+ // A malformed $lineage is the upload worker's problem; the hook
169
+ // still learns about the new version.
170
+ }
171
+ deps.onDataPointIndexed({
172
+ scope: envelope.scope,
173
+ dataPointId: record.id,
174
+ version: Number(record.expectedVersion),
175
+ collectedAt: envelope.collectedAt,
176
+ lineageSources,
177
+ });
178
+ }
179
+ catch (err) {
180
+ logger.warn({ scope: envelope.scope, error: err.message }, "onDataPointIndexed hook failed; data point already indexed");
181
+ }
182
+ }
138
183
  return {
139
- fileId: record.fileId,
184
+ dataPointId: record.id,
140
185
  scope: envelope.scope,
141
186
  collectedAt: envelope.collectedAt,
142
187
  path: relativePath,
143
188
  };
144
189
  }
145
190
  /**
146
- * Poll Gateway for new file records since lastProcessedTimestamp,
147
- * download each, and advance the cursor only when the page fully succeeds.
191
+ * Poll Gateway for the owner's data points since the stored cursor, download
192
+ * each, and advance the cursor only when the page fully succeeds.
148
193
  */
149
- export async function downloadAll(deps) {
194
+ export async function downloadAll(deps, options = {}) {
150
195
  const { gateway, cursor, serverOwner, logger } = deps;
151
196
  const syncRunId = createSyncRunId();
152
- await repairLocalMissingBlockSidecars(deps);
153
- // 1. Read cursor
154
- const lastProcessedTimestamp = await cursor.read();
155
- // 2. Poll gateway for new file records, including soft-deleted ones so we can reconcile deletions
156
- // of files this device already holds (cross-device "deleted on desktop → gone on web too").
157
- const { files, cursor: nextCursor } = await gateway.listFilesSince(serverOwner, lastProcessedTimestamp, { includeDeleted: true });
197
+ const repairSummary = await repairLocalMissingBlockSidecars(deps);
198
+ // 1. Read cursor (opaque pagination cursor persisted across runs)
199
+ const lastCursor = options.fullReconcile || repairSummary.missingEnvelopeEntries > 0
200
+ ? null
201
+ : await cursor.read();
202
+ if (options.fullReconcile) {
203
+ logger.info("Running full registry reconciliation to repair any missing local scope records");
204
+ }
205
+ if (repairSummary.missingEnvelopeEntries > 0) {
206
+ logger.warn({ missingEnvelopeEntries: repairSummary.missingEnvelopeEntries }, "Resetting this sync listing to repair stale local index entries");
207
+ }
208
+ if (options.fullReconcile || repairSummary.missingEnvelopeEntries > 0) {
209
+ // An EXPLICIT listing restart (full reconcile, or the repair path above)
210
+ // exists to re-fetch — give transient failures a fresh attempt budget so
211
+ // it can; 404s stay dead. Note `lastCursor === null` alone is NOT a
212
+ // reset signal: single-page listings never advance the cursor, so null
213
+ // is their steady state and clearing on it would erase the backoff
214
+ // memory every cycle.
215
+ options.retryMemory?.onListingReset();
216
+ }
217
+ // 2. Poll gateway for the owner's data points, tombstones included: a
218
+ // deletion made on another replica must reach this one or its local copy
219
+ // would be re-uploaded and resurrect the point.
220
+ const feed = deps.dataPointFeed ?? feedFromGatewayClient(gateway);
221
+ const { dataPoints, cursor: nextCursor } = await feed.listDataPointsByOwner(serverOwner, lastCursor, { includeDeleted: true });
222
+ // Feed the read-side deletion memory before any per-record work so a
223
+ // tombstone is refused by reads even if its local reconcile below fails.
224
+ // Only a listing with no further pages proves the memory complete: a
225
+ // multi-page backlog leaves it "stale" and reads fall back to lookups. A
226
+ // listing from no cursor covered the whole registry (`full`), which is
227
+ // what re-validates remembered tombstones; an incremental pass does not.
228
+ if (deps.scopeDeletions) {
229
+ for (const dataPoint of dataPoints) {
230
+ const deletedAt = deletionTimestamp(dataPoint);
231
+ if (deletedAt !== null) {
232
+ deps.scopeDeletions.markDeleted(dataPoint.scope, {
233
+ deletedAt,
234
+ version: tombstoneVersion(dataPoint),
235
+ });
236
+ }
237
+ else {
238
+ deps.scopeDeletions.markLive(dataPoint.scope);
239
+ }
240
+ }
241
+ if (nextCursor === null) {
242
+ deps.scopeDeletions.noteFeedSynced(undefined, {
243
+ full: lastCursor === null,
244
+ });
245
+ }
246
+ }
158
247
  const results = [];
159
248
  let failed = false;
160
- // 3. Process each file record
161
- for (const file of files) {
162
- try {
163
- // Deletion reconciliation: a record marked deletedAt means the owner deleted it elsewhere.
164
- // Drop any local copy instead of downloading. Idempotent — no local copy is a no-op.
165
- if (file.deletedAt) {
166
- const removed = await deps.storage.deleteByFileId(file.fileId);
167
- if (removed) {
168
- logger.info({ fileId: file.fileId }, "Reconciled remote deletion: removed local copy");
169
- }
170
- continue;
249
+ // 3. Process each data point record
250
+ for (const dataPoint of dataPoints) {
251
+ const deletedAt = deletionTimestamp(dataPoint);
252
+ if (deletedAt !== null) {
253
+ // Tombstone: never download; drop what the deletion covers locally.
254
+ // A failure here blocks the cursor so the tombstone is re-listed and
255
+ // the reconcile retried next cycle.
256
+ try {
257
+ await reconcileDeletedDataPoint(deps, dataPoint, deletedAt);
258
+ }
259
+ catch (err) {
260
+ logger.error({
261
+ dataPointId: dataPoint.id,
262
+ scope: dataPoint.scope,
263
+ error: err.message,
264
+ }, "Failed to reconcile deleted data point locally");
265
+ failed = true;
171
266
  }
172
- const result = await downloadOne(deps, file);
267
+ continue;
268
+ }
269
+ const retryKey = downloadRetryKey(dataPoint);
270
+ const decision = options.retryMemory?.decide(retryKey) ?? "attempt";
271
+ if (decision === "give-up") {
272
+ // Non-retryable failure (e.g. blob 404) or exhausted transient
273
+ // retries — skip without blocking the cursor so one dead record
274
+ // can't wedge the rest of the listing. A new registered version
275
+ // (different retry key) starts fresh.
276
+ logger.debug({ dataPointId: dataPoint.id, scope: dataPoint.scope }, "Skipping data point after exhausted download retries");
277
+ continue;
278
+ }
279
+ if (decision === "backoff") {
280
+ // Waiting out the backoff window. Block the cursor so the record is
281
+ // still listed when the window expires.
282
+ failed = true;
283
+ continue;
284
+ }
285
+ try {
286
+ const result = await downloadOne(deps, dataPoint);
287
+ options.retryMemory?.recordSuccess(retryKey);
173
288
  if (result) {
174
289
  results.push(result);
175
290
  }
@@ -178,32 +293,31 @@ export async function downloadAll(deps) {
178
293
  const metadata = getSyncFailureMetadata(err);
179
294
  const classified = classifySyncFailure({
180
295
  error: err,
181
- fileId: file.fileId,
182
- schemaId: file.schemaId,
296
+ fileId: dataPoint.id,
183
297
  syncRunId,
184
298
  stage: metadata.stage,
185
- scope: metadata.scope,
299
+ scope: metadata.scope ?? dataPoint.scope,
186
300
  payloadKind: metadata.payloadKind,
187
301
  encryptedSizeBytes: metadata.encryptedSizeBytes,
188
302
  });
303
+ options.retryMemory?.recordFailure(retryKey, classified.issue.retryable);
189
304
  if (!classified.issue.retryable) {
190
305
  logger.warn({
191
- fileId: file.fileId,
192
- schemaId: file.schemaId,
306
+ dataPointId: dataPoint.id,
193
307
  scope: classified.issue.scope,
194
308
  stage: classified.issue.stage,
195
309
  payloadKind: classified.issue.payloadKind,
196
310
  encryptedSizeBytes: classified.issue.encryptedSizeBytes,
197
311
  errorClass: classified.issue.errorClass,
198
312
  message: classified.issue.message,
199
- }, "Quarantined corrupt synced file");
313
+ }, "Quarantined corrupt synced data point");
200
314
  continue;
201
315
  }
202
316
  logger.error({
203
- fileId: file.fileId,
204
- schemaId: file.schemaId,
317
+ dataPointId: dataPoint.id,
318
+ scope: dataPoint.scope,
205
319
  error: err.message,
206
- }, "Failed to download file");
320
+ }, "Failed to download data point");
207
321
  failed = true;
208
322
  }
209
323
  }
@@ -212,19 +326,78 @@ export async function downloadAll(deps) {
212
326
  await cursor.write(nextCursor);
213
327
  }
214
328
  else if (failed) {
215
- logger.warn({ nextCursor }, "Download cursor not advanced because one or more files failed");
329
+ logger.warn({ nextCursor }, "Download cursor not advanced because one or more data points failed");
216
330
  }
217
331
  return results;
218
332
  }
333
+ /**
334
+ * Apply a gateway tombstone to the local index: remove every local version
335
+ * the tombstone covers (synced rows at or below its version, and unsynced
336
+ * rows ingested without knowledge of it), keep unsynced rows ingested on top
337
+ * of it (a legitimate re-add that the upload worker registers after the
338
+ * tombstone version). See `isEntryCoveredByTombstone`.
339
+ */
340
+ export async function reconcileDeletedDataPoint(deps, record, deletedAt) {
341
+ const tombstone = { version: tombstoneVersion(record) };
342
+ const tombstoned = tombstone.version === null ? null : BigInt(tombstone.version);
343
+ // Unsynced covered entries above the tombstone version: the deleting
344
+ // replica's exact-key pass only enumerates registry versions, so any
345
+ // ciphertext this replica uploaded under a higher, never-registered key is
346
+ // ours to clean up.
347
+ const orphanKeys = [];
348
+ const { storage, logger } = deps;
349
+ const PAGE_SIZE = 500;
350
+ const stale = [];
351
+ let kept = 0;
352
+ for (let offset = 0;; offset += PAGE_SIZE) {
353
+ const entries = storage.listVersions(record.scope, {
354
+ limit: PAGE_SIZE,
355
+ offset,
356
+ });
357
+ for (const entry of entries) {
358
+ if (isEntryCoveredByTombstone(entry, tombstone)) {
359
+ stale.push({ scope: entry.scope, collectedAt: entry.collectedAt });
360
+ if (entry.dataPointId === null &&
361
+ (tombstoned === null || BigInt(entry.version) > tombstoned)) {
362
+ orphanKeys.push({
363
+ scope: entry.scope,
364
+ version: String(entry.version),
365
+ });
366
+ }
367
+ }
368
+ else {
369
+ kept += 1;
370
+ }
371
+ }
372
+ if (entries.length < PAGE_SIZE)
373
+ break;
374
+ }
375
+ let removed = 0;
376
+ for (const version of stale) {
377
+ if (await storage.deleteVersion(version.scope, version.collectedAt)) {
378
+ removed += 1;
379
+ }
380
+ }
381
+ if (orphanKeys.length > 0 && deps.pendingBlobDeletions) {
382
+ await deps.pendingBlobDeletions.add(orphanKeys);
383
+ }
384
+ if (removed > 0 || kept > 0) {
385
+ logger.info({ dataPointId: record.id, scope: record.scope, deletedAt, removed, kept }, "Reconciled gateway deletion against local index");
386
+ }
387
+ return { scope: record.scope, deletedAt, removed, kept };
388
+ }
219
389
  export async function repairLocalMissingBlockSidecars(deps) {
220
390
  const { storage, logger, diagnostics } = deps;
221
- if (!storage.writeBlockManifest || !storage.hasScopeBlocks)
222
- return 0;
391
+ if (!storage.writeBlockManifest || !storage.hasScopeBlocks) {
392
+ return { repaired: 0, missingEnvelopeEntries: 0 };
393
+ }
223
394
  let repaired = 0;
395
+ let missingEnvelopeEntries = 0;
224
396
  let offset = 0;
225
397
  const limit = 100;
226
398
  while (true) {
227
399
  const page = storage.listScopes({ limit, offset });
400
+ let deletedEntryInPage = false;
228
401
  for (const scopeSummary of page.scopes) {
229
402
  const entry = storage.findEntry({
230
403
  scope: scopeSummary.scope,
@@ -232,24 +405,30 @@ export async function repairLocalMissingBlockSidecars(deps) {
232
405
  });
233
406
  if (!entry)
234
407
  continue;
235
- const repairedScope = await repairMissingBlockSidecars(storage, logger, {
236
- fileId: entry.fileId ?? entry.path,
237
- owner: "",
238
- url: "",
239
- schemaId: entry.schemaId ?? "",
240
- createdAt: entry.createdAt,
408
+ const repairResult = await repairMissingBlockSidecars(storage, logger, {
409
+ dataPointId: entry.dataPointId ?? entry.path,
410
+ schemaId: entry.schemaId,
241
411
  }, entry, diagnostics);
242
- if (repairedScope)
412
+ if (repairResult === "repaired")
243
413
  repaired += 1;
414
+ if (repairResult === "missing-envelope") {
415
+ missingEnvelopeEntries += 1;
416
+ deletedEntryInPage = true;
417
+ }
418
+ }
419
+ if (!deletedEntryInPage) {
420
+ offset += page.scopes.length;
244
421
  }
245
- offset += page.scopes.length;
246
422
  if (offset >= page.total || page.scopes.length === 0)
247
423
  break;
248
424
  }
249
425
  if (repaired > 0) {
250
426
  logger.info({ repaired }, "Repaired missing bounded block sidecars for local indexed data");
251
427
  }
252
- return repaired;
428
+ if (missingEnvelopeEntries > 0) {
429
+ logger.warn({ missingEnvelopeEntries }, "Removed stale local index entries missing local envelopes");
430
+ }
431
+ return { repaired, missingEnvelopeEntries };
253
432
  }
254
433
  function createSyncRunId() {
255
434
  return typeof crypto !== "undefined" &&
@@ -276,32 +455,32 @@ function getSyncFailureMetadata(error) {
276
455
  return {};
277
456
  return metadata;
278
457
  }
279
- async function writeBlockSidecars(storage, envelope, logger, record, relativePath, diagnostics) {
458
+ async function writeBlockSidecars(storage, envelope, logger, ctx, relativePath, diagnostics) {
280
459
  if (!storage.writeBlockManifest)
281
460
  return;
282
- diagnostics?.onManifestBuildStart(record.fileId, envelope.scope);
461
+ diagnostics?.onManifestBuildStart(ctx.dataPointId, envelope.scope);
283
462
  try {
284
463
  const built = await buildDataBlocksAsync({
285
464
  scope: envelope.scope,
286
465
  collectedAt: envelope.collectedAt,
287
- schemaId: record.schemaId,
466
+ schemaId: ctx.schemaId ?? undefined,
288
467
  content: envelope,
289
468
  });
290
469
  await storage.writeBlockManifest(envelope.scope, envelope.collectedAt, built.manifest, built.blocks);
291
- diagnostics?.onManifestBuildEnd(record.fileId, envelope.scope);
470
+ diagnostics?.onManifestBuildEnd(ctx.dataPointId, envelope.scope);
292
471
  }
293
472
  catch (error) {
294
473
  const classified = classifySyncFailure({
295
474
  error,
296
- fileId: record.fileId,
475
+ fileId: ctx.dataPointId,
297
476
  syncRunId: "download",
298
477
  stage: "block_build",
299
- schemaId: record.schemaId,
478
+ schemaId: ctx.schemaId ?? undefined,
300
479
  scope: envelope.scope,
301
480
  });
302
- diagnostics?.onManifestBuildError(record.fileId, envelope.scope, classified.issue.message);
481
+ diagnostics?.onManifestBuildError(ctx.dataPointId, envelope.scope, classified.issue.message);
303
482
  logger.warn({
304
- fileId: record.fileId,
483
+ dataPointId: ctx.dataPointId,
305
484
  scope: envelope.scope,
306
485
  collectedAt: envelope.collectedAt,
307
486
  path: relativePath,
@@ -311,24 +490,25 @@ async function writeBlockSidecars(storage, envelope, logger, record, relativePat
311
490
  }, "Bounded block sidecar write failed after raw envelope write");
312
491
  }
313
492
  }
314
- async function repairMissingBlockSidecars(storage, logger, record, entry, diagnostics) {
315
- if (!storage.writeBlockManifest || !storage.hasScopeBlocks)
316
- return false;
493
+ async function repairMissingBlockSidecars(storage, logger, ctx, entry, diagnostics) {
494
+ if (!storage.writeBlockManifest || !storage.hasScopeBlocks) {
495
+ return "not-repaired";
496
+ }
317
497
  let hasBlocks = false;
318
498
  try {
319
499
  hasBlocks = await storage.hasScopeBlocks(entry.scope, entry.collectedAt);
320
500
  }
321
501
  catch (error) {
322
502
  logger.warn({
323
- fileId: entry.fileId ?? record.fileId,
503
+ dataPointId: ctx.dataPointId,
324
504
  scope: entry.scope,
325
505
  collectedAt: entry.collectedAt,
326
506
  error: error.message,
327
507
  }, "Bounded block sidecar repair skipped after readiness check failed");
328
- return false;
508
+ return "not-repaired";
329
509
  }
330
510
  if (hasBlocks)
331
- return false;
511
+ return "already-ready";
332
512
  diagnostics?.onRepair(entry.scope, "indexed entry missing block manifest; rebuilding from local envelope");
333
513
  let envelope;
334
514
  try {
@@ -336,29 +516,44 @@ async function repairMissingBlockSidecars(storage, logger, record, entry, diagno
336
516
  }
337
517
  catch (error) {
338
518
  logger.warn({
339
- fileId: entry.fileId ?? record.fileId,
519
+ dataPointId: ctx.dataPointId,
340
520
  scope: entry.scope,
341
521
  collectedAt: entry.collectedAt,
342
522
  error: error.message,
343
523
  }, "Bounded block sidecar repair skipped because local envelope is unavailable");
344
- return false;
524
+ // Remove the stale index entry whose payload is gone. We can only target
525
+ // it by gateway fileId; DataPoint-only entries (fileId === null) are left
526
+ // in place (no path-keyed delete on the port) and self-heal on re-download.
527
+ if (entry.fileId) {
528
+ diagnostics?.onRepair(entry.scope, "index exists without local payload; removing stale local index entry");
529
+ const removed = await storage.deleteByFileId(entry.fileId);
530
+ logger.warn({
531
+ dataPointId: ctx.dataPointId,
532
+ scope: entry.scope,
533
+ collectedAt: entry.collectedAt,
534
+ removed,
535
+ }, "Removed stale local index entry missing local envelope");
536
+ return removed ? "missing-envelope" : "not-repaired";
537
+ }
538
+ return "not-repaired";
345
539
  }
346
540
  await writeBlockSidecars(storage, envelope, logger, {
347
- ...record,
348
- fileId: entry.fileId ?? record.fileId,
349
- schemaId: entry.schemaId ?? record.schemaId,
541
+ dataPointId: ctx.dataPointId,
542
+ schemaId: entry.schemaId ?? ctx.schemaId,
350
543
  }, entry.path, diagnostics);
351
544
  try {
352
- return await storage.hasScopeBlocks(entry.scope, entry.collectedAt);
545
+ return (await storage.hasScopeBlocks(entry.scope, entry.collectedAt))
546
+ ? "repaired"
547
+ : "not-repaired";
353
548
  }
354
549
  catch (error) {
355
550
  logger.warn({
356
- fileId: entry.fileId ?? record.fileId,
551
+ dataPointId: ctx.dataPointId,
357
552
  scope: entry.scope,
358
553
  collectedAt: entry.collectedAt,
359
554
  error: error.message,
360
555
  }, "Bounded block sidecar repair completed but readiness could not be verified");
361
- return false;
556
+ return "not-repaired";
362
557
  }
363
558
  }
364
559
  //# sourceMappingURL=download.js.map