@opendatalabs/personal-server-ts-core 1.34.1 → 1.35.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 (421) hide show
  1. package/dist/additions/durable.d.ts +32 -2
  2. package/dist/additions/durable.d.ts.map +1 -1
  3. package/dist/additions/durable.js +237 -88
  4. package/dist/additions/durable.js.map +1 -1
  5. package/dist/additions/version-inventory.d.ts +4 -0
  6. package/dist/additions/version-inventory.d.ts.map +1 -1
  7. package/dist/additions/version-inventory.js +11 -1
  8. package/dist/additions/version-inventory.js.map +1 -1
  9. package/dist/api/index.d.ts +19 -2
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/index.js +185 -13
  12. package/dist/api/index.js.map +1 -1
  13. package/dist/api/oura-collection-requirements.d.ts +16 -0
  14. package/dist/api/oura-collection-requirements.d.ts.map +1 -0
  15. package/dist/api/oura-collection-requirements.js +70 -0
  16. package/dist/api/oura-collection-requirements.js.map +1 -0
  17. package/dist/api/scope-upload-batches.d.ts +18 -0
  18. package/dist/api/scope-upload-batches.d.ts.map +1 -0
  19. package/dist/api/scope-upload-batches.js +71 -0
  20. package/dist/api/scope-upload-batches.js.map +1 -0
  21. package/dist/api/scope-uploads.d.ts +112 -0
  22. package/dist/api/scope-uploads.d.ts.map +1 -0
  23. package/dist/api/scope-uploads.js +369 -0
  24. package/dist/api/scope-uploads.js.map +1 -0
  25. package/dist/client.d.ts +70 -0
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +1 -0
  28. package/dist/client.js.map +1 -1
  29. package/dist/contracts/data.d.ts +8 -0
  30. package/dist/contracts/data.d.ts.map +1 -1
  31. package/dist/contracts/data.js +31 -1
  32. package/dist/contracts/data.js.map +1 -1
  33. package/dist/contracts/grants.d.ts.map +1 -1
  34. package/dist/contracts/grants.js +3 -0
  35. package/dist/contracts/grants.js.map +1 -1
  36. package/dist/contracts/index.d.ts +3 -1
  37. package/dist/contracts/index.d.ts.map +1 -1
  38. package/dist/contracts/index.js +3 -1
  39. package/dist/contracts/index.js.map +1 -1
  40. package/dist/contracts/oura-collection-requirements.d.ts +17 -0
  41. package/dist/contracts/oura-collection-requirements.d.ts.map +1 -0
  42. package/dist/contracts/oura-collection-requirements.js +49 -0
  43. package/dist/contracts/oura-collection-requirements.js.map +1 -0
  44. package/dist/legacy-projection/bindings.d.ts +1 -7
  45. package/dist/legacy-projection/bindings.d.ts.map +1 -1
  46. package/dist/legacy-projection/bindings.js +162 -28
  47. package/dist/legacy-projection/bindings.js.map +1 -1
  48. package/dist/legacy-projection/index.d.ts +6 -5
  49. package/dist/legacy-projection/index.d.ts.map +1 -1
  50. package/dist/legacy-projection/index.js +9 -21
  51. package/dist/legacy-projection/index.js.map +1 -1
  52. package/dist/legacy-projection/oura-browser-bindings.d.ts +2 -0
  53. package/dist/legacy-projection/oura-browser-bindings.d.ts.map +1 -1
  54. package/dist/legacy-projection/oura-browser-bindings.js +2 -0
  55. package/dist/legacy-projection/oura-browser-bindings.js.map +1 -1
  56. package/dist/legacy-projection/strava-browser-bindings.d.ts +3 -0
  57. package/dist/legacy-projection/strava-browser-bindings.d.ts.map +1 -0
  58. package/dist/legacy-projection/strava-browser-bindings.js +32 -0
  59. package/dist/legacy-projection/strava-browser-bindings.js.map +1 -0
  60. package/dist/legacy-projection/types.d.ts +44 -12
  61. package/dist/legacy-projection/types.d.ts.map +1 -1
  62. package/dist/mcp/connection-api.d.ts +11 -1
  63. package/dist/mcp/connection-api.d.ts.map +1 -1
  64. package/dist/mcp/connection-api.js +94 -9
  65. package/dist/mcp/connection-api.js.map +1 -1
  66. package/dist/mcp/index.d.ts +1 -0
  67. package/dist/mcp/index.d.ts.map +1 -1
  68. package/dist/mcp/index.js +1 -0
  69. package/dist/mcp/index.js.map +1 -1
  70. package/dist/mcp/server.d.ts +9 -0
  71. package/dist/mcp/server.d.ts.map +1 -1
  72. package/dist/mcp/server.js +19 -6
  73. package/dist/mcp/server.js.map +1 -1
  74. package/dist/mcp/store.d.ts +10 -1
  75. package/dist/mcp/store.d.ts.map +1 -1
  76. package/dist/mcp/store.js +51 -0
  77. package/dist/mcp/store.js.map +1 -1
  78. package/dist/mcp/tools.d.ts +52 -2
  79. package/dist/mcp/tools.d.ts.map +1 -1
  80. package/dist/mcp/tools.js +395 -11
  81. package/dist/mcp/tools.js.map +1 -1
  82. package/dist/mcp/types.d.ts +36 -2
  83. package/dist/mcp/types.d.ts.map +1 -1
  84. package/dist/mcp/types.js +12 -0
  85. package/dist/mcp/types.js.map +1 -1
  86. package/dist/mcp/write-client.d.ts +81 -0
  87. package/dist/mcp/write-client.d.ts.map +1 -0
  88. package/dist/mcp/write-client.js +140 -0
  89. package/dist/mcp/write-client.js.map +1 -0
  90. package/dist/oura/history-policy.d.ts +12 -0
  91. package/dist/oura/history-policy.d.ts.map +1 -0
  92. package/dist/oura/history-policy.js +54 -0
  93. package/dist/oura/history-policy.js.map +1 -0
  94. package/dist/oura/index.d.ts +51 -0
  95. package/dist/oura/index.d.ts.map +1 -0
  96. package/dist/oura/index.js +315 -0
  97. package/dist/oura/index.js.map +1 -0
  98. package/dist/pdpp/approval.d.ts +1 -1
  99. package/dist/pdpp/approval.d.ts.map +1 -1
  100. package/dist/pdpp/approval.js +29 -10
  101. package/dist/pdpp/approval.js.map +1 -1
  102. package/dist/pdpp/declaration-limits.d.ts +7 -0
  103. package/dist/pdpp/declaration-limits.d.ts.map +1 -0
  104. package/dist/pdpp/declaration-limits.js +7 -0
  105. package/dist/pdpp/declaration-limits.js.map +1 -0
  106. package/dist/pdpp/declaration.d.ts +2 -1
  107. package/dist/pdpp/declaration.d.ts.map +1 -1
  108. package/dist/pdpp/declaration.js +2 -1
  109. package/dist/pdpp/declaration.js.map +1 -1
  110. package/dist/pdpp/index.d.ts +3 -2
  111. package/dist/pdpp/index.d.ts.map +1 -1
  112. package/dist/pdpp/index.js +3 -2
  113. package/dist/pdpp/index.js.map +1 -1
  114. package/dist/pdpp/issuance.d.ts.map +1 -1
  115. package/dist/pdpp/issuance.js +6 -2
  116. package/dist/pdpp/issuance.js.map +1 -1
  117. package/dist/pdpp/resolve.d.ts +12 -1
  118. package/dist/pdpp/resolve.d.ts.map +1 -1
  119. package/dist/pdpp/resolve.js +156 -58
  120. package/dist/pdpp/resolve.js.map +1 -1
  121. package/dist/pdpp/selection.d.ts +30 -1
  122. package/dist/pdpp/selection.d.ts.map +1 -1
  123. package/dist/pdpp/selection.js +77 -26
  124. package/dist/pdpp/selection.js.map +1 -1
  125. package/dist/pdpp/time.d.ts +39 -0
  126. package/dist/pdpp/time.d.ts.map +1 -0
  127. package/dist/pdpp/time.js +173 -0
  128. package/dist/pdpp/time.js.map +1 -0
  129. package/dist/pdpp/tokens.d.ts.map +1 -1
  130. package/dist/pdpp/tokens.js +4 -2
  131. package/dist/pdpp/tokens.js.map +1 -1
  132. package/dist/pdpp/types.d.ts +18 -2
  133. package/dist/pdpp/types.d.ts.map +1 -1
  134. package/dist/pdpp/types.js +7 -0
  135. package/dist/pdpp/types.js.map +1 -1
  136. package/dist/policy/reserved-scopes.d.ts +7 -0
  137. package/dist/policy/reserved-scopes.d.ts.map +1 -1
  138. package/dist/policy/reserved-scopes.js +12 -1
  139. package/dist/policy/reserved-scopes.js.map +1 -1
  140. package/dist/ports/index.d.ts +89 -1
  141. package/dist/ports/index.d.ts.map +1 -1
  142. package/dist/ports/index.js.map +1 -1
  143. package/dist/ports/pdpp-auth.d.ts +17 -27
  144. package/dist/ports/pdpp-auth.d.ts.map +1 -1
  145. package/dist/ports/pdpp-auth.js +14 -28
  146. package/dist/ports/pdpp-auth.js.map +1 -1
  147. package/dist/publication/publish.d.ts +96 -0
  148. package/dist/publication/publish.d.ts.map +1 -0
  149. package/dist/publication/publish.js +261 -0
  150. package/dist/publication/publish.js.map +1 -0
  151. package/dist/replication/completion.d.ts +20 -0
  152. package/dist/replication/completion.d.ts.map +1 -0
  153. package/dist/replication/completion.js +73 -0
  154. package/dist/replication/completion.js.map +1 -0
  155. package/dist/replication/coordinator-state.d.ts +29 -0
  156. package/dist/replication/coordinator-state.d.ts.map +1 -0
  157. package/dist/replication/coordinator-state.js +88 -0
  158. package/dist/replication/coordinator-state.js.map +1 -0
  159. package/dist/replication/coordinator-state.test-utils.d.ts +32 -0
  160. package/dist/replication/coordinator-state.test-utils.d.ts.map +1 -0
  161. package/dist/replication/coordinator-state.test-utils.js +113 -0
  162. package/dist/replication/coordinator-state.test-utils.js.map +1 -0
  163. package/dist/replication/coordinator.d.ts +264 -0
  164. package/dist/replication/coordinator.d.ts.map +1 -0
  165. package/dist/replication/coordinator.js +499 -0
  166. package/dist/replication/coordinator.js.map +1 -0
  167. package/dist/replication/copies.d.ts +126 -0
  168. package/dist/replication/copies.d.ts.map +1 -0
  169. package/dist/replication/copies.js +437 -0
  170. package/dist/replication/copies.js.map +1 -0
  171. package/dist/replication/copy-promotion.d.ts +132 -0
  172. package/dist/replication/copy-promotion.d.ts.map +1 -0
  173. package/dist/replication/copy-promotion.js +126 -0
  174. package/dist/replication/copy-promotion.js.map +1 -0
  175. package/dist/replication/door.d.ts +33 -0
  176. package/dist/replication/door.d.ts.map +1 -0
  177. package/dist/replication/door.js +10 -0
  178. package/dist/replication/door.js.map +1 -0
  179. package/dist/replication/dpv2.d.ts +141 -0
  180. package/dist/replication/dpv2.d.ts.map +1 -0
  181. package/dist/replication/dpv2.js +320 -0
  182. package/dist/replication/dpv2.js.map +1 -0
  183. package/dist/replication/effects.d.ts +68 -0
  184. package/dist/replication/effects.d.ts.map +1 -0
  185. package/dist/replication/effects.js +89 -0
  186. package/dist/replication/effects.js.map +1 -0
  187. package/dist/replication/engine.d.ts +378 -0
  188. package/dist/replication/engine.d.ts.map +1 -0
  189. package/dist/replication/engine.js +3197 -0
  190. package/dist/replication/engine.js.map +1 -0
  191. package/dist/replication/envelope.d.ts +481 -0
  192. package/dist/replication/envelope.d.ts.map +1 -0
  193. package/dist/replication/envelope.js +192 -0
  194. package/dist/replication/envelope.js.map +1 -0
  195. package/dist/replication/evidence.d.ts +118 -0
  196. package/dist/replication/evidence.d.ts.map +1 -0
  197. package/dist/replication/evidence.js +198 -0
  198. package/dist/replication/evidence.js.map +1 -0
  199. package/dist/replication/finality.d.ts +16 -0
  200. package/dist/replication/finality.d.ts.map +1 -0
  201. package/dist/replication/finality.js +11 -0
  202. package/dist/replication/finality.js.map +1 -0
  203. package/dist/replication/fixtures.d.ts +16 -0
  204. package/dist/replication/fixtures.d.ts.map +1 -0
  205. package/dist/replication/fixtures.js +47 -0
  206. package/dist/replication/fixtures.js.map +1 -0
  207. package/dist/replication/host-config.d.ts +52 -0
  208. package/dist/replication/host-config.d.ts.map +1 -0
  209. package/dist/replication/host-config.js +111 -0
  210. package/dist/replication/host-config.js.map +1 -0
  211. package/dist/replication/host-driver.d.ts +95 -0
  212. package/dist/replication/host-driver.d.ts.map +1 -0
  213. package/dist/replication/host-driver.js +202 -0
  214. package/dist/replication/host-driver.js.map +1 -0
  215. package/dist/replication/host-ports.d.ts +48 -0
  216. package/dist/replication/host-ports.d.ts.map +1 -0
  217. package/dist/replication/host-ports.js +69 -0
  218. package/dist/replication/host-ports.js.map +1 -0
  219. package/dist/replication/identity.d.ts +13 -0
  220. package/dist/replication/identity.d.ts.map +1 -0
  221. package/dist/replication/identity.js +22 -0
  222. package/dist/replication/identity.js.map +1 -0
  223. package/dist/replication/limits.d.ts +124 -0
  224. package/dist/replication/limits.d.ts.map +1 -0
  225. package/dist/replication/limits.js +182 -0
  226. package/dist/replication/limits.js.map +1 -0
  227. package/dist/replication/merge-plan.d.ts +92 -0
  228. package/dist/replication/merge-plan.d.ts.map +1 -0
  229. package/dist/replication/merge-plan.js +229 -0
  230. package/dist/replication/merge-plan.js.map +1 -0
  231. package/dist/replication/namespace.d.ts +4 -0
  232. package/dist/replication/namespace.d.ts.map +1 -0
  233. package/dist/replication/namespace.js +7 -0
  234. package/dist/replication/namespace.js.map +1 -0
  235. package/dist/replication/outcome.d.ts +178 -0
  236. package/dist/replication/outcome.d.ts.map +1 -0
  237. package/dist/replication/outcome.js +83 -0
  238. package/dist/replication/outcome.js.map +1 -0
  239. package/dist/replication/payload.d.ts +39 -0
  240. package/dist/replication/payload.d.ts.map +1 -0
  241. package/dist/replication/payload.js +137 -0
  242. package/dist/replication/payload.js.map +1 -0
  243. package/dist/replication/preparation.d.ts +42 -0
  244. package/dist/replication/preparation.d.ts.map +1 -0
  245. package/dist/replication/preparation.js +118 -0
  246. package/dist/replication/preparation.js.map +1 -0
  247. package/dist/replication/rebaseline.d.ts +33 -0
  248. package/dist/replication/rebaseline.d.ts.map +1 -0
  249. package/dist/replication/rebaseline.js +40 -0
  250. package/dist/replication/rebaseline.js.map +1 -0
  251. package/dist/replication/rebinding.d.ts +43 -0
  252. package/dist/replication/rebinding.d.ts.map +1 -0
  253. package/dist/replication/rebinding.js +52 -0
  254. package/dist/replication/rebinding.js.map +1 -0
  255. package/dist/replication/receive.d.ts +57 -0
  256. package/dist/replication/receive.d.ts.map +1 -0
  257. package/dist/replication/receive.js +114 -0
  258. package/dist/replication/receive.js.map +1 -0
  259. package/dist/replication/record-reducer.d.ts +16 -0
  260. package/dist/replication/record-reducer.d.ts.map +1 -0
  261. package/dist/replication/record-reducer.js +52 -0
  262. package/dist/replication/record-reducer.js.map +1 -0
  263. package/dist/replication/revision.d.ts +24 -0
  264. package/dist/replication/revision.d.ts.map +1 -0
  265. package/dist/replication/revision.js +35 -0
  266. package/dist/replication/revision.js.map +1 -0
  267. package/dist/replication/roster.d.ts +53 -0
  268. package/dist/replication/roster.d.ts.map +1 -0
  269. package/dist/replication/roster.js +115 -0
  270. package/dist/replication/roster.js.map +1 -0
  271. package/dist/replication/schedule.d.ts +73 -0
  272. package/dist/replication/schedule.d.ts.map +1 -0
  273. package/dist/replication/schedule.js +182 -0
  274. package/dist/replication/schedule.js.map +1 -0
  275. package/dist/replication/stream.d.ts +73 -0
  276. package/dist/replication/stream.d.ts.map +1 -0
  277. package/dist/replication/stream.js +542 -0
  278. package/dist/replication/stream.js.map +1 -0
  279. package/dist/replication/transport.d.ts +236 -0
  280. package/dist/replication/transport.d.ts.map +1 -0
  281. package/dist/replication/transport.js +440 -0
  282. package/dist/replication/transport.js.map +1 -0
  283. package/dist/replication/visibility-engine.d.ts +337 -0
  284. package/dist/replication/visibility-engine.d.ts.map +1 -0
  285. package/dist/replication/visibility-engine.js +602 -0
  286. package/dist/replication/visibility-engine.js.map +1 -0
  287. package/dist/replication/visibility-policy.d.ts +26 -0
  288. package/dist/replication/visibility-policy.d.ts.map +1 -0
  289. package/dist/replication/visibility-policy.js +33 -0
  290. package/dist/replication/visibility-policy.js.map +1 -0
  291. package/dist/replication/writer-identity-store.d.ts +100 -0
  292. package/dist/replication/writer-identity-store.d.ts.map +1 -0
  293. package/dist/replication/writer-identity-store.js +218 -0
  294. package/dist/replication/writer-identity-store.js.map +1 -0
  295. package/dist/replication/writer-identity.d.ts +252 -0
  296. package/dist/replication/writer-identity.d.ts.map +1 -0
  297. package/dist/replication/writer-identity.js +281 -0
  298. package/dist/replication/writer-identity.js.map +1 -0
  299. package/dist/replication/writer-identity.test-utils.d.ts +24 -0
  300. package/dist/replication/writer-identity.test-utils.d.ts.map +1 -0
  301. package/dist/replication/writer-identity.test-utils.js +86 -0
  302. package/dist/replication/writer-identity.test-utils.js.map +1 -0
  303. package/dist/schemas/server-config.d.ts +13 -0
  304. package/dist/schemas/server-config.d.ts.map +1 -1
  305. package/dist/schemas/server-config.js +24 -0
  306. package/dist/schemas/server-config.js.map +1 -1
  307. package/dist/storage/adapters/index.d.ts +3 -1
  308. package/dist/storage/adapters/index.d.ts.map +1 -1
  309. package/dist/storage/adapters/index.js +2 -0
  310. package/dist/storage/adapters/index.js.map +1 -1
  311. package/dist/storage/adapters/interface.d.ts +18 -1
  312. package/dist/storage/adapters/interface.d.ts.map +1 -1
  313. package/dist/storage/adapters/interface.js +13 -1
  314. package/dist/storage/adapters/interface.js.map +1 -1
  315. package/dist/storage/adapters/sdk.d.ts +2 -1
  316. package/dist/storage/adapters/sdk.d.ts.map +1 -1
  317. package/dist/storage/adapters/sdk.js +21 -3
  318. package/dist/storage/adapters/sdk.js.map +1 -1
  319. package/dist/storage/adapters/vana-audience.d.ts +17 -5
  320. package/dist/storage/adapters/vana-audience.d.ts.map +1 -1
  321. package/dist/storage/adapters/vana-audience.js +24 -9
  322. package/dist/storage/adapters/vana-audience.js.map +1 -1
  323. package/dist/storage/adapters/vana.d.ts +2 -2
  324. package/dist/storage/adapters/vana.d.ts.map +1 -1
  325. package/dist/storage/adapters/vana.js +30 -22
  326. package/dist/storage/adapters/vana.js.map +1 -1
  327. package/dist/storage/canonical-legacy-projection.d.ts +1 -1
  328. package/dist/storage/canonical-legacy-projection.d.ts.map +1 -1
  329. package/dist/storage/canonical-legacy-projection.js +13 -15
  330. package/dist/storage/canonical-legacy-projection.js.map +1 -1
  331. package/dist/storage/legacy-projection.d.ts.map +1 -1
  332. package/dist/storage/legacy-projection.js +17 -9
  333. package/dist/storage/legacy-projection.js.map +1 -1
  334. package/dist/storage/legacy-scope-coverage-exceptions.d.ts +5 -0
  335. package/dist/storage/legacy-scope-coverage-exceptions.d.ts.map +1 -1
  336. package/dist/storage/legacy-scope-coverage-exceptions.js +4 -0
  337. package/dist/storage/legacy-scope-coverage-exceptions.js.map +1 -1
  338. package/dist/storage/legacy-scope-coverage.d.ts.map +1 -1
  339. package/dist/storage/legacy-scope-coverage.js +14 -1
  340. package/dist/storage/legacy-scope-coverage.js.map +1 -1
  341. package/dist/storage/legacy-scope-entity-keys.d.ts +14 -1
  342. package/dist/storage/legacy-scope-entity-keys.d.ts.map +1 -1
  343. package/dist/storage/legacy-scope-entity-keys.js +18 -1
  344. package/dist/storage/legacy-scope-entity-keys.js.map +1 -1
  345. package/dist/storage/legacy-scope-view.d.ts +57 -5
  346. package/dist/storage/legacy-scope-view.d.ts.map +1 -1
  347. package/dist/storage/legacy-scope-view.js +644 -318
  348. package/dist/storage/legacy-scope-view.js.map +1 -1
  349. package/dist/storage/pdpp-records/cursor.d.ts +47 -2
  350. package/dist/storage/pdpp-records/cursor.d.ts.map +1 -1
  351. package/dist/storage/pdpp-records/cursor.js +7 -0
  352. package/dist/storage/pdpp-records/cursor.js.map +1 -1
  353. package/dist/storage/pdpp-records/enforcement.d.ts +3 -16
  354. package/dist/storage/pdpp-records/enforcement.d.ts.map +1 -1
  355. package/dist/storage/pdpp-records/enforcement.js +40 -13
  356. package/dist/storage/pdpp-records/enforcement.js.map +1 -1
  357. package/dist/storage/pdpp-records/index.d.ts +2 -0
  358. package/dist/storage/pdpp-records/index.d.ts.map +1 -1
  359. package/dist/storage/pdpp-records/index.js +1 -0
  360. package/dist/storage/pdpp-records/index.js.map +1 -1
  361. package/dist/storage/pdpp-records/interface.d.ts +8 -1
  362. package/dist/storage/pdpp-records/interface.d.ts.map +1 -1
  363. package/dist/storage/pdpp-records/list-page.d.ts +35 -0
  364. package/dist/storage/pdpp-records/list-page.d.ts.map +1 -0
  365. package/dist/storage/pdpp-records/list-page.js +106 -0
  366. package/dist/storage/pdpp-records/list-page.js.map +1 -0
  367. package/dist/storage/pdpp-records/memory-store.d.ts.map +1 -1
  368. package/dist/storage/pdpp-records/memory-store.js +95 -99
  369. package/dist/storage/pdpp-records/memory-store.js.map +1 -1
  370. package/dist/storage/pdpp-records/portable.d.ts +3 -0
  371. package/dist/storage/pdpp-records/portable.d.ts.map +1 -1
  372. package/dist/storage/pdpp-records/portable.js +3 -0
  373. package/dist/storage/pdpp-records/portable.js.map +1 -1
  374. package/dist/storage/pdpp-records/replication-journal.d.ts +20 -0
  375. package/dist/storage/pdpp-records/replication-journal.d.ts.map +1 -0
  376. package/dist/storage/pdpp-records/replication-journal.js +2 -0
  377. package/dist/storage/pdpp-records/replication-journal.js.map +1 -0
  378. package/dist/storage/pdpp-records/stream-declaration.d.ts +0 -2
  379. package/dist/storage/pdpp-records/stream-declaration.d.ts.map +1 -1
  380. package/dist/storage/pdpp-records/stream-declaration.js +0 -9
  381. package/dist/storage/pdpp-records/stream-declaration.js.map +1 -1
  382. package/dist/storage/pdpp-records/sync-selection.d.ts +4 -0
  383. package/dist/storage/pdpp-records/sync-selection.d.ts.map +1 -0
  384. package/dist/storage/pdpp-records/sync-selection.js +37 -0
  385. package/dist/storage/pdpp-records/sync-selection.js.map +1 -0
  386. package/dist/storage/pdpp-records/types.d.ts +64 -0
  387. package/dist/storage/pdpp-records/types.d.ts.map +1 -1
  388. package/dist/storage/pdpp-records/types.js.map +1 -1
  389. package/dist/sync/data-point-feed.d.ts.map +1 -1
  390. package/dist/sync/data-point-feed.js +4 -1
  391. package/dist/sync/data-point-feed.js.map +1 -1
  392. package/dist/sync/delete-data-port.d.ts +15 -0
  393. package/dist/sync/delete-data-port.d.ts.map +1 -1
  394. package/dist/sync/delete-data-port.js +19 -7
  395. package/dist/sync/delete-data-port.js.map +1 -1
  396. package/dist/sync/index.d.ts +2 -1
  397. package/dist/sync/index.d.ts.map +1 -1
  398. package/dist/sync/index.js +2 -1
  399. package/dist/sync/index.js.map +1 -1
  400. package/dist/sync/pending-blob-deletions.d.ts +18 -2
  401. package/dist/sync/pending-blob-deletions.d.ts.map +1 -1
  402. package/dist/sync/pending-blob-deletions.js +69 -23
  403. package/dist/sync/pending-blob-deletions.js.map +1 -1
  404. package/dist/sync/scope-deletions.d.ts +2 -0
  405. package/dist/sync/scope-deletions.d.ts.map +1 -1
  406. package/dist/sync/scope-deletions.js +60 -19
  407. package/dist/sync/scope-deletions.js.map +1 -1
  408. package/dist/sync/workers/delete.d.ts.map +1 -1
  409. package/dist/sync/workers/delete.js +316 -142
  410. package/dist/sync/workers/delete.js.map +1 -1
  411. package/dist/sync/workers/download.d.ts.map +1 -1
  412. package/dist/sync/workers/download.js +53 -5
  413. package/dist/sync/workers/download.js.map +1 -1
  414. package/dist/sync/workers/upload.d.ts +24 -5
  415. package/dist/sync/workers/upload.d.ts.map +1 -1
  416. package/dist/sync/workers/upload.js +160 -275
  417. package/dist/sync/workers/upload.js.map +1 -1
  418. package/dist/test-utils/memory-storage.d.ts.map +1 -1
  419. package/dist/test-utils/memory-storage.js +17 -2
  420. package/dist/test-utils/memory-storage.js.map +1 -1
  421. package/package.json +24 -2
