@opendatalabs/personal-server-ts-core 1.33.8 → 1.34.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.
Files changed (330) hide show
  1. package/dist/additions/budget.d.ts +28 -0
  2. package/dist/additions/budget.d.ts.map +1 -0
  3. package/dist/additions/budget.js +119 -0
  4. package/dist/additions/budget.js.map +1 -0
  5. package/dist/additions/durable.d.ts +64 -0
  6. package/dist/additions/durable.d.ts.map +1 -0
  7. package/dist/additions/durable.js +591 -0
  8. package/dist/additions/durable.js.map +1 -0
  9. package/dist/additions/first-added.d.ts +32 -48
  10. package/dist/additions/first-added.d.ts.map +1 -1
  11. package/dist/additions/first-added.js +68 -123
  12. package/dist/additions/first-added.js.map +1 -1
  13. package/dist/additions/index.d.ts +3 -0
  14. package/dist/additions/index.d.ts.map +1 -1
  15. package/dist/additions/index.js +3 -0
  16. package/dist/additions/index.js.map +1 -1
  17. package/dist/additions/ledger-store.js +1 -1
  18. package/dist/additions/record-rules.d.ts.map +1 -1
  19. package/dist/additions/record-rules.js +7 -0
  20. package/dist/additions/record-rules.js.map +1 -1
  21. package/dist/additions/summary.d.ts +88 -1
  22. package/dist/additions/summary.d.ts.map +1 -1
  23. package/dist/additions/summary.js +173 -8
  24. package/dist/additions/summary.js.map +1 -1
  25. package/dist/additions/version-inventory.d.ts +80 -0
  26. package/dist/additions/version-inventory.d.ts.map +1 -0
  27. package/dist/additions/version-inventory.js +232 -0
  28. package/dist/additions/version-inventory.js.map +1 -0
  29. package/dist/api/index.d.ts +70 -2
  30. package/dist/api/index.d.ts.map +1 -1
  31. package/dist/api/index.js +506 -79
  32. package/dist/api/index.js.map +1 -1
  33. package/dist/auth/index.d.ts +1 -1
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +1 -1
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/request.d.ts +6 -0
  38. package/dist/auth/request.d.ts.map +1 -1
  39. package/dist/auth/request.js +66 -4
  40. package/dist/auth/request.js.map +1 -1
  41. package/dist/contracts/data.d.ts +54 -8
  42. package/dist/contracts/data.d.ts.map +1 -1
  43. package/dist/contracts/data.js +178 -35
  44. package/dist/contracts/data.js.map +1 -1
  45. package/dist/contracts/index.d.ts +1 -1
  46. package/dist/contracts/index.d.ts.map +1 -1
  47. package/dist/derivatives/registration.d.ts +0 -5
  48. package/dist/derivatives/registration.d.ts.map +1 -1
  49. package/dist/derivatives/registration.js +2 -1
  50. package/dist/derivatives/registration.js.map +1 -1
  51. package/dist/errors/pdpp-catalog.d.ts +36 -0
  52. package/dist/errors/pdpp-catalog.d.ts.map +1 -0
  53. package/dist/errors/pdpp-catalog.js +86 -0
  54. package/dist/errors/pdpp-catalog.js.map +1 -0
  55. package/dist/grants/index.d.ts +2 -0
  56. package/dist/grants/index.d.ts.map +1 -1
  57. package/dist/grants/index.js +4 -0
  58. package/dist/grants/index.js.map +1 -1
  59. package/dist/grants/pdpp-binding-store.d.ts +89 -0
  60. package/dist/grants/pdpp-binding-store.d.ts.map +1 -0
  61. package/dist/grants/pdpp-binding-store.js +112 -0
  62. package/dist/grants/pdpp-binding-store.js.map +1 -0
  63. package/dist/grants/pdpp-binding.d.ts +169 -0
  64. package/dist/grants/pdpp-binding.d.ts.map +1 -0
  65. package/dist/grants/pdpp-binding.js +236 -0
  66. package/dist/grants/pdpp-binding.js.map +1 -0
  67. package/dist/legacy-projection/bindings.d.ts +28 -1
  68. package/dist/legacy-projection/bindings.d.ts.map +1 -1
  69. package/dist/legacy-projection/bindings.js +622 -189
  70. package/dist/legacy-projection/bindings.js.map +1 -1
  71. package/dist/legacy-projection/claude-source-record.d.ts +22 -0
  72. package/dist/legacy-projection/claude-source-record.d.ts.map +1 -0
  73. package/dist/legacy-projection/claude-source-record.js +360 -0
  74. package/dist/legacy-projection/claude-source-record.js.map +1 -0
  75. package/dist/legacy-projection/declared-text.d.ts +41 -0
  76. package/dist/legacy-projection/declared-text.d.ts.map +1 -0
  77. package/dist/legacy-projection/declared-text.js +199 -0
  78. package/dist/legacy-projection/declared-text.js.map +1 -0
  79. package/dist/legacy-projection/github-browser-bindings.d.ts.map +1 -1
  80. package/dist/legacy-projection/github-browser-bindings.js +89 -5
  81. package/dist/legacy-projection/github-browser-bindings.js.map +1 -1
  82. package/dist/legacy-projection/github-legacy-fields.d.ts +6 -0
  83. package/dist/legacy-projection/github-legacy-fields.d.ts.map +1 -0
  84. package/dist/legacy-projection/github-legacy-fields.js +202 -0
  85. package/dist/legacy-projection/github-legacy-fields.js.map +1 -0
  86. package/dist/legacy-projection/index.d.ts +3 -1
  87. package/dist/legacy-projection/index.d.ts.map +1 -1
  88. package/dist/legacy-projection/index.js +49 -1
  89. package/dist/legacy-projection/index.js.map +1 -1
  90. package/dist/legacy-projection/oura-browser-bindings.d.ts.map +1 -1
  91. package/dist/legacy-projection/oura-browser-bindings.js +43 -41
  92. package/dist/legacy-projection/oura-browser-bindings.js.map +1 -1
  93. package/dist/legacy-projection/types.d.ts +38 -2
  94. package/dist/legacy-projection/types.d.ts.map +1 -1
  95. package/dist/mcp/resources.d.ts.map +1 -1
  96. package/dist/mcp/resources.js +3 -0
  97. package/dist/mcp/resources.js.map +1 -1
  98. package/dist/mcp/tools.d.ts.map +1 -1
  99. package/dist/mcp/tools.js +11 -2
  100. package/dist/mcp/tools.js.map +1 -1
  101. package/dist/pdpp/approval.d.ts +271 -0
  102. package/dist/pdpp/approval.d.ts.map +1 -0
  103. package/dist/pdpp/approval.js +467 -0
  104. package/dist/pdpp/approval.js.map +1 -0
  105. package/dist/pdpp/client-identity.d.ts +88 -0
  106. package/dist/pdpp/client-identity.d.ts.map +1 -0
  107. package/dist/pdpp/client-identity.js +196 -0
  108. package/dist/pdpp/client-identity.js.map +1 -0
  109. package/dist/pdpp/client-metadata.d.ts +95 -0
  110. package/dist/pdpp/client-metadata.d.ts.map +1 -0
  111. package/dist/pdpp/client-metadata.js +96 -0
  112. package/dist/pdpp/client-metadata.js.map +1 -0
  113. package/dist/pdpp/commitments.d.ts +75 -0
  114. package/dist/pdpp/commitments.d.ts.map +1 -0
  115. package/dist/pdpp/commitments.js +113 -0
  116. package/dist/pdpp/commitments.js.map +1 -0
  117. package/dist/pdpp/declaration.d.ts +118 -0
  118. package/dist/pdpp/declaration.d.ts.map +1 -0
  119. package/dist/pdpp/declaration.js +836 -0
  120. package/dist/pdpp/declaration.js.map +1 -0
  121. package/dist/pdpp/index.d.ts +37 -0
  122. package/dist/pdpp/index.d.ts.map +1 -0
  123. package/dist/pdpp/index.js +23 -0
  124. package/dist/pdpp/index.js.map +1 -0
  125. package/dist/pdpp/issuance.d.ts +126 -0
  126. package/dist/pdpp/issuance.d.ts.map +1 -0
  127. package/dist/pdpp/issuance.js +180 -0
  128. package/dist/pdpp/issuance.js.map +1 -0
  129. package/dist/pdpp/pkce.d.ts +53 -0
  130. package/dist/pdpp/pkce.d.ts.map +1 -0
  131. package/dist/pdpp/pkce.js +109 -0
  132. package/dist/pdpp/pkce.js.map +1 -0
  133. package/dist/pdpp/redirect.d.ts +60 -0
  134. package/dist/pdpp/redirect.d.ts.map +1 -0
  135. package/dist/pdpp/redirect.js +125 -0
  136. package/dist/pdpp/redirect.js.map +1 -0
  137. package/dist/pdpp/resolve.d.ts +109 -0
  138. package/dist/pdpp/resolve.d.ts.map +1 -0
  139. package/dist/pdpp/resolve.js +393 -0
  140. package/dist/pdpp/resolve.js.map +1 -0
  141. package/dist/pdpp/review.d.ts +280 -0
  142. package/dist/pdpp/review.d.ts.map +1 -0
  143. package/dist/pdpp/review.js +205 -0
  144. package/dist/pdpp/review.js.map +1 -0
  145. package/dist/pdpp/selection.d.ts +50 -0
  146. package/dist/pdpp/selection.d.ts.map +1 -0
  147. package/dist/pdpp/selection.js +442 -0
  148. package/dist/pdpp/selection.js.map +1 -0
  149. package/dist/pdpp/store.d.ts +228 -0
  150. package/dist/pdpp/store.d.ts.map +1 -0
  151. package/dist/pdpp/store.js +542 -0
  152. package/dist/pdpp/store.js.map +1 -0
  153. package/dist/pdpp/tokens.d.ts +181 -0
  154. package/dist/pdpp/tokens.d.ts.map +1 -0
  155. package/dist/pdpp/tokens.js +419 -0
  156. package/dist/pdpp/tokens.js.map +1 -0
  157. package/dist/pdpp/types.d.ts +441 -0
  158. package/dist/pdpp/types.d.ts.map +1 -0
  159. package/dist/pdpp/types.js +61 -0
  160. package/dist/pdpp/types.js.map +1 -0
  161. package/dist/pdpp-version.d.ts +28 -0
  162. package/dist/pdpp-version.d.ts.map +1 -0
  163. package/dist/pdpp-version.js +28 -0
  164. package/dist/pdpp-version.js.map +1 -0
  165. package/dist/policy/data-read.d.ts +24 -0
  166. package/dist/policy/data-read.d.ts.map +1 -1
  167. package/dist/policy/data-read.js +60 -3
  168. package/dist/policy/data-read.js.map +1 -1
  169. package/dist/policy/data-write.d.ts.map +1 -1
  170. package/dist/policy/data-write.js +3 -0
  171. package/dist/policy/data-write.js.map +1 -1
  172. package/dist/policy/index.d.ts +1 -0
  173. package/dist/policy/index.d.ts.map +1 -1
  174. package/dist/policy/index.js +1 -0
  175. package/dist/policy/index.js.map +1 -1
  176. package/dist/policy/reserved-scopes.d.ts +3 -0
  177. package/dist/policy/reserved-scopes.d.ts.map +1 -0
  178. package/dist/policy/reserved-scopes.js +5 -0
  179. package/dist/policy/reserved-scopes.js.map +1 -0
  180. package/dist/ports/index.d.ts +63 -0
  181. package/dist/ports/index.d.ts.map +1 -1
  182. package/dist/ports/index.js.map +1 -1
  183. package/dist/ports/pdpp-auth.d.ts +129 -0
  184. package/dist/ports/pdpp-auth.d.ts.map +1 -0
  185. package/dist/ports/pdpp-auth.js +60 -0
  186. package/dist/ports/pdpp-auth.js.map +1 -0
  187. package/dist/ports/pdpp-auth.test-utils.d.ts +9 -0
  188. package/dist/ports/pdpp-auth.test-utils.d.ts.map +1 -0
  189. package/dist/ports/pdpp-auth.test-utils.js +18 -0
  190. package/dist/ports/pdpp-auth.test-utils.js.map +1 -0
  191. package/dist/schemas/server-config.d.ts +44 -0
  192. package/dist/schemas/server-config.d.ts.map +1 -1
  193. package/dist/schemas/server-config.js +91 -0
  194. package/dist/schemas/server-config.js.map +1 -1
  195. package/dist/signing/request-signer.d.ts +2 -0
  196. package/dist/signing/request-signer.d.ts.map +1 -1
  197. package/dist/signing/request-signer.js +1 -0
  198. package/dist/signing/request-signer.js.map +1 -1
  199. package/dist/storage/adapters/interface.d.ts +3 -0
  200. package/dist/storage/adapters/interface.d.ts.map +1 -1
  201. package/dist/storage/adapters/vana-audience.d.ts +12 -0
  202. package/dist/storage/adapters/vana-audience.d.ts.map +1 -0
  203. package/dist/storage/adapters/vana-audience.js +72 -0
  204. package/dist/storage/adapters/vana-audience.js.map +1 -0
  205. package/dist/storage/adapters/vana.d.ts +2 -0
  206. package/dist/storage/adapters/vana.d.ts.map +1 -1
  207. package/dist/storage/adapters/vana.js +53 -20
  208. package/dist/storage/adapters/vana.js.map +1 -1
  209. package/dist/storage/canonical-legacy-projection.d.ts +262 -0
  210. package/dist/storage/canonical-legacy-projection.d.ts.map +1 -0
  211. package/dist/storage/canonical-legacy-projection.js +761 -0
  212. package/dist/storage/canonical-legacy-projection.js.map +1 -0
  213. package/dist/storage/index/manager.d.ts +29 -2
  214. package/dist/storage/index/manager.d.ts.map +1 -1
  215. package/dist/storage/index/schema.d.ts +1 -1
  216. package/dist/storage/index/schema.js +1 -1
  217. package/dist/storage/index/types.d.ts +7 -1
  218. package/dist/storage/index/types.d.ts.map +1 -1
  219. package/dist/storage/legacy-projection.d.ts +4 -3
  220. package/dist/storage/legacy-projection.d.ts.map +1 -1
  221. package/dist/storage/legacy-projection.js +145 -42
  222. package/dist/storage/legacy-projection.js.map +1 -1
  223. package/dist/storage/legacy-scope-coverage-exceptions.d.ts +8 -0
  224. package/dist/storage/legacy-scope-coverage-exceptions.d.ts.map +1 -0
  225. package/dist/storage/legacy-scope-coverage-exceptions.js +10 -0
  226. package/dist/storage/legacy-scope-coverage-exceptions.js.map +1 -0
  227. package/dist/storage/legacy-scope-coverage.d.ts +53 -0
  228. package/dist/storage/legacy-scope-coverage.d.ts.map +1 -0
  229. package/dist/storage/legacy-scope-coverage.js +403 -0
  230. package/dist/storage/legacy-scope-coverage.js.map +1 -0
  231. package/dist/storage/legacy-scope-entity-keys.d.ts +4 -0
  232. package/dist/storage/legacy-scope-entity-keys.d.ts.map +1 -0
  233. package/dist/storage/legacy-scope-entity-keys.js +92 -0
  234. package/dist/storage/legacy-scope-entity-keys.js.map +1 -0
  235. package/dist/storage/legacy-scope-view.d.ts +154 -0
  236. package/dist/storage/legacy-scope-view.d.ts.map +1 -0
  237. package/dist/storage/legacy-scope-view.js +735 -0
  238. package/dist/storage/legacy-scope-view.js.map +1 -0
  239. package/dist/storage/pdpp-records/completion.d.ts +91 -0
  240. package/dist/storage/pdpp-records/completion.d.ts.map +1 -0
  241. package/dist/storage/pdpp-records/completion.js +170 -0
  242. package/dist/storage/pdpp-records/completion.js.map +1 -0
  243. package/dist/storage/pdpp-records/cursor.d.ts +41 -0
  244. package/dist/storage/pdpp-records/cursor.d.ts.map +1 -0
  245. package/dist/storage/pdpp-records/cursor.js +49 -0
  246. package/dist/storage/pdpp-records/cursor.js.map +1 -0
  247. package/dist/storage/pdpp-records/declaration-witness.d.ts +58 -0
  248. package/dist/storage/pdpp-records/declaration-witness.d.ts.map +1 -0
  249. package/dist/storage/pdpp-records/declaration-witness.js +230 -0
  250. package/dist/storage/pdpp-records/declaration-witness.js.map +1 -0
  251. package/dist/storage/pdpp-records/enforcement.d.ts +52 -0
  252. package/dist/storage/pdpp-records/enforcement.d.ts.map +1 -0
  253. package/dist/storage/pdpp-records/enforcement.js +116 -0
  254. package/dist/storage/pdpp-records/enforcement.js.map +1 -0
  255. package/dist/storage/pdpp-records/index.d.ts +5 -0
  256. package/dist/storage/pdpp-records/index.d.ts.map +1 -0
  257. package/dist/storage/pdpp-records/index.js +5 -0
  258. package/dist/storage/pdpp-records/index.js.map +1 -0
  259. package/dist/storage/pdpp-records/ingest-plan.d.ts +39 -0
  260. package/dist/storage/pdpp-records/ingest-plan.d.ts.map +1 -0
  261. package/dist/storage/pdpp-records/ingest-plan.js +103 -0
  262. package/dist/storage/pdpp-records/ingest-plan.js.map +1 -0
  263. package/dist/storage/pdpp-records/interface.d.ts +104 -0
  264. package/dist/storage/pdpp-records/interface.d.ts.map +1 -0
  265. package/dist/storage/pdpp-records/interface.js +2 -0
  266. package/dist/storage/pdpp-records/interface.js.map +1 -0
  267. package/dist/storage/pdpp-records/memory-store.d.ts +9 -0
  268. package/dist/storage/pdpp-records/memory-store.d.ts.map +1 -0
  269. package/dist/storage/pdpp-records/memory-store.js +466 -0
  270. package/dist/storage/pdpp-records/memory-store.js.map +1 -0
  271. package/dist/storage/pdpp-records/portable.d.ts +9 -0
  272. package/dist/storage/pdpp-records/portable.d.ts.map +1 -0
  273. package/dist/storage/pdpp-records/portable.js +8 -0
  274. package/dist/storage/pdpp-records/portable.js.map +1 -0
  275. package/dist/storage/pdpp-records/read-port.d.ts +44 -0
  276. package/dist/storage/pdpp-records/read-port.d.ts.map +1 -0
  277. package/dist/storage/pdpp-records/read-port.js +22 -0
  278. package/dist/storage/pdpp-records/read-port.js.map +1 -0
  279. package/dist/storage/pdpp-records/record-key.d.ts +52 -0
  280. package/dist/storage/pdpp-records/record-key.d.ts.map +1 -0
  281. package/dist/storage/pdpp-records/record-key.js +97 -0
  282. package/dist/storage/pdpp-records/record-key.js.map +1 -0
  283. package/dist/storage/pdpp-records/schema.d.ts +41 -0
  284. package/dist/storage/pdpp-records/schema.d.ts.map +1 -0
  285. package/dist/storage/pdpp-records/schema.js +153 -0
  286. package/dist/storage/pdpp-records/schema.js.map +1 -0
  287. package/dist/storage/pdpp-records/stream-declaration.d.ts +88 -0
  288. package/dist/storage/pdpp-records/stream-declaration.d.ts.map +1 -0
  289. package/dist/storage/pdpp-records/stream-declaration.js +57 -0
  290. package/dist/storage/pdpp-records/stream-declaration.js.map +1 -0
  291. package/dist/storage/pdpp-records/types.d.ts +138 -0
  292. package/dist/storage/pdpp-records/types.d.ts.map +1 -0
  293. package/dist/storage/pdpp-records/types.js +38 -0
  294. package/dist/storage/pdpp-records/types.js.map +1 -0
  295. package/dist/sync/data-point-feed.d.ts +3 -0
  296. package/dist/sync/data-point-feed.d.ts.map +1 -1
  297. package/dist/sync/data-point-feed.js +17 -1
  298. package/dist/sync/data-point-feed.js.map +1 -1
  299. package/dist/sync/delete-data-port.d.ts +2 -0
  300. package/dist/sync/delete-data-port.d.ts.map +1 -1
  301. package/dist/sync/delete-data-port.js +1 -1
  302. package/dist/sync/delete-data-port.js.map +1 -1
  303. package/dist/sync/engine/sync-manager.d.ts.map +1 -1
  304. package/dist/sync/engine/sync-manager.js +5 -2
  305. package/dist/sync/engine/sync-manager.js.map +1 -1
  306. package/dist/sync/index.d.ts +3 -1
  307. package/dist/sync/index.d.ts.map +1 -1
  308. package/dist/sync/index.js +4 -0
  309. package/dist/sync/index.js.map +1 -1
  310. package/dist/sync/pdpp-import.d.ts +192 -0
  311. package/dist/sync/pdpp-import.d.ts.map +1 -0
  312. package/dist/sync/pdpp-import.js +441 -0
  313. package/dist/sync/pdpp-import.js.map +1 -0
  314. package/dist/sync/scope-deletions.d.ts +10 -0
  315. package/dist/sync/scope-deletions.d.ts.map +1 -1
  316. package/dist/sync/scope-deletions.js +40 -7
  317. package/dist/sync/scope-deletions.js.map +1 -1
  318. package/dist/sync/workers/download.d.ts +14 -0
  319. package/dist/sync/workers/download.d.ts.map +1 -1
  320. package/dist/sync/workers/download.js +149 -13
  321. package/dist/sync/workers/download.js.map +1 -1
  322. package/dist/sync/workers/upload.d.ts +2 -0
  323. package/dist/sync/workers/upload.d.ts.map +1 -1
  324. package/dist/sync/workers/upload.js +7 -0
  325. package/dist/sync/workers/upload.js.map +1 -1
  326. package/dist/test-utils/memory-storage.d.ts +1 -1
  327. package/dist/test-utils/memory-storage.d.ts.map +1 -1
  328. package/dist/test-utils/memory-storage.js +11 -5
  329. package/dist/test-utils/memory-storage.js.map +1 -1
  330. package/package.json +54 -4