@@ -1,6 +1,7 @@
1
1
  import { compareLegacyCoverage, } from "./legacy-scope-coverage.js";
2
2
  import { LEGACY_SCOPE_ENTITY_KEYS } from "./legacy-scope-entity-keys.js";
3
- import { GITHUB_BROWSER_BINDINGS, LEGACY_SCOPE_BINDINGS, legacyBindingsForProfile, META_040_BINDINGS, OURA_BROWSER_BINDINGS, } from "../legacy-projection/index.js";
3
+ import { OURA_BROWSER_HISTORY_POLICY } from "../oura/history-policy.js";
4
+ import { ANTHROPIC_LIVE_BINDINGS, GITHUB_BROWSER_BINDINGS, LEGACY_SCOPE_BINDINGS, legacyBindingsForProfile, META_040_BINDINGS, OURA_BROWSER_BINDINGS, STRAVA_BROWSER_BINDINGS, } from "../legacy-projection/index.js";
4
5
  import { legacyConnectorKey, readCanonicalLegacyScope, CANONICAL_LEGACY_VIEW_CONTRACT, } from "./canonical-legacy-projection.js";
5
6
  /**
6
7
  * Decides, per legacy scope, whether the latest body comes from the
@@ -28,11 +29,60 @@ import { legacyConnectorKey, readCanonicalLegacyScope, CANONICAL_LEGACY_VIEW_CON
28
29
  /** Every declaration profile the pure module serves a scope from. */
29
30
  const BINDING_PROFILES = [
30
31
  LEGACY_SCOPE_BINDINGS,
32
+ ANTHROPIC_LIVE_BINDINGS,
31
33
  GITHUB_BROWSER_BINDINGS,
32
34
  META_040_BINDINGS,
33
35
  OURA_BROWSER_BINDINGS,
36
+ STRAVA_BROWSER_BINDINGS,
34
37
  ];
35
- const COVERAGE_RULE_VERSION = 5;
38
+ // Invalidate persisted witnesses created before completion-horizon fencing.
39
+ const COVERAGE_RULE_VERSION = 11;
40
+ const OURA_RUN_STREAMS = ["sleep", "readiness", "activity"];
41
+ /** Live Claude omits some account metadata. Preserve known fields from the
42
+ * same organization's saved head; never borrow entities or another identity. */
43
+ async function retainClaudeProfile(read, legacy) {
44
+ const current = read.envelope.data;
45
+ const previous = legacy.data;
46
+ if (current.source !== "live-session" ||
47
+ typeof current.organizationId !== "string" ||
48
+ current.organizationId !== previous.organizationId)
49
+ return;
50
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
51
+ if (!isObject(current.profile) || !isObject(previous.profile))
52
+ return;
53
+ const profile = { ...current.profile };
54
+ let retained = false;
55
+ for (const field of ["name", "plan"]) {
56
+ if (profile[field] === null &&
57
+ typeof previous.profile[field] === "string") {
58
+ profile[field] = previous.profile[field];
59
+ retained = true;
60
+ }
61
+ }
62
+ if (retained) {
63
+ read.envelope = { ...read.envelope, data: { ...current, profile } };
64
+ // The stored metadata contributes to the response even when live rows do not change.
65
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${read.revision}:profile:${JSON.stringify(profile)}`));
66
+ read.revision = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
67
+ }
68
+ }
69
+ /** An export-to-live transition never treats missing old entities as deletions. */
70
+ export async function coversClaudeTransition(current, previous) {
71
+ if (current.envelope.scope !== "claude.conversations" ||
72
+ previous.scope !== "claude.conversations" ||
73
+ typeof previous.data.organizationId !== "string" ||
74
+ !previous.data.organizationId ||
75
+ previous.data.organizationId !== current.envelope.data.organizationId)
76
+ return false;
77
+ await retainClaudeProfile(current, previous);
78
+ return compareLegacyCoverage(previous.data, current.envelope.data, {
79
+ scope: "claude.conversations",
80
+ entityKeys: LEGACY_SCOPE_ENTITY_KEYS["claude.conversations"],
81
+ canonicalNewer: Date.parse(current.envelope.collectedAt) >
82
+ Date.parse(previous.collectedAt),
83
+ allowProvenDeletion: false,
84
+ }).covered;
85
+ }
36
86
  function scopeDeclarationCursor(scope) {
37
87
  return JSON.stringify(BINDING_PROFILES.flatMap((profile) => [...profile.values()])
38
88
  .filter((binding) => binding.scope === scope)
@@ -44,11 +94,100 @@ function scopeDeclarationCursor(scope) {
44
94
  provenance: binding.provenance,
45
95
  })));
46
96
  }
97
+ /**
98
+ * The deletion history of a body, decided with the body. Callers never
99
+ * recompute it from later ownership state.
100
+ */
101
+ export function deletionHistoryOf(body) {
102
+ return body.kind === "file" ? body.deletion : "canonical";
103
+ }
47
104
  export function createLegacyScopeView(input) {
48
105
  const { authority, records } = input;
49
106
  const coveredProjection = new Map();
50
- function decide(scope) {
51
- const streamsByMethod = scopeMethods(scope);
107
+ /**
108
+ * The scope's methods, minus each opt-in method (`input.optInMethods`)
109
+ * while the legacy file is still the owner's latest body: an eligible
110
+ * legacy head and no takeover recorded for that method. Such a method never
111
+ * served this scope, so its deleted, reset or replaced connections are not
112
+ * ownership evidence: the file was the served body all along. A takeover
113
+ * another method earned (the PAT `oura` connection, say), or one that does
114
+ * not name its method, leaves it excluded. Once it has earned one, it
115
+ * serves, and deletion refuses, as for any other method.
116
+ */
117
+ function withoutOptedOutMethods(scope) {
118
+ const methods = scopeMethods(scope);
119
+ const head = authority.eligibleLegacyHead(scope);
120
+ if (head === null)
121
+ return methods;
122
+ const gated = new Set([...optedOutMethods(scope)].filter((method) => !earnedTakeover(scope, method)));
123
+ if (gated.size === 0)
124
+ return methods;
125
+ reportKeptLegacyFile(scope, head, gated);
126
+ return new Map([...methods].filter(([method]) => !gated.has(method)));
127
+ }
128
+ function optedOutMethods(scope) {
129
+ const optIn = input.optInMethods;
130
+ if (!optIn || optIn.size === 0)
131
+ return [];
132
+ return [...scopeMethods(scope).keys()].filter((method) => optIn.has(method));
133
+ }
134
+ function earnedTakeover(scope, method) {
135
+ return authority.hasMethodTakeover(scope, method);
136
+ }
137
+ /**
138
+ * Deletion safety asks the ungated question: do connection rows exist that
139
+ * a legacy-only deletion would let appear? Where the gate keeps the file
140
+ * over an opted-out method, only live rows of that method count: a deleted,
141
+ * reset or replaced connection erased its rows, and a connection that never
142
+ * held data has none.
143
+ */
144
+ function ownsScope(scope) {
145
+ if (decide(scope, false).kind === "file")
146
+ return false;
147
+ if (decide(scope).kind !== "file")
148
+ return true;
149
+ return authority
150
+ .listMethodConnections([...optedOutMethods(scope)])
151
+ .some((connection) => connection.deletedAt === null &&
152
+ authority.hasHeldData(connection.instance, null));
153
+ }
154
+ /** Decided with the body: connection rows serve the scope, or they do not. */
155
+ function deletionFor(decision) {
156
+ return decision.kind === "file" ? "file" : "canonical";
157
+ }
158
+ /**
159
+ * One line per (scope, legacy head, number of connections holding rows), so
160
+ * an operator can see a stalled collector keeping a fresh projection gated.
161
+ */
162
+ const reportedKept = new Set();
163
+ function reportKeptLegacyFile(scope, head, optedOut) {
164
+ if (!input.logger)
165
+ return;
166
+ const held = authority
167
+ .listMethodConnections([...optedOut])
168
+ .filter((connection) => connection.deletedAt === null &&
169
+ authority.hasHeldData(connection.instance, null)).length;
170
+ if (held === 0)
171
+ return;
172
+ const key = `${scope}:${head.id}:${held}`;
173
+ if (reportedKept.has(key))
174
+ return;
175
+ reportedKept.add(key);
176
+ input.logger.info({
177
+ scope,
178
+ legacyHeadCollectedAt: head.collectedAt,
179
+ heldConnections: held,
180
+ }, "Legacy file kept as the latest body; a method that opts out of takeover holds synced rows");
181
+ }
182
+ /**
183
+ * `legacyFirst` (the default) lets the legacy file stay authoritative over
184
+ * methods whose binding opts out of takeover. Deletion safety asks the
185
+ * ungated question: do any connection rows exist for this scope?
186
+ */
187
+ function decide(scope, legacyFirst = true) {
188
+ const streamsByMethod = legacyFirst
189
+ ? withoutOptedOutMethods(scope)
190
+ : scopeMethods(scope);
52
191
  if (streamsByMethod.size === 0) {
53
192
  return authority.hasTakeover(scope)
54
193
  ? refused({
@@ -124,149 +263,292 @@ export function createLegacyScopeView(input) {
124
263
  }
125
264
  function listingFor(scope) {
126
265
  return authority.readSnapshot(() => {
127
- if (authority.isReset(scope))
128
- return { kind: "file" };
129
266
  const decision = decide(scope);
130
- const legacyHead = authority.eligibleLegacyHead(scope);
131
- const hasTakeover = authority.hasTakeover(scope, decision.kind === "canonical" ? decision.selection.instance : undefined, decision.kind === "canonical" ? decision.generation : undefined);
132
- if (decision.kind === "refused" &&
133
- decision.refusal.status === "unavailable" &&
134
- decision.refusal.reason === "no_connection" &&
135
- canServeLegacyBeforeTakeover(scope)) {
136
- return { kind: "file" };
137
- }
138
- if (decision.kind !== "canonical")
139
- return { kind: decision.kind };
140
- const hasLegacyHead = legacyHead !== null;
141
- if (hasLegacyHead && input.readLegacyHead) {
142
- const verdict = authority
143
- .verdictFor(scope)
144
- .find((row) => row.instance === decision.selection.instance &&
145
- row.generation === decision.generation &&
146
- row.legacyVersionId === legacyHead.id);
147
- const coveredHead = authority.hasCoveredHead(scope, legacyHead);
148
- if (verdict?.verdict !== "covered") {
149
- return { kind: coveredHead ? "refused" : "file" };
150
- }
151
- if (!verdict.canonicalCursor.startsWith(`${authority.writeClock()}:${authority.arrivalRevision()}:${receiptCursor(decision, input.declarations)}:`)) {
152
- return { kind: "refused" };
153
- }
267
+ const listed = chooseListing(scope, decision);
268
+ return listed.kind === "file"
269
+ ? { kind: "file", deletion: deletionFor(decision) }
270
+ : listed;
271
+ });
272
+ }
273
+ function chooseListing(scope, decision) {
274
+ if (authority.isReset(scope))
275
+ return { kind: "file" };
276
+ const legacyHead = authority.eligibleLegacyHead(scope);
277
+ const hasTakeover = authority.hasTakeover(scope, decision.kind === "canonical" ? decision.selection.instance : undefined, decision.kind === "canonical" ? decision.generation : undefined);
278
+ if (decision.kind === "refused" &&
279
+ decision.refusal.status === "unavailable" &&
280
+ decision.refusal.reason === "no_connection" &&
281
+ canServeLegacyBeforeTakeover(scope)) {
282
+ return { kind: "file" };
283
+ }
284
+ if (decision.kind !== "canonical")
285
+ return { kind: decision.kind };
286
+ if (ouraHasRetainedEntityLoss(decision) &&
287
+ !ouraCompletedReplacement(decision))
288
+ return { kind: hasTakeover || legacyHead === null ? "refused" : "file" };
289
+ const hasLegacyHead = legacyHead !== null;
290
+ if (hasLegacyHead && !hasTakeover && !initialOuraRunComplete(decision)) {
291
+ return { kind: "file" };
292
+ }
293
+ const completedReplacement = legacyHead !== null &&
294
+ authority.hasCompletedReplacement?.(scope, legacyHead, decision.selection.instance, decision.generation, decision.selection.method, coverageCursor(scope, decision, authority.writeClock(), authority.arrivalRevision(), receiptCursor(decision, input.declarations))) === true;
295
+ if (hasLegacyHead && input.readLegacyHead && !completedReplacement) {
296
+ const verdict = authority
297
+ .verdictFor(scope)
298
+ .find((row) => row.instance === decision.selection.instance &&
299
+ row.generation === decision.generation &&
300
+ row.legacyVersionId === legacyHead.id);
301
+ const coveredHead = authority.hasCoveredHead(scope, legacyHead);
302
+ if (verdict?.verdict !== "covered") {
303
+ return { kind: coveredHead ? "refused" : "file" };
154
304
  }
155
- else if (hasLegacyHead && !hasTakeover)
156
- return { kind: "file" };
157
- if (!input.readLegacyHead &&
158
- hasLegacyHead &&
159
- hasTakeover &&
160
- missingCompletionReceipts(decision).length > 0) {
305
+ if (verdict.canonicalCursor !==
306
+ coverageCursor(scope, decision, authority.writeClock(), authority.arrivalRevision(), receiptCursor(decision, input.declarations))) {
161
307
  return { kind: "refused" };
162
308
  }
163
- const collectedAt = listedCollectedAt(decision.records, decision.selection.instance, decision.streams);
164
- return collectedAt === null
165
- ? { kind: "refused" }
166
- : { kind: "canonical", collectedAt };
309
+ }
310
+ else if (hasLegacyHead && !hasTakeover)
311
+ return { kind: "file" };
312
+ if (!input.readLegacyHead &&
313
+ hasLegacyHead &&
314
+ hasTakeover &&
315
+ missingCompletionReceipts(decision).length > 0) {
316
+ return { kind: "refused" };
317
+ }
318
+ const collectedAt = listedCollectedAt(decision.records, decision.selection.instance, decision.streams);
319
+ return collectedAt === null
320
+ ? { kind: "refused" }
321
+ : { kind: "canonical", collectedAt };
322
+ }
323
+ function ouraHasRetainedEntityLoss(decision) {
324
+ return (decision.selection.method === "oura-browser" &&
325
+ OURA_BROWSER_HISTORY_POLICY.preserveRetainedEntities &&
326
+ authority.hasRetainedTombstones(decision.selection.instance, decision.streams));
327
+ }
328
+ // Omitted IDs are intentional only when every Oura stream has current,
329
+ // coherent completion evidence. A tombstone alone proves no replacement.
330
+ function ouraCompletedReplacement(decision) {
331
+ if (!initialOuraRunComplete(decision))
332
+ return false;
333
+ const receipts = decision.records.listStreamCompletions(decision.selection.instance);
334
+ return OURA_RUN_STREAMS.every((stream) => {
335
+ const head = authority.eligibleLegacyHead(`oura.${stream}`);
336
+ if (!head)
337
+ return true;
338
+ const receipt = receipts.find((entry) => entry.stream === stream);
339
+ const collected = Date.parse(receipt?.sourceCollectedAt ?? "");
340
+ const previous = Date.parse(head.collectedAt);
341
+ return (Number.isFinite(collected) &&
342
+ Number.isFinite(previous) &&
343
+ collected > previous);
167
344
  });
168
345
  }
169
- return {
170
- async readLatest(scope) {
171
- if (authority.isReset(scope))
172
- return { kind: "file" };
173
- const initial = authority.readSnapshot(() => {
174
- const decision = decide(scope);
346
+ function coverageCursor(scope, decision, writeClock, arrivalRevision, receipts) {
347
+ return [
348
+ writeClock,
349
+ arrivalRevision,
350
+ receipts,
351
+ decision.generation,
352
+ CANONICAL_LEGACY_VIEW_CONTRACT,
353
+ scopeDeclarationCursor(scope),
354
+ COVERAGE_RULE_VERSION,
355
+ input.coverageThresholds?.maxPayloadRatio ?? 1.5,
356
+ input.coverageThresholds?.maxExtraBytes ?? 256 * 1024,
357
+ ].join(":");
358
+ }
359
+ /** One snapshot: the decision and the evidence the read is judged by. */
360
+ function readDecision(scope) {
361
+ return authority.readSnapshot(() => {
362
+ const decision = decide(scope);
363
+ return {
364
+ reset: authority.isReset(scope),
365
+ decision,
366
+ retainedEntityLoss: decision.kind === "canonical" && ouraHasRetainedEntityLoss(decision),
367
+ legacyHead: authority.eligibleLegacyHead(scope),
368
+ hasTakeover: authority.hasTakeover(scope, decision.kind === "canonical"
369
+ ? decision.selection.instance
370
+ : undefined, decision.kind === "canonical" ? decision.generation : undefined),
371
+ canServeLegacy: canServeLegacyBeforeTakeover(scope),
372
+ hasCompletion: decision.kind === "canonical" &&
373
+ missingCompletionReceipts(decision).length === 0,
374
+ initialOuraRunComplete: decision.kind !== "canonical" || initialOuraRunComplete(decision),
375
+ writeClock: authority.writeClock(),
376
+ arrivalRevision: authority.arrivalRevision(),
377
+ receiptCursor: decision.kind === "canonical"
378
+ ? receiptCursor(decision, input.declarations)
379
+ : "",
380
+ };
381
+ });
382
+ }
383
+ /** The body `decision` serves; the caller attaches its deletion history. */
384
+ async function chooseBody(scope, initial) {
385
+ if (initial.reset)
386
+ return { kind: "file" };
387
+ const { decision, legacyHead, hasTakeover, canServeLegacy } = initial;
388
+ if (decision.kind === "refused" &&
389
+ decision.refusal.status === "unavailable" &&
390
+ decision.refusal.reason === "no_connection" &&
391
+ canServeLegacy) {
392
+ return { kind: "file" };
393
+ }
394
+ if (decision.kind !== "canonical")
395
+ return decision;
396
+ if (initial.retainedEntityLoss && !ouraCompletedReplacement(decision)) {
397
+ return hasTakeover || legacyHead === null
398
+ ? {
399
+ kind: "refused",
400
+ refusal: {
401
+ status: "incomplete",
402
+ reason: "canonical_view_incomplete",
403
+ diagnostics: [],
404
+ },
405
+ }
406
+ : { kind: "file" };
407
+ }
408
+ if (legacyHead !== null &&
409
+ !hasTakeover &&
410
+ !initial.initialOuraRunComplete) {
411
+ return { kind: "file" };
412
+ }
413
+ const replacementEligible = initial.hasCompletion &&
414
+ typeof authority.hasCompletedReplacement === "function";
415
+ const canonicalCursor = legacyHead
416
+ ? coverageCursor(scope, decision, initial.writeClock, initial.arrivalRevision, initial.receiptCursor)
417
+ : "";
418
+ const projectionKey = legacyHead
419
+ ? JSON.stringify([
420
+ decision.selection.instance,
421
+ decision.generation,
422
+ legacyHead.id,
423
+ canonicalCursor,
424
+ ])
425
+ : "";
426
+ const projectionOwner = scope;
427
+ const cachedCoverage = legacyHead !== null && input.readLegacyHead
428
+ ? authority.cachedVerdict(scope, decision.selection.instance, decision.generation, legacyHead.id, canonicalCursor)
429
+ : null;
430
+ const cachedProjection = coveredProjection.get(projectionOwner);
431
+ if (cachedCoverage?.covered && cachedProjection?.key === projectionKey) {
432
+ const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
433
+ receiptCursor(decision, input.declarations) === initial.receiptCursor, decision.selection.method, {
434
+ instance: decision.selection.instance,
435
+ generation: decision.generation,
436
+ writeClock: initial.writeClock,
437
+ arrivalRevision: initial.arrivalRevision,
438
+ canonicalCursor,
439
+ coverage: cachedCoverage,
440
+ cached: true,
441
+ });
442
+ if (!confirmed) {
443
+ if (authority.isReset(scope))
444
+ return { kind: "file" };
175
445
  return {
176
- decision,
177
- legacyHead: authority.eligibleLegacyHead(scope),
178
- hasTakeover: authority.hasTakeover(scope, decision.kind === "canonical"
179
- ? decision.selection.instance
180
- : undefined, decision.kind === "canonical" ? decision.generation : undefined),
181
- canServeLegacy: canServeLegacyBeforeTakeover(scope),
182
- writeClock: authority.writeClock(),
183
- arrivalRevision: authority.arrivalRevision(),
184
- receiptCursor: decision.kind === "canonical"
185
- ? receiptCursor(decision, input.declarations)
186
- : "",
446
+ kind: "refused",
447
+ refusal: {
448
+ status: "unavailable",
449
+ reason: "store_inconsistent_read",
450
+ detail: "legacy head or canonical cursor changed during parity evaluation",
451
+ },
187
452
  };
188
- });
189
- const { decision, legacyHead, hasTakeover, canServeLegacy } = initial;
190
- if (decision.kind === "refused" &&
191
- decision.refusal.status === "unavailable" &&
192
- decision.refusal.reason === "no_connection" &&
193
- canServeLegacy) {
194
- return { kind: "file" };
195
453
  }
196
- if (decision.kind !== "canonical")
197
- return decision;
198
- const canonicalCursor = legacyHead
199
- ? [
200
- initial.writeClock,
201
- initial.arrivalRevision,
202
- initial.receiptCursor,
203
- decision.generation,
204
- CANONICAL_LEGACY_VIEW_CONTRACT,
205
- scopeDeclarationCursor(scope),
206
- COVERAGE_RULE_VERSION,
207
- input.coverageThresholds?.maxPayloadRatio ?? 1.5,
208
- input.coverageThresholds?.maxExtraBytes ?? 256 * 1024,
209
- ].join(":")
210
- : "";
211
- const projectionKey = legacyHead
212
- ? JSON.stringify([
213
- decision.selection.instance,
214
- decision.generation,
215
- legacyHead.id,
216
- canonicalCursor,
217
- ])
218
- : "";
219
- const projectionOwner = scope;
220
- const cachedCoverage = legacyHead !== null && input.readLegacyHead
221
- ? authority.cachedVerdict(scope, decision.selection.instance, decision.generation, legacyHead.id, canonicalCursor)
222
- : null;
223
- const cachedProjection = coveredProjection.get(projectionOwner);
224
- if (cachedCoverage?.covered && cachedProjection?.key === projectionKey) {
225
- const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
226
- receiptCursor(decision, input.declarations) ===
227
- initial.receiptCursor, {
228
- instance: decision.selection.instance,
229
- generation: decision.generation,
230
- writeClock: initial.writeClock,
231
- arrivalRevision: initial.arrivalRevision,
232
- canonicalCursor,
233
- coverage: cachedCoverage,
234
- cached: true,
235
- });
236
- if (!confirmed) {
237
- if (authority.isReset(scope))
238
- return { kind: "file" };
239
- return {
454
+ return {
455
+ kind: "canonical",
456
+ envelope: cachedProjection.envelope,
457
+ revision: cachedProjection.revision,
458
+ };
459
+ }
460
+ if (cachedCoverage && !cachedCoverage.covered && legacyHead) {
461
+ let legacy;
462
+ try {
463
+ legacy = await input.readLegacyHead(legacyHead);
464
+ }
465
+ catch {
466
+ return hasTakeover
467
+ ? {
240
468
  kind: "refused",
241
469
  refusal: {
242
470
  status: "unavailable",
243
471
  reason: "store_inconsistent_read",
244
- detail: "legacy head or canonical cursor changed during parity evaluation",
472
+ detail: "indexed legacy head could not be read",
245
473
  },
246
- };
247
- }
474
+ }
475
+ : { kind: "file" };
476
+ }
477
+ if (legacy.scope !== scope) {
478
+ return {
479
+ kind: "refused",
480
+ refusal: {
481
+ status: "unavailable",
482
+ reason: "store_inconsistent_read",
483
+ detail: "indexed legacy head has another scope",
484
+ },
485
+ };
486
+ }
487
+ const alreadyCovered = authority.hasCoveredHead(scope, legacyHead);
488
+ const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
489
+ receiptCursor(decision, input.declarations) === initial.receiptCursor, decision.selection.method, {
490
+ instance: decision.selection.instance,
491
+ generation: decision.generation,
492
+ writeClock: initial.writeClock,
493
+ arrivalRevision: initial.arrivalRevision,
494
+ canonicalCursor,
495
+ coverage: cachedCoverage,
496
+ cached: true,
497
+ });
498
+ if (!confirmed) {
499
+ if (authority.isReset(scope))
500
+ return { kind: "file" };
248
501
  return {
249
- kind: "canonical",
250
- envelope: cachedProjection.envelope,
251
- revision: cachedProjection.revision,
502
+ kind: "refused",
503
+ refusal: {
504
+ status: "unavailable",
505
+ reason: "store_inconsistent_read",
506
+ detail: "legacy head or canonical cursor changed during parity evaluation",
507
+ },
252
508
  };
253
509
  }
254
- if (cachedCoverage && !cachedCoverage.covered && legacyHead) {
510
+ return alreadyCovered
511
+ ? {
512
+ kind: "refused",
513
+ refusal: {
514
+ status: "incomplete",
515
+ reason: "canonical_view_incomplete",
516
+ diagnostics: [],
517
+ },
518
+ }
519
+ : { kind: "file" };
520
+ }
521
+ const read = await readCanonicalLegacyScope({
522
+ store: decision.records,
523
+ declarations: input.declarations,
524
+ scope,
525
+ selection: decision.selection,
526
+ profileBy: "method",
527
+ // Completion proves initial Oura admission. Once a takeover is earned,
528
+ // partial refreshes still face coverage checks without requiring receipts.
529
+ requireCompletionReceipts: legacyHead !== null &&
530
+ (replacementEligible ||
531
+ !input.readLegacyHead ||
532
+ (decision.selection.method === "oura-browser" && !hasTakeover)),
533
+ allowUnprovenEmptyStreams: legacyHead !== null && !!input.readLegacyHead,
534
+ includeStreamTimes: legacyHead !== null,
535
+ requireClaudeRawBlobs: legacyHead !== null && !!input.readLegacyHead,
536
+ // The rows must come from a state in which this is still the one
537
+ // connection: a second account appearing between the decision and
538
+ // the rows refuses instead of serving a body chosen while ambiguous.
539
+ confirmSelection: () => sameSelection(decide(scope), decision),
540
+ });
541
+ if (!read.ok) {
542
+ if (legacyHead !== null &&
543
+ input.readLegacyHead &&
544
+ isProjectionDataFailure(read.reason)) {
255
545
  let legacy;
256
546
  try {
257
547
  legacy = await input.readLegacyHead(legacyHead);
258
548
  }
259
549
  catch {
260
- return hasTakeover
261
- ? {
262
- kind: "refused",
263
- refusal: {
264
- status: "unavailable",
265
- reason: "store_inconsistent_read",
266
- detail: "indexed legacy head could not be read",
267
- },
268
- }
269
- : { kind: "file" };
550
+ const { ok: _ok, ...refusal } = read;
551
+ return { kind: "refused", refusal };
270
552
  }
271
553
  if (legacy.scope !== scope) {
272
554
  return {
@@ -278,17 +560,18 @@ export function createLegacyScopeView(input) {
278
560
  },
279
561
  };
280
562
  }
281
- const alreadyCovered = authority.hasCoveredHead(scope, legacyHead);
563
+ const coveredHead = authority.hasCoveredHead(scope, legacyHead);
564
+ const coverage = projectionUnavailableCoverage(legacy.data, read.reason === "missing_raw_blob" ? read.recordCount : 0);
282
565
  const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
283
566
  receiptCursor(decision, input.declarations) ===
284
- initial.receiptCursor, {
567
+ initial.receiptCursor, decision.selection.method, {
285
568
  instance: decision.selection.instance,
286
569
  generation: decision.generation,
287
570
  writeClock: initial.writeClock,
288
571
  arrivalRevision: initial.arrivalRevision,
289
- canonicalCursor,
290
- coverage: cachedCoverage,
291
- cached: true,
572
+ canonicalCursor: `${initial.writeClock}:${initial.arrivalRevision}:${initial.receiptCursor}:unavailable:${read.reason}:${COVERAGE_RULE_VERSION}`,
573
+ coverage,
574
+ cached: false,
292
575
  });
293
576
  if (!confirmed) {
294
577
  if (authority.isReset(scope))
@@ -302,228 +585,248 @@ export function createLegacyScopeView(input) {
302
585
  },
303
586
  };
304
587
  }
305
- return alreadyCovered
306
- ? {
307
- kind: "refused",
308
- refusal: {
309
- status: "incomplete",
310
- reason: "canonical_view_incomplete",
311
- diagnostics: [],
312
- },
313
- }
314
- : { kind: "file" };
588
+ if (coveredHead) {
589
+ const { ok: _ok, ...refusal } = read;
590
+ return { kind: "refused", refusal };
591
+ }
592
+ return { kind: "file" };
593
+ }
594
+ if (legacyHead !== null &&
595
+ !hasTakeover &&
596
+ (read.reason === "projection_rejected" ||
597
+ read.reason === "all_rows_rejected" ||
598
+ read.reason === "no_synced_data" ||
599
+ read.reason === "stream_completion_unknown" ||
600
+ read.reason === "blob_read_budget_exceeded" ||
601
+ read.reason === "blob_backed_record_invalid" ||
602
+ read.reason === "missing_raw_blob" ||
603
+ read.reason === "blob_backed_records_unsupported")) {
604
+ const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
605
+ if (stillLegacyOwned)
606
+ return { kind: "file" };
315
607
  }
316
- const read = await readCanonicalLegacyScope({
317
- store: decision.records,
318
- declarations: input.declarations,
319
- scope,
320
- selection: decision.selection,
321
- profileBy: "method",
322
- requireCompletionReceipts: legacyHead !== null && !input.readLegacyHead,
323
- allowUnprovenEmptyStreams: legacyHead !== null && !!input.readLegacyHead,
324
- includeStreamTimes: legacyHead !== null && !!input.readLegacyHead,
325
- requireClaudeRawBlobs: legacyHead !== null && !!input.readLegacyHead,
326
- // The rows must come from a state in which this is still the one
327
- // connection: a second account appearing between the decision and
328
- // the rows refuses instead of serving a body chosen while ambiguous.
329
- confirmSelection: () => sameSelection(decide(scope), decision),
608
+ const { ok: _ok, ...refusal } = read;
609
+ return { kind: "refused", refusal };
610
+ }
611
+ // Receipt presence cannot make a lossy/partial projection complete.
612
+ if (initial.retainedEntityLoss && !read.complete) {
613
+ if (legacyHead !== null && !hasTakeover) {
614
+ const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
615
+ if (stillLegacyOwned)
616
+ return { kind: "file" };
617
+ }
618
+ return {
619
+ kind: "refused",
620
+ refusal: {
621
+ status: "incomplete",
622
+ reason: "canonical_view_incomplete",
623
+ diagnostics: incompleteness(read),
624
+ },
625
+ };
626
+ }
627
+ // Whole-dataset replacement needs a fresh completion for every input.
628
+ // Equal/older horizons retain the existing parity path, not deletion authority.
629
+ if (legacyHead !== null &&
630
+ replacementEligible &&
631
+ read.complete &&
632
+ allStreamsProvenAfter(read, decision.streams, legacyHead.collectedAt)) {
633
+ const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
634
+ receiptCursor(decision, input.declarations) === initial.receiptCursor, decision.selection.method, {
635
+ instance: decision.selection.instance,
636
+ generation: decision.generation,
637
+ writeClock: initial.writeClock,
638
+ arrivalRevision: initial.arrivalRevision,
639
+ canonicalCursor,
640
+ completion: true,
330
641
  });
331
- if (!read.ok) {
332
- if (legacyHead !== null &&
333
- input.readLegacyHead &&
334
- isProjectionDataFailure(read.reason)) {
335
- let legacy;
336
- try {
337
- legacy = await input.readLegacyHead(legacyHead);
338
- }
339
- catch {
340
- const { ok: _ok, ...refusal } = read;
341
- return { kind: "refused", refusal };
342
- }
343
- if (legacy.scope !== scope) {
344
- return {
345
- kind: "refused",
346
- refusal: {
347
- status: "unavailable",
348
- reason: "store_inconsistent_read",
349
- detail: "indexed legacy head has another scope",
350
- },
351
- };
352
- }
353
- const coveredHead = authority.hasCoveredHead(scope, legacyHead);
354
- const coverage = projectionUnavailableCoverage(legacy.data, read.reason === "missing_raw_blob" ? read.recordCount : 0);
355
- const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
356
- receiptCursor(decision, input.declarations) ===
357
- initial.receiptCursor, {
358
- instance: decision.selection.instance,
359
- generation: decision.generation,
360
- writeClock: initial.writeClock,
361
- arrivalRevision: initial.arrivalRevision,
362
- canonicalCursor: `${initial.writeClock}:${initial.arrivalRevision}:${initial.receiptCursor}:unavailable:${read.reason}:${COVERAGE_RULE_VERSION}`,
363
- coverage,
364
- cached: false,
365
- });
366
- if (!confirmed) {
367
- if (authority.isReset(scope))
368
- return { kind: "file" };
369
- return {
370
- kind: "refused",
371
- refusal: {
372
- status: "unavailable",
373
- reason: "store_inconsistent_read",
374
- detail: "legacy head or canonical cursor changed during parity evaluation",
375
- },
376
- };
377
- }
378
- if (coveredHead) {
379
- const { ok: _ok, ...refusal } = read;
380
- return { kind: "refused", refusal };
381
- }
642
+ if (!confirmed) {
643
+ if (authority.isReset(scope))
382
644
  return { kind: "file" };
383
- }
384
- if (legacyHead !== null &&
385
- !hasTakeover &&
386
- (read.reason === "projection_rejected" ||
387
- read.reason === "all_rows_rejected" ||
388
- read.reason === "no_synced_data" ||
389
- read.reason === "stream_completion_unknown" ||
390
- read.reason === "blob_read_budget_exceeded" ||
391
- read.reason === "blob_backed_record_invalid" ||
392
- read.reason === "missing_raw_blob" ||
393
- read.reason === "blob_backed_records_unsupported")) {
394
- const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
395
- if (stillLegacyOwned)
396
- return { kind: "file" };
397
- }
398
- const { ok: _ok, ...refusal } = read;
399
- return { kind: "refused", refusal };
645
+ const canKeepFile = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope) &&
646
+ !authority.hasTakeover(scope, decision.selection.instance, decision.generation));
647
+ if (canKeepFile)
648
+ return { kind: "file" };
649
+ return {
650
+ kind: "refused",
651
+ refusal: {
652
+ status: "unavailable",
653
+ reason: "store_inconsistent_read",
654
+ detail: "legacy head or canonical snapshot changed during replacement",
655
+ },
656
+ };
400
657
  }
401
- if (legacyHead !== null && input.readLegacyHead) {
402
- let coverage = cachedCoverage;
403
- const cached = coverage !== null;
404
- if (!coverage) {
405
- let legacy;
406
- try {
407
- legacy = await input.readLegacyHead(legacyHead);
408
- }
409
- catch {
410
- return hasTakeover
411
- ? {
412
- kind: "refused",
413
- refusal: {
414
- status: "unavailable",
415
- reason: "store_inconsistent_read",
416
- detail: "indexed legacy head could not be read",
417
- },
418
- }
419
- : { kind: "file" };
420
- }
421
- if (legacy.scope !== scope) {
422
- return {
658
+ return {
659
+ kind: "canonical",
660
+ envelope: read.envelope,
661
+ revision: read.revision,
662
+ };
663
+ }
664
+ // Without the old body, only a newer complete import can prove replacement.
665
+ // Do not fall through to confirmation that records an unscoped takeover.
666
+ if (legacyHead !== null && !input.readLegacyHead) {
667
+ const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
668
+ if (stillLegacyOwned)
669
+ return { kind: "file" };
670
+ return {
671
+ kind: "refused",
672
+ refusal: {
673
+ status: "incomplete",
674
+ reason: "canonical_view_incomplete",
675
+ diagnostics: incompleteness(read),
676
+ },
677
+ };
678
+ }
679
+ if (legacyHead !== null && input.readLegacyHead) {
680
+ // Coverage compares against the old file, so it cannot see rows the
681
+ // source reports short of detail. Such a history cannot take over; once
682
+ // this file is covered, it keeps serving (no hard break).
683
+ if (read.diagnostics.partialRows.length > 0 &&
684
+ !authority.hasCoveredHead(scope, legacyHead)) {
685
+ return { kind: "file" };
686
+ }
687
+ let coverage = cachedCoverage;
688
+ const cached = coverage !== null;
689
+ if (!coverage || decision.selection.method === "anthropic-live") {
690
+ let legacy;
691
+ try {
692
+ legacy = await input.readLegacyHead(legacyHead);
693
+ }
694
+ catch {
695
+ return hasTakeover
696
+ ? {
423
697
  kind: "refused",
424
698
  refusal: {
425
699
  status: "unavailable",
426
700
  reason: "store_inconsistent_read",
427
- detail: "indexed legacy head has another scope",
701
+ detail: "indexed legacy head could not be read",
428
702
  },
429
- };
430
- }
431
- coverage = compareLegacyCoverage(legacy.data, read.envelope.data, {
432
- scope,
433
- entityKeys: LEGACY_SCOPE_ENTITY_KEYS[scope],
434
- projectionOmissions: read.projectionOmissions,
435
- canonicalNewer: allStreamsNewerThan(read, decision.streams, legacyHead.collectedAt),
436
- allowProvenDeletion: read.complete &&
437
- allStreamsProvenAfter(read, decision.streams, legacyHead.collectedAt),
438
- ...input.coverageThresholds,
439
- });
703
+ }
704
+ : { kind: "file" };
440
705
  }
441
- const alreadyCovered = authority.hasCoveredHead(scope, legacyHead);
442
- const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
443
- receiptCursor(decision, input.declarations) ===
444
- initial.receiptCursor, {
445
- instance: decision.selection.instance,
446
- generation: decision.generation,
447
- writeClock: initial.writeClock,
448
- arrivalRevision: initial.arrivalRevision,
449
- canonicalCursor,
450
- coverage,
451
- cached,
452
- });
453
- if (!confirmed) {
454
- if (authority.isReset(scope))
455
- return { kind: "file" };
706
+ if (legacy.scope !== scope) {
456
707
  return {
457
708
  kind: "refused",
458
709
  refusal: {
459
710
  status: "unavailable",
460
711
  reason: "store_inconsistent_read",
461
- detail: "legacy head or canonical cursor changed during parity evaluation",
712
+ detail: "indexed legacy head has another scope",
462
713
  },
463
714
  };
464
715
  }
465
- if (!coverage.covered) {
466
- // A newer file is an explicit, recorded rollback. A canonical
467
- // regression against the same file must still refuse after takeover.
468
- return alreadyCovered
469
- ? {
470
- kind: "refused",
471
- refusal: {
472
- status: "incomplete",
473
- reason: "canonical_view_incomplete",
474
- diagnostics: [],
475
- },
476
- }
477
- : { kind: "file" };
716
+ if (scope === "claude.conversations" &&
717
+ decision.selection.method === "anthropic-live") {
718
+ await retainClaudeProfile(read, legacy);
478
719
  }
479
- coveredProjection.set(projectionOwner, {
480
- key: projectionKey,
481
- envelope: read.envelope,
482
- revision: read.revision,
720
+ coverage = compareLegacyCoverage(legacy.data, read.envelope.data, {
721
+ scope,
722
+ entityKeys: LEGACY_SCOPE_ENTITY_KEYS[scope],
723
+ projectionOmissions: read.projectionOmissions,
724
+ canonicalNewer: allStreamsNewerThan(read, decision.streams, legacyHead.collectedAt),
725
+ // A partial row is present, only short of detail, so it does not
726
+ // weaken the receipts' proof that a missing entity was deleted. A
727
+ // dropped row would look deleted, so omissions still block it.
728
+ allowProvenDeletion: (decision.selection.method === "oura-browser"
729
+ ? OURA_BROWSER_HISTORY_POLICY.allowProvenDeletion
730
+ : true) &&
731
+ read.diagnostics.omissions.length === 0 &&
732
+ (read.diagnostics.unproven?.length ?? 0) === 0 &&
733
+ allStreamsProvenAfter(read, decision.streams, legacyHead.collectedAt),
734
+ ...input.coverageThresholds,
483
735
  });
484
- return {
485
- kind: "canonical",
486
- envelope: read.envelope,
487
- revision: read.revision,
488
- };
489
736
  }
490
- if (!read.complete) {
491
- if (legacyHead !== null && !hasTakeover) {
492
- const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
493
- if (stillLegacyOwned)
494
- return { kind: "file" };
495
- }
496
- return {
497
- kind: "refused",
498
- refusal: {
499
- status: "incomplete",
500
- reason: "canonical_view_incomplete",
501
- diagnostics: incompleteness(read),
502
- },
503
- };
504
- }
505
- const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision));
737
+ const alreadyCovered = authority.hasCoveredHead(scope, legacyHead);
738
+ const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision) &&
739
+ receiptCursor(decision, input.declarations) === initial.receiptCursor, decision.selection.method, {
740
+ instance: decision.selection.instance,
741
+ generation: decision.generation,
742
+ writeClock: initial.writeClock,
743
+ arrivalRevision: initial.arrivalRevision,
744
+ canonicalCursor,
745
+ coverage,
746
+ cached,
747
+ });
506
748
  if (!confirmed) {
507
- const current = authority.readSnapshot(() => ({
508
- canServeLegacy: canServeLegacyBeforeTakeover(scope),
509
- hasTakeover: authority.hasTakeover(scope, decision.selection.instance, decision.generation),
510
- }));
511
- if (current.canServeLegacy && !current.hasTakeover)
749
+ if (authority.isReset(scope))
512
750
  return { kind: "file" };
513
751
  return {
514
752
  kind: "refused",
515
753
  refusal: {
516
754
  status: "unavailable",
517
755
  reason: "store_inconsistent_read",
518
- detail: "legacy head or connection selection changed during canonical projection",
756
+ detail: "legacy head or canonical cursor changed during parity evaluation",
519
757
  },
520
758
  };
521
759
  }
760
+ if (!coverage.covered) {
761
+ // A newer file is an explicit, recorded rollback. A canonical
762
+ // regression against the same file must still refuse after takeover.
763
+ return alreadyCovered
764
+ ? {
765
+ kind: "refused",
766
+ refusal: {
767
+ status: "incomplete",
768
+ reason: "canonical_view_incomplete",
769
+ diagnostics: [],
770
+ },
771
+ }
772
+ : { kind: "file" };
773
+ }
774
+ coveredProjection.set(projectionOwner, {
775
+ key: projectionKey,
776
+ envelope: read.envelope,
777
+ revision: read.revision,
778
+ });
522
779
  return {
523
780
  kind: "canonical",
524
781
  envelope: read.envelope,
525
782
  revision: read.revision,
526
783
  };
784
+ }
785
+ if (!read.complete) {
786
+ if (legacyHead !== null && !hasTakeover) {
787
+ const stillLegacyOwned = authority.readSnapshot(() => canServeLegacyBeforeTakeover(scope));
788
+ if (stillLegacyOwned)
789
+ return { kind: "file" };
790
+ }
791
+ return {
792
+ kind: "refused",
793
+ refusal: {
794
+ status: "incomplete",
795
+ reason: "canonical_view_incomplete",
796
+ diagnostics: incompleteness(read),
797
+ },
798
+ };
799
+ }
800
+ const confirmed = authority.confirmRead(scope, legacyHead, () => sameSelection(decide(scope), decision), decision.selection.method);
801
+ if (!confirmed) {
802
+ const current = authority.readSnapshot(() => ({
803
+ canServeLegacy: canServeLegacyBeforeTakeover(scope),
804
+ hasTakeover: authority.hasTakeover(scope, decision.selection.instance, decision.generation),
805
+ }));
806
+ if (current.canServeLegacy && !current.hasTakeover)
807
+ return { kind: "file" };
808
+ return {
809
+ kind: "refused",
810
+ refusal: {
811
+ status: "unavailable",
812
+ reason: "store_inconsistent_read",
813
+ detail: "legacy head or connection selection changed during canonical projection",
814
+ },
815
+ };
816
+ }
817
+ return {
818
+ kind: "canonical",
819
+ envelope: read.envelope,
820
+ revision: read.revision,
821
+ };
822
+ }
823
+ return {
824
+ async readLatest(scope) {
825
+ const initial = readDecision(scope);
826
+ const body = await chooseBody(scope, initial);
827
+ return body.kind === "file"
828
+ ? { kind: "file", deletion: deletionFor(initial.decision) }
829
+ : body;
527
830
  },
528
831
  listingFor,
529
832
  listCanonicalScopes(scopePrefix) {
@@ -540,13 +843,34 @@ export function createLegacyScopeView(input) {
540
843
  return listed;
541
844
  },
542
845
  ownsScope(scope) {
543
- return authority.readSnapshot(() => decide(scope)).kind !== "file";
846
+ return authority.readSnapshot(() => ownsScope(scope));
544
847
  },
545
848
  verdictFor: (scope) => authority.verdictFor(scope),
546
849
  resetTakeover: (scope) => authority.resetTakeover(scope),
547
850
  isReset: (scope) => authority.isReset(scope),
548
851
  };
549
852
  }
853
+ /** Initial Oura admission is one completed three-stream run, never a mix of windows. */
854
+ function initialOuraRunComplete(decision) {
855
+ if (decision.selection.method !== "oura-browser")
856
+ return true;
857
+ const binding = decision.records.getInstanceBinding(decision.selection.instance);
858
+ const listings = decision.records.listStreams([decision.selection.instance]);
859
+ const receipts = decision.records.listStreamCompletions(decision.selection.instance);
860
+ let horizon;
861
+ return OURA_RUN_STREAMS.every((stream) => {
862
+ const count = listings.find((entry) => entry.stream === stream)?.recordCount ?? 0;
863
+ const receipt = receipts.find((entry) => entry.stream === stream &&
864
+ entry.method === binding.method &&
865
+ entry.generation === binding.generation &&
866
+ entry.liveCount === count);
867
+ const time = Date.parse(receipt?.sourceCollectedAt ?? "");
868
+ if (!Number.isFinite(time))
869
+ return false;
870
+ horizon ??= time;
871
+ return time === horizon;
872
+ });
873
+ }
550
874
  function missingCompletionReceipts(decision) {
551
875
  const binding = decision.records.getInstanceBinding(decision.selection.instance);
552
876
  const listings = decision.records.listStreams([decision.selection.instance]);
@@ -575,7 +899,9 @@ function isProjectionDataFailure(reason) {
575
899
  function receiptCursor(decision, declarations) {
576
900
  const receipts = decision.records
577
901
  .listStreamCompletions(decision.selection.instance)
578
- .filter((receipt) => decision.streams.includes(receipt.stream))
902
+ .filter((receipt) => (decision.selection.method === "oura-browser"
903
+ ? OURA_RUN_STREAMS
904
+ : decision.streams).includes(receipt.stream))
579
905
  .map((receipt) => [
580
906
  receipt.stream,
581
907
  receipt.method,