package/dist/api/index.js CHANGED
@@ -1,9 +1,12 @@
1
+ import { isSystemScope } from "../policy/reserved-scopes.js";
1
2
  import { DataDeletedError, DeleteTombstoneFailedError, GrantRequiredError, InvalidCascadeError, LineageCascadeUnavailableError, LineageGatewayError, LineageUnavailableError, ProtocolError, ServerNotConfiguredError, } from "../errors/catalog.js";
2
3
  import { computeDataPointId } from "../sync/data-point-id.js";
3
4
  import { isEntryCoveredByTombstone, } from "../sync/scope-deletions.js";
5
+ import { isPermanentRejection, } from "../sync/pdpp-import.js";
4
6
  import { supersedeLegacyVersions, } from "../storage/legacy-projection.js";
5
7
  import { deleteScope as deleteScopeLocally, } from "../sync/workers/delete.js";
6
- import { ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, IngestPersistedError, } from "../contracts/index.js";
8
+ import { ingestDataContract, ingestBinaryDataContract, listAccessLogsContract, listDataScopesContract, listDataVersionsContract, parseDataScopeContract, parseJsonObjectBody, readDataContract, syncFileContract, triggerSyncContract, getSyncStatusContract, configReadErrorContract, configWriteErrorContract, createGrantContract, listGrantsContract, oauthTokenContract, revokeGrantContract, validateServerConfigContract, verifyGrantContract, decodeBinaryEnvelope, isBinaryEnvelope, parseMetadataHeader, stringifyMetadataHeader, IngestPersistedError, sha256Hex, } from "../contracts/index.js";
9
+ import { canonicalJsonBytes } from "../derivatives/e2ee/jcs.js";
7
10
  // Imported directly: the contracts barrel does not re-export this addition.
8
11
  import { summarizeDataAdditionsContract } from "../contracts/data.js";
9
12
  import { scopeCoveredByGrant } from "@opendatalabs/vana-sdk/browser";
@@ -86,17 +89,80 @@ function methodNotAllowed() {
86
89
  function notFound() {
87
90
  return errorResponse(404, "NOT_FOUND", "Not found");
88
91
  }
89
- async function withApiErrors(handler) {
92
+ async function withApiErrors(handler, context) {
90
93
  try {
91
- return await handler();
94
+ const response = await handler();
95
+ // A handled 4xx that means "the index and the disk disagree" is an
96
+ // operator-facing fault even though it is not a 500 — log it once, here,
97
+ // rather than at the contract that cannot see the route.
98
+ if (response.status === 404 && context) {
99
+ await logMissingDataFile(response, context);
100
+ }
101
+ return response;
92
102
  }
93
103
  catch (err) {
94
104
  if (err instanceof ProtocolError) {
95
105
  return protocolErrorResponse(err);
96
106
  }
107
+ if (context) {
108
+ context.logger?.error?.({
109
+ route: context.route,
110
+ requestId: context.requestId ?? crypto.randomUUID(),
111
+ errorCode: "INTERNAL_ERROR",
112
+ ...stripUndefined(context.detail),
113
+ err: describeError(err),
114
+ }, "Unhandled error in personal server API");
115
+ }
97
116
  return errorResponse(500, "INTERNAL_ERROR", "Internal server error");
98
117
  }
99
118
  }
119
+ /**
120
+ * The `DATA_FILE_MISSING` 404 the read contract produces, logged at the one
121
+ * layer that knows which route served it. Peeks at a clone so the response
122
+ * body stays readable by the caller.
123
+ */
124
+ async function logMissingDataFile(response, context) {
125
+ if (!context.logger?.error)
126
+ return;
127
+ try {
128
+ const body = await response.clone().json();
129
+ if (typeof body !== "object" ||
130
+ body === null ||
131
+ body.error !== "DATA_FILE_MISSING") {
132
+ return;
133
+ }
134
+ context.logger.error({
135
+ route: context.route,
136
+ requestId: context.requestId ?? crypto.randomUUID(),
137
+ errorCode: "DATA_FILE_MISSING",
138
+ ...stripUndefined(context.detail),
139
+ }, "Indexed data file is missing on disk");
140
+ }
141
+ catch {
142
+ // A diagnostic log must not change the response sent to the caller.
143
+ }
144
+ }
145
+ /**
146
+ * An error rendered for a log line — name, message and stack only. Never the
147
+ * whole object: a thrown error can carry a request, a config or a token on an
148
+ * arbitrary property, and a structured logger would serialize all of it.
149
+ */
150
+ function describeError(err) {
151
+ if (err instanceof Error) {
152
+ return {
153
+ name: err.name,
154
+ message: err.message,
155
+ ...("code" in err && { code: err.code }),
156
+ stack: err.stack,
157
+ };
158
+ }
159
+ return { message: String(err) };
160
+ }
161
+ function stripUndefined(detail) {
162
+ if (!detail)
163
+ return {};
164
+ return Object.fromEntries(Object.entries(detail).filter((pair) => pair[1] !== undefined));
165
+ }
100
166
  function normalizeLimit(value, fallback) {
101
167
  if (value === null)
102
168
  return fallback;
@@ -129,7 +195,8 @@ export function selectedGrantId(request, url) {
129
195
  undefined);
130
196
  }
131
197
  function listAuthCovers(listAuth, scope) {
132
- return scopeCoveredByGrant(scope, [...listAuth.grantedScopes]);
198
+ return (!isSystemScope(scope) &&
199
+ scopeCoveredByGrant(scope, [...listAuth.grantedScopes]));
133
200
  }
134
201
  /**
135
202
  * Run the X402 dispatch for one read:
@@ -293,33 +360,102 @@ export async function handleX402Cycle(input) {
293
360
  }
294
361
  return { kind: "gateway-error", status: gatewayRes.status, body: errorBody };
295
362
  }
296
- const MAX_INGEST_STAMP_ADVANCE_SEC = 60;
297
363
  const pendingIngestStamps = new WeakMap();
298
- /** Reserve before awaiting ingestion; keep the existing second-precision paths. */
299
- function reserveIngestStamp(storage, scope, now) {
300
- let pending = pendingIngestStamps.get(storage);
301
- if (!pending) {
302
- pending = new Set();
303
- pendingIngestStamps.set(storage, pending);
304
- }
305
- const base = Math.floor(now().getTime() / 1000) * 1000;
306
- for (let bump = 0; bump < MAX_INGEST_STAMP_ADVANCE_SEC; bump += 1) {
307
- const value = new Date(base + bump * 1000)
308
- .toISOString()
309
- .replace(/\.\d{3}Z$/, "Z");
310
- const key = `${scope}:${value}`;
311
- if (pending.has(key) ||
312
- storage.findEntry({ scope, at: value })?.collectedAt === value)
364
+ function reserveIngestStamp(now, storage, scope) {
365
+ // Bound allocation to 60 millisecond slots per scope, anchored to wall time.
366
+ // Failed requests release their slot; neither failures nor other scopes
367
+ // advance this scope's clock. The durable head protects restart/clock rollback.
368
+ const wall = now().getTime();
369
+ const latest = Date.parse(storage.findEntry({ scope })?.collectedAt ?? "");
370
+ let scopes = pendingIngestStamps.get(storage);
371
+ const pending = scopes?.get(scope) ?? new Set();
372
+ for (let stamp = wall; stamp < wall + 60; stamp += 1) {
373
+ if ((Number.isFinite(latest) && stamp <= latest) || pending.has(stamp)) {
313
374
  continue;
314
- pending.add(key);
375
+ }
376
+ if (!scopes) {
377
+ scopes = new Map();
378
+ pendingIngestStamps.set(storage, scopes);
379
+ }
380
+ scopes.set(scope, pending);
381
+ pending.add(stamp);
382
+ const reservations = scopes;
315
383
  return {
316
- value,
317
- release: () => {
318
- pending.delete(key);
384
+ collectedAt: new Date(stamp).toISOString(),
385
+ release() {
386
+ pending.delete(stamp);
387
+ if (pending.size === 0)
388
+ reservations.delete(scope);
319
389
  },
320
390
  };
321
391
  }
322
- throw new ProtocolError(429, "INGEST_CAPACITY", "Too many writes to this scope; retry after a minute");
392
+ return undefined;
393
+ }
394
+ function parseLegacyPrecondition(request) {
395
+ const none = request.headers.get("if-none-match");
396
+ const match = request.headers.get("if-match");
397
+ if (none !== null && match !== null) {
398
+ return errorResponse(400, "INVALID_PRECONDITION", "Supply only one precondition");
399
+ }
400
+ if (none !== null) {
401
+ return none === "*"
402
+ ? { kind: "none" }
403
+ : errorResponse(400, "INVALID_PRECONDITION", "If-None-Match must be *");
404
+ }
405
+ if (match !== null) {
406
+ const parsed = /^"([1-9]\d*)"$/.exec(match);
407
+ const version = parsed ? Number(parsed[1]) : NaN;
408
+ return Number.isSafeInteger(version)
409
+ ? { kind: "match", version }
410
+ : errorResponse(400, "INVALID_PRECONDITION", "If-Match must be a quoted positive version");
411
+ }
412
+ return undefined;
413
+ }
414
+ function parseProducerHeader(request) {
415
+ const producer = request.headers.get("vana-producer");
416
+ const encodedProvenance = request.headers.get("vana-producer-provenance") ?? undefined;
417
+ if (producer === null && !encodedProvenance)
418
+ return {};
419
+ if (producer !== "pdpp-projector" && producer !== "pdpp-import-projection") {
420
+ return errorResponse(400, "INVALID_PRODUCER", "Vana-Producer is unrecognized or missing");
421
+ }
422
+ if (encodedProvenance &&
423
+ (encodedProvenance.length > 8192 ||
424
+ !/^[A-Za-z0-9_-]+$/.test(encodedProvenance))) {
425
+ return errorResponse(400, "INVALID_PROVENANCE", "Vana-Producer-Provenance is malformed");
426
+ }
427
+ return { producer, encodedProvenance };
428
+ }
429
+ async function parseProducerProvenance(encoded, body) {
430
+ if (!encoded)
431
+ return undefined;
432
+ try {
433
+ const value = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")), (char) => char.charCodeAt(0))));
434
+ if (!value ||
435
+ typeof value !== "object" ||
436
+ typeof value.projector_version !== "string" ||
437
+ !value.projector_version ||
438
+ typeof value.declaration_digest !== "string" ||
439
+ !value.declaration_digest ||
440
+ !Array.isArray(value.inputs) ||
441
+ !value.inputs.every((input) => input &&
442
+ typeof input.stream === "string" &&
443
+ input.stream &&
444
+ typeof input.changes_since_token === "string" &&
445
+ input.changes_since_token) ||
446
+ typeof value.payload_sha256 !== "string" ||
447
+ !/^(?:0x)?[0-9a-fA-F]{64}$/.test(value.payload_sha256)) {
448
+ return errorResponse(400, "INVALID_PROVENANCE", "Producer provenance has invalid fields");
449
+ }
450
+ const actual = (await sha256Hex(canonicalJsonBytes(body))).slice(2);
451
+ if (actual !== value.payload_sha256.replace(/^0x/, "").toLowerCase()) {
452
+ return errorResponse(400, "PAYLOAD_HASH_MISMATCH", "Provenance payload_sha256 does not match the JSON body");
453
+ }
454
+ return value;
455
+ }
456
+ catch {
457
+ return errorResponse(400, "INVALID_PROVENANCE", "Vana-Producer-Provenance is not base64url JSON");
458
+ }
323
459
  }
324
460
  /**
325
461
  * The deletion that applies to a read of `scope`, or null when the read may
@@ -335,15 +471,25 @@ function reserveIngestStamp(storage, scope, now) {
335
471
  * Gateway unreachability never fails the read: the tracker answers from its
336
472
  * last known state, which is the documented offline behaviour.
337
473
  */
338
- export async function resolveReadDeletion(deps, scope, entry) {
474
+ export async function resolveReadDeletion(deps, scope, entry, body = "file") {
339
475
  if (!deps.scopeDeletions)
340
476
  return null;
341
477
  const verdict = await deps.scopeDeletions.resolve(scope, {
342
478
  consultGateway: entry ? "if-stale" : "always",
343
479
  });
344
- if (!verdict.deleted)
480
+ // A live DPv2 file cannot establish that a different canonical body was
481
+ // re-added. Keep this refusal until a genuine canonical re-add seam exists.
482
+ const tombstone = body === "canonical"
483
+ ? (deps.scopeDeletions.deletionHistory?.get(scope) ??
484
+ (verdict.deleted ? verdict : null))
485
+ : verdict.deleted
486
+ ? verdict
487
+ : null;
488
+ if (!tombstone)
345
489
  return null;
346
- if (entry && !isEntryCoveredByTombstone(entry, verdict)) {
490
+ if (body === "file" &&
491
+ entry &&
492
+ !isEntryCoveredByTombstone(entry, tombstone)) {
347
493
  return null;
348
494
  }
349
495
  return {
@@ -351,7 +497,7 @@ export async function resolveReadDeletion(deps, scope, entry) {
351
497
  dataPointId: deps.serverOwner
352
498
  ? computeDataPointId(deps.serverOwner, scope)
353
499
  : null,
354
- deletedAt: verdict.deletedAt,
500
+ deletedAt: tombstone.deletedAt,
355
501
  };
356
502
  }
357
503
  /**
@@ -359,8 +505,8 @@ export async function resolveReadDeletion(deps, scope, entry) {
359
505
  * served. Call it before any payment step: a builder must never be charged
360
506
  * for a scope the owner deleted.
361
507
  */
362
- export async function assertScopeNotDeleted(deps, scope, entry) {
363
- const deletion = await resolveReadDeletion(deps, scope, entry);
508
+ export async function assertScopeNotDeleted(deps, scope, entry, body = "file") {
509
+ const deletion = await resolveReadDeletion(deps, scope, entry, body);
364
510
  if (deletion)
365
511
  throw new DataDeletedError(deletion);
366
512
  }
@@ -374,6 +520,82 @@ function discoveryVisibility(deps) {
374
520
  return undefined;
375
521
  return async (scope, entry) => (await resolveReadDeletion(deps, scope, entry)) === null;
376
522
  }
523
+ /**
524
+ * Discovery filter for the scope listing: the deletion filter, and a
525
+ * canonical scope is listed only when the view could serve it.
526
+ */
527
+ function listingVisibility(deps) {
528
+ const deletion = discoveryVisibility(deps);
529
+ const view = deps.legacyScopeView;
530
+ if (!view)
531
+ return deletion;
532
+ return async (scope, entry) => {
533
+ const listing = view.listingFor(scope);
534
+ if (listing.kind === "refused")
535
+ return false;
536
+ const canonicalOwned = view.ownsScope(scope);
537
+ // A file's re-add marker cannot revive the canonical body this row
538
+ // describes. Canonical re-adds have no registered version yet.
539
+ return ((await resolveReadDeletion(deps, scope, canonicalOwned ? undefined : entry, canonicalOwned ? "canonical" : "file")) === null);
540
+ };
541
+ }
542
+ /**
543
+ * Bound scopes served only from canonical records, for the listing: no
544
+ * legacy write is needed to discover them. A gateway deletion hides them
545
+ * the way it hides a stored scope.
546
+ */
547
+ function canonicalOnlyScopes(deps) {
548
+ const view = deps.legacyScopeView;
549
+ if (!view)
550
+ return undefined;
551
+ return async (scopePrefix, stored) => {
552
+ const listed = [];
553
+ for (const { scope, collectedAt } of view.listCanonicalScopes(scopePrefix)) {
554
+ if (stored.has(scope))
555
+ continue;
556
+ if ((await resolveReadDeletion(deps, scope, undefined, "canonical")) !==
557
+ null) {
558
+ continue;
559
+ }
560
+ // No legacy version exists to count or to ask `/versions` for.
561
+ listed.push({ scope, latestCollectedAt: collectedAt, versionCount: 0 });
562
+ }
563
+ return listed;
564
+ };
565
+ }
566
+ /**
567
+ * A canonical scope's listing row carries the canonical freshness. The
568
+ * legacy head's size and block readiness describe a file the latest read
569
+ * does not serve, so they are dropped.
570
+ */
571
+ function withCanonicalListing(deps, summary) {
572
+ const listing = deps.legacyScopeView?.listingFor(summary.scope);
573
+ if (listing?.kind !== "canonical")
574
+ return summary;
575
+ return {
576
+ scope: summary.scope,
577
+ latestCollectedAt: listing.collectedAt,
578
+ versionCount: summary.versionCount,
579
+ };
580
+ }
581
+ /**
582
+ * 409 when the owner must choose between connections, 503 otherwise. The
583
+ * body names the reason and stream names only: no instance id, no row data,
584
+ * no legacy content.
585
+ */
586
+ function legacyViewRefusalResponse(scope, refusal) {
587
+ const status = refusal.reason === "ambiguous_connection" ? 409 : 503;
588
+ return jsonResponse({
589
+ error: "CANONICAL_VIEW_UNAVAILABLE",
590
+ message: `The latest data for scope "${scope}" cannot be served from the owner's connector records (${refusal.reason}).`,
591
+ status: refusal.status,
592
+ reason: refusal.reason,
593
+ ...("detail" in refusal ? { detail: refusal.detail } : {}),
594
+ ...("streams" in refusal ? { streams: refusal.streams } : {}),
595
+ ...("stream" in refusal ? { stream: refusal.stream } : {}),
596
+ ...("candidates" in refusal ? { candidates: refusal.candidates } : {}),
597
+ }, { status });
598
+ }
377
599
  /**
378
600
  * The causal marker a fresh ingest carries: the tombstone version this
379
601
  * replica currently knows for the scope, if any, so the new entry is a
@@ -393,12 +615,11 @@ async function ingestTombstoneMarker(deps, scope) {
393
615
  }
394
616
  // The delete worker wants a full Logger; the API logger is all-optional.
395
617
  function apiLoggerAsLogger(logger) {
396
- const noop = () => undefined;
397
618
  return {
398
- debug: (payload, message) => (logger?.debug ?? noop)(payload, message ?? ""),
399
- info: (payload, message) => (logger?.info ?? noop)(payload, message ?? ""),
400
- warn: (payload, message) => (logger?.warn ?? noop)(payload, message ?? ""),
401
- error: (payload, message) => (logger?.error ?? noop)(payload, message ?? ""),
619
+ debug: (payload, message) => logger?.debug?.(payload, message ?? ""),
620
+ info: (payload, message) => logger?.info?.(payload, message ?? ""),
621
+ warn: (payload, message) => logger?.warn?.(payload, message ?? ""),
622
+ error: (payload, message) => logger?.error?.(payload, message ?? ""),
402
623
  };
403
624
  }
404
625
  function notifyDataWritten(deps, event) {
@@ -414,6 +635,50 @@ function notifyDataWritten(deps, event) {
414
635
  }, "onDataWritten hook failed; record already stored");
415
636
  }
416
637
  }
638
+ /**
639
+ * Offer a just-ingested envelope to the PDPP importer.
640
+ *
641
+ * The local-ingest twin of the download worker's `importIntoPdpp`, and
642
+ * deliberately the same shape: same `ImportableEnvelope`, same total handling
643
+ * of every outcome, same permanent-vs-transient logging split. Keeping the
644
+ * two symmetric is the point — a `$pdpp` envelope must mean the same thing
645
+ * and reach the same store whether the gateway handed it over or a local
646
+ * producer POSTed it, otherwise "imported" would depend on which door the
647
+ * bytes came through.
648
+ *
649
+ * `skipped` is the ordinary legacy case (no `$pdpp` block) and stays silent.
650
+ * A rejection is a WARNING, not an error: it means the producer sent
651
+ * something this deployment could not verify — actionable, but not a failure
652
+ * of the write, which has already committed.
653
+ */
654
+ function importIngestedIntoPdpp(deps, envelope) {
655
+ if (!deps.pdppImporter)
656
+ return;
657
+ try {
658
+ const outcome = deps.pdppImporter.importEnvelope(envelope);
659
+ if (outcome.status === "rejected") {
660
+ const permanent = isPermanentRejection(outcome.rejection);
661
+ deps.logger?.warn?.({
662
+ scope: envelope.scope,
663
+ collectedAt: envelope.collectedAt,
664
+ code: outcome.rejection.code,
665
+ message: outcome.rejection.message,
666
+ permanent,
667
+ }, permanent
668
+ ? "Ingested data point was refused by the PDPP importer and will not be retried"
669
+ : "PDPP import unavailable for this ingested data point");
670
+ }
671
+ }
672
+ catch (err) {
673
+ // A throwing importer is infrastructure failing, not the envelope being
674
+ // wrong. The record is stored and indexed already, so this must never
675
+ // turn a committed write into a 500.
676
+ deps.logger?.warn?.({
677
+ scope: envelope.scope,
678
+ error: err instanceof Error ? err.message : String(err),
679
+ }, "PDPP import threw on ingest; record already stored and indexed");
680
+ }
681
+ }
417
682
  function notifyDataRead(deps, event) {
418
683
  if (!deps.onDataRead)
419
684
  return;
@@ -571,6 +836,15 @@ export function redactEnvelopeForGrantee(envelope) {
571
836
  }
572
837
  export { redactJsonEnvelopeBytesForGrantee } from "./raw-envelope.js";
573
838
  export async function handlePersonalServerDataRequest(request, deps, options = {}) {
839
+ // Filled in as the dispatch narrows, so a 500 thrown deep in a read is
840
+ // logged against the route that served it rather than a generic label. It
841
+ // is mutable for exactly that reason: the route is not known until the path
842
+ // has been parsed, and by then we are already inside the try.
843
+ const logContext = {
844
+ logger: deps.logger,
845
+ route: `${request.method} ${options.basePath ?? "/v1/data"}`,
846
+ requestId: crypto.randomUUID(),
847
+ };
574
848
  return withApiErrors(async () => {
575
849
  const url = new URL(request.url);
576
850
  const pathname = stripBasePath(url.pathname, options.basePath);
@@ -585,12 +859,15 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
585
859
  scopePrefix: url.searchParams.get("scopePrefix") ?? undefined,
586
860
  limit: normalizeLimit(url.searchParams.get("limit"), 20),
587
861
  offset: normalizeLimit(url.searchParams.get("offset"), 0),
588
- isVisible: discoveryVisibility(deps),
589
- isListable: listAuth
590
- ? (scope) => listAuthCovers(listAuth, scope)
591
- : undefined,
862
+ isVisible: listingVisibility(deps),
863
+ isListable: (scope) => !isSystemScope(scope) &&
864
+ (!listAuth || listAuthCovers(listAuth, scope)),
865
+ extraScopes: canonicalOnlyScopes(deps),
866
+ });
867
+ return jsonResponse({
868
+ ...result.response,
869
+ scopes: result.response.scopes.map((summary) => withCanonicalListing(deps, summary)),
592
870
  });
593
- return jsonResponse(result.response);
594
871
  }
595
872
  // "additions" has no dot, so it can never be a real scope name (scopes are
596
873
  // "source.dataset"); handle it before the single-segment scope path.
@@ -601,6 +878,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
601
878
  await deps.auth.authorizeOwner(request);
602
879
  const daysRaw = url.searchParams.get("days");
603
880
  const result = await summarizeDataAdditionsContract({
881
+ additions: deps.additions,
604
882
  storage: deps.storage,
605
883
  isVisible: discoveryVisibility(deps),
606
884
  timezone: url.searchParams.get("tz") ?? "UTC",
@@ -617,15 +895,80 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
617
895
  return jsonResponse(result);
618
896
  }
619
897
  const parts = pathname.split("/").filter(Boolean);
898
+ if (parts.length === 3 && parts[1] === "imports") {
899
+ if (request.method !== "PUT")
900
+ return methodNotAllowed();
901
+ await deps.auth.authorizeOwner(request);
902
+ const scopeResult = parseDataScopeContract(decodePathPart(parts[0]));
903
+ if (!scopeResult.ok)
904
+ return contractErrorResponse(scopeResult);
905
+ const operation = decodePathPart(parts[2]);
906
+ if (!/^[a-f0-9]{64}$/.test(operation)) {
907
+ return errorResponse(400, "INVALID_IMPORT_OPERATION", "operation must be 64 lowercase hexadecimal characters");
908
+ }
909
+ if (!deps.storage.commitVersion) {
910
+ throw new ProtocolError(501, "IMPORT_UNSUPPORTED", "This storage backend does not support durable owner imports");
911
+ }
912
+ if (!deps.serverOwner)
913
+ throw new ServerNotConfiguredError({
914
+ reason: "serverOwner is required for durable owner imports",
915
+ });
916
+ if (!isJsonContentType(request) || url.search.length > 0) {
917
+ return errorResponse(400, "INVALID_IMPORT", "Owner imports accept JSON root writes without query parameters");
918
+ }
919
+ const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
920
+ if (!parsed.ok)
921
+ return contractResponse(parsed.result);
922
+ if ("lineage" in parsed.body || "$binary" in parsed.body) {
923
+ return errorResponse(400, "INVALID_IMPORT", "Owner imports do not accept binary or lineage writes");
924
+ }
925
+ const bodySha256 = await sha256Hex(canonicalJsonBytes(parsed.body));
926
+ const authority = deps.gatewayConfig
927
+ ? `${deps.gatewayConfig.chainId}:${deps.gatewayConfig.contracts.dataRegistry.toLowerCase()}`
928
+ : "local";
929
+ const result = await ingestDataContract({
930
+ storage: deps.storage,
931
+ scopeParam: scopeResult.scope,
932
+ body: parsed.body,
933
+ collectedAt: (deps.now ?? (() => new Date()))().toISOString(),
934
+ status: deps.syncManager ? "syncing" : "stored",
935
+ afterTombstoneVersion: await ingestTombstoneMarker(deps, scopeResult.scope),
936
+ import: {
937
+ operation,
938
+ bodySha256,
939
+ owner: deps.serverOwner.toLowerCase(),
940
+ authority,
941
+ },
942
+ });
943
+ if (!result.ok)
944
+ return contractErrorResponse(result);
945
+ notifyNewData(deps.syncManager);
946
+ if (!result.replay)
947
+ notifyDataWritten(deps, {
948
+ scope: result.scope,
949
+ collectedAt: result.collectedAt,
950
+ });
951
+ return jsonResponse(result.response, {
952
+ status: result.replay ? 200 : 201,
953
+ });
954
+ }
620
955
  if (parts.length === 2 && parts[1] === "versions") {
621
956
  if (request.method !== "GET")
622
957
  return methodNotAllowed();
623
- const listAuth = await deps.auth.authorizeBuilderList(request, {
624
- grantId: selectedGrantId(request, url),
625
- });
958
+ const versionsScopeParam = decodePathPart(parts[0]);
959
+ const versionsGrantId = selectedGrantId(request, url);
960
+ const listAuth = deps.auth.authorizeScopeVersions
961
+ ? await deps.auth.authorizeScopeVersions({
962
+ request,
963
+ scope: versionsScopeParam,
964
+ grantId: versionsGrantId,
965
+ })
966
+ : await deps.auth.authorizeBuilderList(request, {
967
+ grantId: versionsGrantId,
968
+ });
626
969
  // A scope's version history is as much its data as its content is, so
627
970
  // a non-owner needs a grant covering it, exactly like a read.
628
- const scopeResult = parseDataScopeContract(decodePathPart(parts[0]));
971
+ const scopeResult = parseDataScopeContract(versionsScopeParam);
629
972
  if (scopeResult.ok &&
630
973
  listAuth &&
631
974
  !listAuthCovers(listAuth, scopeResult.scope)) {
@@ -738,10 +1081,16 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
738
1081
  if (parts.length !== 1)
739
1082
  return notFound();
740
1083
  const scopeParam = decodePathPart(parts[0]);
1084
+ logContext.route = `${request.method} ${options.basePath ?? "/v1/data"}/:scope`;
741
1085
  if (request.method === "GET") {
742
1086
  const scopeResult = parseDataScopeContract(scopeParam);
743
1087
  if (!scopeResult.ok)
744
1088
  return contractErrorResponse(scopeResult);
1089
+ logContext.detail = { scope: scopeResult.scope };
1090
+ // An empty `fileId=` or `at=` selects no version: it is the latest
1091
+ // read, as storage already treats it.
1092
+ const fileIdParam = url.searchParams.get("fileId") || undefined;
1093
+ const atParam = url.searchParams.get("at") || undefined;
745
1094
  // `?view=stored` returns the body as stored, without the read-time
746
1095
  // legacy projection. Owner only; checked after auth below.
747
1096
  const view = url.searchParams.get("view");
@@ -750,8 +1099,8 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
750
1099
  }
751
1100
  const selectedEntry = deps.storage.findEntry({
752
1101
  scope: scopeResult.scope,
753
- fileId: url.searchParams.get("fileId") ?? undefined,
754
- at: url.searchParams.get("at") ?? undefined,
1102
+ fileId: fileIdParam,
1103
+ at: atParam,
755
1104
  });
756
1105
  // Holds the base64-encoded gateway payForOperation response body when
757
1106
  // X402 succeeds — emitted on the final read response as X-PAYMENT-RESPONSE.
@@ -761,13 +1110,22 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
761
1110
  request,
762
1111
  scope: scopeResult.scope,
763
1112
  grantId,
764
- fileId: url.searchParams.get("fileId") ?? selectedEntry?.fileId ?? undefined,
765
- at: url.searchParams.get("at") ?? undefined,
1113
+ fileId: fileIdParam ?? selectedEntry?.fileId ?? undefined,
1114
+ at: atParam,
766
1115
  });
1116
+ // Select the body after auth and before deletion or payment. A file's
1117
+ // version and re-add marker authorize that file, not canonical rows.
1118
+ // `?view=stored` asks for the stored file's body, so it never reads the
1119
+ // canonical view either.
1120
+ const latest = fileIdParam === undefined && atParam === undefined && view === null
1121
+ ? await deps.legacyScopeView?.readLatest(scopeResult.scope)
1122
+ : undefined;
1123
+ const canonicalOwned = latest !== undefined &&
1124
+ deps.legacyScopeView?.ownsScope(scopeResult.scope);
767
1125
  // Deletion gate, after auth (so unauthenticated callers learn nothing)
768
1126
  // and before payment (so nobody is charged for deleted data). Covers
769
1127
  // both a stale local copy of a tombstoned scope and a local miss.
770
- await assertScopeNotDeleted(deps, scopeResult.scope, selectedEntry);
1128
+ await assertScopeNotDeleted(deps, scopeResult.scope, canonicalOwned ? undefined : selectedEntry, canonicalOwned ? "canonical" : "file");
771
1129
  if (view === "stored" && !isOwnerView(authResult)) {
772
1130
  return errorResponse(403, "FORBIDDEN", "Only the owner can read the stored view");
773
1131
  }
@@ -780,6 +1138,13 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
780
1138
  // answer unreachable through this route. Nothing here blocks: a
781
1139
  // previous version is served now, the fresh one lands in a later read.
782
1140
  notifyDataRead(deps, { scope: scopeResult.scope });
1141
+ // A canonical scope's latest body is the canonical view, and only it:
1142
+ // a refusal is answered, never replaced by the legacy file. Explicit
1143
+ // versions stay legacy history. Read before payment, so a builder is
1144
+ // never challenged or charged for a read that is then refused.
1145
+ if (latest?.kind === "refused") {
1146
+ return legacyViewRefusalResponse(scopeResult.scope, latest.refusal);
1147
+ }
783
1148
  // X402 payment dance for builder reads. Owner-exempt reads (the
784
1149
  // grantId sentinels "owner" / "policy-bypass") skip payment entirely
785
1150
  // since there's no payable op to attach the payment to.
@@ -833,6 +1198,12 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
833
1198
  willCharge,
834
1199
  });
835
1200
  }
1201
+ if (willCharge && latest?.kind === "canonical") {
1202
+ return legacyViewRefusalResponse(scopeResult.scope, {
1203
+ status: "unavailable",
1204
+ reason: "paid_read_unsupported",
1205
+ });
1206
+ }
836
1207
  // Keep the full condition inline (not `if (willCharge)`) so TS narrows
837
1208
  // deps.gateway/gatewayConfig/gatewayUrl to non-undefined in the block.
838
1209
  if (deps.paymentEnabled &&
@@ -847,8 +1218,8 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
847
1218
  deps,
848
1219
  request,
849
1220
  scope: scopeResult.scope,
850
- fileIdParam: url.searchParams.get("fileId") ?? undefined,
851
- atParam: url.searchParams.get("at") ?? undefined,
1221
+ fileIdParam,
1222
+ atParam,
852
1223
  grantId: resolvedGrantId,
853
1224
  builder: builder,
854
1225
  gateway: deps.gateway,
@@ -869,25 +1240,38 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
869
1240
  // breakdown / paidAt / paymentNonce without a second gateway round-trip.
870
1241
  paymentResponseHeader = encodePaymentResponseHeader(x402Result.payResponse);
871
1242
  }
872
- const result = await readDataContract({
873
- storage: deps.storage,
874
- scopeParam: scopeResult.scope,
875
- fileId: url.searchParams.get("fileId") ?? undefined,
876
- at: url.searchParams.get("at") ?? undefined,
877
- ...(view === "stored" ? { view } : {}),
878
- });
879
- if (!result.ok) {
880
- if (result.status === 404) {
881
- // The entry selected above can vanish before the read (a sync
882
- // reconcile applying a tombstone concurrently); answer 410, not
883
- // 404, when that is why.
884
- await assertScopeNotDeleted(deps, scopeResult.scope, undefined);
1243
+ const headers = {};
1244
+ headers["X-Vana-Data-Source"] =
1245
+ latest?.kind === "canonical" ? "canonical" : "legacy";
1246
+ let envelope;
1247
+ let servedFileId;
1248
+ if (latest?.kind === "canonical") {
1249
+ envelope = latest.envelope;
1250
+ headers.ETag = `"${latest.revision}"`;
1251
+ }
1252
+ else {
1253
+ const result = await readDataContract({
1254
+ storage: deps.storage,
1255
+ scopeParam: scopeResult.scope,
1256
+ fileId: fileIdParam,
1257
+ at: atParam,
1258
+ ...(view === "stored" ? { view } : {}),
1259
+ });
1260
+ if (!result.ok) {
1261
+ if (result.status === 404) {
1262
+ // The entry selected above can vanish before the read (a sync
1263
+ // reconcile applying a tombstone concurrently); answer 410, not
1264
+ // 404, when that is why.
1265
+ await assertScopeNotDeleted(deps, scopeResult.scope, undefined);
1266
+ }
1267
+ return contractErrorResponse(result);
885
1268
  }
886
- return contractErrorResponse(result);
1269
+ envelope = result.envelope;
1270
+ servedFileId = fileIdParam ?? selectedEntry?.fileId ?? undefined;
887
1271
  }
888
1272
  const servedEnvelope = isOwnerView(authResult)
889
- ? result.envelope
890
- : redactEnvelopeForGrantee(result.envelope);
1273
+ ? envelope
1274
+ : redactEnvelopeForGrantee(envelope);
891
1275
  const logId = deps.createLogId?.() ?? crypto.randomUUID();
892
1276
  const timestamp = (deps.now ?? (() => new Date()))().toISOString();
893
1277
  const ipAddress = request.headers.get("x-forwarded-for") ??
@@ -911,9 +1295,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
911
1295
  return;
912
1296
  reportPersonalServerReadFulfillment(deps, {
913
1297
  builder: authResult.builder,
914
- fileId: url.searchParams.get("fileId") ??
915
- selectedEntry?.fileId ??
916
- undefined,
1298
+ fileId: servedFileId,
917
1299
  grantId: authResult.grantId,
918
1300
  ipAddress,
919
1301
  logId,
@@ -924,7 +1306,6 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
924
1306
  userAgent,
925
1307
  });
926
1308
  };
927
- const headers = {};
928
1309
  if (paymentResponseHeader) {
929
1310
  headers["X-PAYMENT-RESPONSE"] = paymentResponseHeader;
930
1311
  }
@@ -989,7 +1370,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
989
1370
  // so the builder's retry with the same still-valid proof is accepted;
990
1371
  // after commit the proof stays consumed (a retry would be a duplicate).
991
1372
  let committed = false;
992
- let releaseStamp;
1373
+ let ingestStamp;
993
1374
  const failWrite = async (response) => {
994
1375
  await writeAuth?.releaseProof?.();
995
1376
  return response;
@@ -998,9 +1379,20 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
998
1379
  const scopeResult = parseDataScopeContract(scopeParam);
999
1380
  if (!scopeResult.ok)
1000
1381
  return failWrite(contractErrorResponse(scopeResult));
1001
- const stamp = reserveIngestStamp(deps.storage, scopeResult.scope, deps.now ?? (() => new Date()));
1002
- releaseStamp = stamp.release;
1003
- const collectedAtValue = stamp.value;
1382
+ const parsedPrecondition = parseLegacyPrecondition(request);
1383
+ if (parsedPrecondition instanceof Response)
1384
+ return failWrite(parsedPrecondition);
1385
+ const producerHeader = parseProducerHeader(request);
1386
+ if (producerHeader instanceof Response)
1387
+ return failWrite(producerHeader);
1388
+ if (writeAuth && producerHeader.producer) {
1389
+ return failWrite(errorResponse(403, "NOT_OWNER", "Only the owner may assert a producer"));
1390
+ }
1391
+ ingestStamp = reserveIngestStamp(deps.now ?? (() => new Date()), deps.storage, scopeResult.scope);
1392
+ if (!ingestStamp) {
1393
+ return failWrite(errorResponse(429, "INGEST_CAPACITY", "Too many writes for this scope; retry when the clock advances"));
1394
+ }
1395
+ const collectedAtValue = ingestStamp.collectedAt;
1004
1396
  const status = deps.syncManager ? "syncing" : "stored";
1005
1397
  const afterTombstoneVersion = await ingestTombstoneMarker(deps, scopeResult.scope);
1006
1398
  // Builder writes land in the same access log as builder reads, so the
@@ -1040,6 +1432,9 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1040
1432
  // data needs no schema at all — we ingest it schemaless. (Structured JSON
1041
1433
  // below still resolves a schema for validation/metadata.)
1042
1434
  if (!isJsonContentType(request)) {
1435
+ if (producerHeader.producer) {
1436
+ return failWrite(errorResponse(400, "INVALID_PRODUCER", "Producer attribution requires a JSON body"));
1437
+ }
1043
1438
  if (supersede !== null) {
1044
1439
  return failWrite(errorResponse(400, "INVALID_SUPERSEDE", "supersede applies to JSON writes only"));
1045
1440
  }
@@ -1060,6 +1455,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1060
1455
  attribution: writeAuth?.attribution,
1061
1456
  lineage,
1062
1457
  afterTombstoneVersion,
1458
+ precondition: parsedPrecondition,
1063
1459
  });
1064
1460
  if (!result.ok)
1065
1461
  return failWrite(contractErrorResponse(result));
@@ -1084,6 +1480,9 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1084
1480
  const parsed = await parseJsonObjectBody(request, "Request body must be valid JSON");
1085
1481
  if (!parsed.ok)
1086
1482
  return failWrite(contractResponse(parsed.result));
1483
+ const provenance = await parseProducerProvenance(producerHeader.encodedProvenance, parsed.body);
1484
+ if (provenance instanceof Response)
1485
+ return failWrite(provenance);
1087
1486
  // Derivative writes: validate the caller's `lineage` before anything
1088
1487
  // is stored (a failure here throws, releasing the builder's proof).
1089
1488
  const lineage = await prepareWriteLineage(deps, scopeResult.scope, extractLineageField(parsed.body));
@@ -1098,9 +1497,14 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1098
1497
  attribution: writeAuth?.attribution,
1099
1498
  lineage,
1100
1499
  afterTombstoneVersion,
1500
+ precondition: parsedPrecondition,
1501
+ producer: producerHeader.producer,
1502
+ producerProvenance: provenance,
1101
1503
  });
1102
1504
  if (!result.ok)
1103
1505
  return failWrite(contractErrorResponse(result));
1506
+ if (result.replay)
1507
+ throw new Error("Ordinary POST cannot replay an import");
1104
1508
  committed = true;
1105
1509
  deps.logger?.info?.({
1106
1510
  scope: scopeResult.scope,
@@ -1113,6 +1517,14 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1113
1517
  ? await supersedeAfterCommit(deps, scopeResult.scope, collectedAtValue)
1114
1518
  : undefined;
1115
1519
  notifyNewData(deps.syncManager);
1520
+ // The parsed body is the envelope's `data`, which is where a producer
1521
+ // stamps `$pdpp`. Offered here rather than inside the ingest contract
1522
+ // so the import stays strictly after the record is durable.
1523
+ importIngestedIntoPdpp(deps, {
1524
+ scope: scopeResult.scope,
1525
+ collectedAt: collectedAtValue,
1526
+ data: parsed.body,
1527
+ });
1116
1528
  notifyDataWritten(deps, {
1117
1529
  scope: scopeResult.scope,
1118
1530
  collectedAt: collectedAtValue,
@@ -1139,7 +1551,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1139
1551
  error: err.cause instanceof Error
1140
1552
  ? err.cause.message
1141
1553
  : String(err.cause),
1142
- }, "Envelope written but indexing failed; record persisted unindexed");
1554
+ }, "Envelope write incomplete after durable commit; boot recovery may finish it");
1143
1555
  }
1144
1556
  else if (!committed) {
1145
1557
  await writeAuth?.releaseProof?.();
@@ -1147,7 +1559,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1147
1559
  throw err;
1148
1560
  }
1149
1561
  finally {
1150
- releaseStamp?.();
1562
+ ingestStamp?.release();
1151
1563
  }
1152
1564
  }
1153
1565
  if (request.method === "DELETE") {
@@ -1170,6 +1582,18 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1170
1582
  // first tombstone). Until that lands the only honest answer is 501.
1171
1583
  throw new LineageCascadeUnavailableError({ scope: parsed.scope });
1172
1584
  }
1585
+ // A canonical-owned scope's latest body is its connection's rows. No
1586
+ // store seam deletes one legacy scope's rows (streams can be shared,
1587
+ // and the account and its generation are the unit of erasure), so a
1588
+ // 200 here would leave the data readable. Refuse before any gateway or
1589
+ // file change; the connection's own delete erases its rows.
1590
+ if (deps.legacyScopeView?.ownsScope(parsed.scope)) {
1591
+ return jsonResponse({
1592
+ error: "CANONICAL_DELETE_UNSUPPORTED",
1593
+ message: `Scope "${parsed.scope}" is served from the owner's connector records, which this route cannot delete. Nothing was deleted; delete the connection to erase its records.`,
1594
+ scope: parsed.scope,
1595
+ }, { status: 409 });
1596
+ }
1173
1597
  // Durable order: gateway tombstone -> storage blobs -> local copy. The
1174
1598
  // sync manager owns the remote ports; without one (sync disabled or
1175
1599
  // no owner key material) only the local copy goes and the result says
@@ -1225,7 +1649,7 @@ export async function handlePersonalServerDataRequest(request, deps, options = {
1225
1649
  return jsonResponse(result, { status: 200 });
1226
1650
  }
1227
1651
  return methodNotAllowed();
1228
- });
1652
+ }, logContext);
1229
1653
  }
1230
1654
  export async function handlePersonalServerAccessLogsRequest(request, deps, options = {}) {
1231
1655
  return withApiErrors(async () => {
@@ -1271,6 +1695,9 @@ export async function handlePersonalServerSyncRequest(request, deps, options = {
1271
1695
  }));
1272
1696
  }
1273
1697
  return notFound();
1698
+ }, {
1699
+ logger: deps.logger,
1700
+ route: `${request.method} ${options.basePath ?? "/v1/sync"}`,
1274
1701
  });
1275
1702
  }
1276
1703
  export async function handlePersonalServerGrantsRequest(request, deps, options = {}) {