@opendatalabs/personal-server-ts-core 1.34.2 → 1.35.1
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.
- package/dist/additions/durable.d.ts +32 -2
- package/dist/additions/durable.d.ts.map +1 -1
- package/dist/additions/durable.js +237 -88
- package/dist/additions/durable.js.map +1 -1
- package/dist/additions/version-inventory.d.ts +4 -0
- package/dist/additions/version-inventory.d.ts.map +1 -1
- package/dist/additions/version-inventory.js +11 -1
- package/dist/additions/version-inventory.js.map +1 -1
- package/dist/api/index.d.ts +18 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +172 -4
- package/dist/api/index.js.map +1 -1
- package/dist/api/oura-collection-requirements.d.ts +16 -0
- package/dist/api/oura-collection-requirements.d.ts.map +1 -0
- package/dist/api/oura-collection-requirements.js +70 -0
- package/dist/api/oura-collection-requirements.js.map +1 -0
- package/dist/api/scope-upload-batches.d.ts +18 -0
- package/dist/api/scope-upload-batches.d.ts.map +1 -0
- package/dist/api/scope-upload-batches.js +71 -0
- package/dist/api/scope-upload-batches.js.map +1 -0
- package/dist/api/scope-uploads.d.ts +112 -0
- package/dist/api/scope-uploads.d.ts.map +1 -0
- package/dist/api/scope-uploads.js +369 -0
- package/dist/api/scope-uploads.js.map +1 -0
- package/dist/client.d.ts +70 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/contracts/data.d.ts +8 -0
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +31 -1
- package/dist/contracts/data.js.map +1 -1
- package/dist/contracts/grants.d.ts.map +1 -1
- package/dist/contracts/grants.js +3 -0
- package/dist/contracts/grants.js.map +1 -1
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/oura-collection-requirements.d.ts +17 -0
- package/dist/contracts/oura-collection-requirements.d.ts.map +1 -0
- package/dist/contracts/oura-collection-requirements.js +49 -0
- package/dist/contracts/oura-collection-requirements.js.map +1 -0
- package/dist/legacy-projection/bindings.d.ts +1 -7
- package/dist/legacy-projection/bindings.d.ts.map +1 -1
- package/dist/legacy-projection/bindings.js +162 -28
- package/dist/legacy-projection/bindings.js.map +1 -1
- package/dist/legacy-projection/index.d.ts +6 -5
- package/dist/legacy-projection/index.d.ts.map +1 -1
- package/dist/legacy-projection/index.js +9 -21
- package/dist/legacy-projection/index.js.map +1 -1
- package/dist/legacy-projection/oura-browser-bindings.d.ts +2 -0
- package/dist/legacy-projection/oura-browser-bindings.d.ts.map +1 -1
- package/dist/legacy-projection/oura-browser-bindings.js +2 -0
- package/dist/legacy-projection/oura-browser-bindings.js.map +1 -1
- package/dist/legacy-projection/strava-browser-bindings.d.ts +3 -0
- package/dist/legacy-projection/strava-browser-bindings.d.ts.map +1 -0
- package/dist/legacy-projection/strava-browser-bindings.js +32 -0
- package/dist/legacy-projection/strava-browser-bindings.js.map +1 -0
- package/dist/legacy-projection/types.d.ts +44 -12
- package/dist/legacy-projection/types.d.ts.map +1 -1
- package/dist/mcp/connection-api.d.ts +11 -1
- package/dist/mcp/connection-api.d.ts.map +1 -1
- package/dist/mcp/connection-api.js +94 -9
- package/dist/mcp/connection-api.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +19 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/store.d.ts +10 -1
- package/dist/mcp/store.d.ts.map +1 -1
- package/dist/mcp/store.js +51 -0
- package/dist/mcp/store.js.map +1 -1
- package/dist/mcp/tools.d.ts +52 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +395 -11
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +36 -2
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js +12 -0
- package/dist/mcp/types.js.map +1 -1
- package/dist/mcp/write-client.d.ts +81 -0
- package/dist/mcp/write-client.d.ts.map +1 -0
- package/dist/mcp/write-client.js +140 -0
- package/dist/mcp/write-client.js.map +1 -0
- package/dist/oura/history-policy.d.ts +12 -0
- package/dist/oura/history-policy.d.ts.map +1 -0
- package/dist/oura/history-policy.js +54 -0
- package/dist/oura/history-policy.js.map +1 -0
- package/dist/oura/index.d.ts +51 -0
- package/dist/oura/index.d.ts.map +1 -0
- package/dist/oura/index.js +315 -0
- package/dist/oura/index.js.map +1 -0
- package/dist/pdpp/approval.d.ts +1 -1
- package/dist/pdpp/approval.d.ts.map +1 -1
- package/dist/pdpp/approval.js +29 -10
- package/dist/pdpp/approval.js.map +1 -1
- package/dist/pdpp/declaration-limits.d.ts +7 -0
- package/dist/pdpp/declaration-limits.d.ts.map +1 -0
- package/dist/pdpp/declaration-limits.js +7 -0
- package/dist/pdpp/declaration-limits.js.map +1 -0
- package/dist/pdpp/declaration.d.ts +2 -1
- package/dist/pdpp/declaration.d.ts.map +1 -1
- package/dist/pdpp/declaration.js +2 -1
- package/dist/pdpp/declaration.js.map +1 -1
- package/dist/pdpp/index.d.ts +3 -2
- package/dist/pdpp/index.d.ts.map +1 -1
- package/dist/pdpp/index.js +3 -2
- package/dist/pdpp/index.js.map +1 -1
- package/dist/pdpp/issuance.d.ts.map +1 -1
- package/dist/pdpp/issuance.js +6 -2
- package/dist/pdpp/issuance.js.map +1 -1
- package/dist/pdpp/resolve.d.ts +12 -1
- package/dist/pdpp/resolve.d.ts.map +1 -1
- package/dist/pdpp/resolve.js +156 -58
- package/dist/pdpp/resolve.js.map +1 -1
- package/dist/pdpp/selection.d.ts +30 -1
- package/dist/pdpp/selection.d.ts.map +1 -1
- package/dist/pdpp/selection.js +77 -26
- package/dist/pdpp/selection.js.map +1 -1
- package/dist/pdpp/time.d.ts +39 -0
- package/dist/pdpp/time.d.ts.map +1 -0
- package/dist/pdpp/time.js +173 -0
- package/dist/pdpp/time.js.map +1 -0
- package/dist/pdpp/tokens.d.ts.map +1 -1
- package/dist/pdpp/tokens.js +4 -2
- package/dist/pdpp/tokens.js.map +1 -1
- package/dist/pdpp/types.d.ts +18 -2
- package/dist/pdpp/types.d.ts.map +1 -1
- package/dist/pdpp/types.js +7 -0
- package/dist/pdpp/types.js.map +1 -1
- package/dist/policy/reserved-scopes.d.ts +7 -0
- package/dist/policy/reserved-scopes.d.ts.map +1 -1
- package/dist/policy/reserved-scopes.js +12 -1
- package/dist/policy/reserved-scopes.js.map +1 -1
- package/dist/ports/index.d.ts +85 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/pdpp-auth.d.ts +17 -27
- package/dist/ports/pdpp-auth.d.ts.map +1 -1
- package/dist/ports/pdpp-auth.js +14 -28
- package/dist/ports/pdpp-auth.js.map +1 -1
- package/dist/publication/publish.d.ts +96 -0
- package/dist/publication/publish.d.ts.map +1 -0
- package/dist/publication/publish.js +261 -0
- package/dist/publication/publish.js.map +1 -0
- package/dist/replication/completion.d.ts +20 -0
- package/dist/replication/completion.d.ts.map +1 -0
- package/dist/replication/completion.js +73 -0
- package/dist/replication/completion.js.map +1 -0
- package/dist/replication/coordinator-state.d.ts +29 -0
- package/dist/replication/coordinator-state.d.ts.map +1 -0
- package/dist/replication/coordinator-state.js +88 -0
- package/dist/replication/coordinator-state.js.map +1 -0
- package/dist/replication/coordinator-state.test-utils.d.ts +32 -0
- package/dist/replication/coordinator-state.test-utils.d.ts.map +1 -0
- package/dist/replication/coordinator-state.test-utils.js +113 -0
- package/dist/replication/coordinator-state.test-utils.js.map +1 -0
- package/dist/replication/coordinator.d.ts +264 -0
- package/dist/replication/coordinator.d.ts.map +1 -0
- package/dist/replication/coordinator.js +499 -0
- package/dist/replication/coordinator.js.map +1 -0
- package/dist/replication/copies.d.ts +126 -0
- package/dist/replication/copies.d.ts.map +1 -0
- package/dist/replication/copies.js +437 -0
- package/dist/replication/copies.js.map +1 -0
- package/dist/replication/copy-promotion.d.ts +132 -0
- package/dist/replication/copy-promotion.d.ts.map +1 -0
- package/dist/replication/copy-promotion.js +126 -0
- package/dist/replication/copy-promotion.js.map +1 -0
- package/dist/replication/door.d.ts +33 -0
- package/dist/replication/door.d.ts.map +1 -0
- package/dist/replication/door.js +10 -0
- package/dist/replication/door.js.map +1 -0
- package/dist/replication/dpv2.d.ts +141 -0
- package/dist/replication/dpv2.d.ts.map +1 -0
- package/dist/replication/dpv2.js +320 -0
- package/dist/replication/dpv2.js.map +1 -0
- package/dist/replication/effects.d.ts +68 -0
- package/dist/replication/effects.d.ts.map +1 -0
- package/dist/replication/effects.js +89 -0
- package/dist/replication/effects.js.map +1 -0
- package/dist/replication/engine.d.ts +378 -0
- package/dist/replication/engine.d.ts.map +1 -0
- package/dist/replication/engine.js +3197 -0
- package/dist/replication/engine.js.map +1 -0
- package/dist/replication/envelope.d.ts +481 -0
- package/dist/replication/envelope.d.ts.map +1 -0
- package/dist/replication/envelope.js +192 -0
- package/dist/replication/envelope.js.map +1 -0
- package/dist/replication/evidence.d.ts +118 -0
- package/dist/replication/evidence.d.ts.map +1 -0
- package/dist/replication/evidence.js +198 -0
- package/dist/replication/evidence.js.map +1 -0
- package/dist/replication/finality.d.ts +16 -0
- package/dist/replication/finality.d.ts.map +1 -0
- package/dist/replication/finality.js +11 -0
- package/dist/replication/finality.js.map +1 -0
- package/dist/replication/fixtures.d.ts +16 -0
- package/dist/replication/fixtures.d.ts.map +1 -0
- package/dist/replication/fixtures.js +47 -0
- package/dist/replication/fixtures.js.map +1 -0
- package/dist/replication/host-config.d.ts +52 -0
- package/dist/replication/host-config.d.ts.map +1 -0
- package/dist/replication/host-config.js +111 -0
- package/dist/replication/host-config.js.map +1 -0
- package/dist/replication/host-driver.d.ts +95 -0
- package/dist/replication/host-driver.d.ts.map +1 -0
- package/dist/replication/host-driver.js +202 -0
- package/dist/replication/host-driver.js.map +1 -0
- package/dist/replication/host-ports.d.ts +48 -0
- package/dist/replication/host-ports.d.ts.map +1 -0
- package/dist/replication/host-ports.js +69 -0
- package/dist/replication/host-ports.js.map +1 -0
- package/dist/replication/identity.d.ts +13 -0
- package/dist/replication/identity.d.ts.map +1 -0
- package/dist/replication/identity.js +22 -0
- package/dist/replication/identity.js.map +1 -0
- package/dist/replication/limits.d.ts +124 -0
- package/dist/replication/limits.d.ts.map +1 -0
- package/dist/replication/limits.js +182 -0
- package/dist/replication/limits.js.map +1 -0
- package/dist/replication/merge-plan.d.ts +92 -0
- package/dist/replication/merge-plan.d.ts.map +1 -0
- package/dist/replication/merge-plan.js +229 -0
- package/dist/replication/merge-plan.js.map +1 -0
- package/dist/replication/namespace.d.ts +4 -0
- package/dist/replication/namespace.d.ts.map +1 -0
- package/dist/replication/namespace.js +7 -0
- package/dist/replication/namespace.js.map +1 -0
- package/dist/replication/outcome.d.ts +178 -0
- package/dist/replication/outcome.d.ts.map +1 -0
- package/dist/replication/outcome.js +83 -0
- package/dist/replication/outcome.js.map +1 -0
- package/dist/replication/payload.d.ts +39 -0
- package/dist/replication/payload.d.ts.map +1 -0
- package/dist/replication/payload.js +137 -0
- package/dist/replication/payload.js.map +1 -0
- package/dist/replication/preparation.d.ts +42 -0
- package/dist/replication/preparation.d.ts.map +1 -0
- package/dist/replication/preparation.js +118 -0
- package/dist/replication/preparation.js.map +1 -0
- package/dist/replication/rebaseline.d.ts +33 -0
- package/dist/replication/rebaseline.d.ts.map +1 -0
- package/dist/replication/rebaseline.js +40 -0
- package/dist/replication/rebaseline.js.map +1 -0
- package/dist/replication/rebinding.d.ts +43 -0
- package/dist/replication/rebinding.d.ts.map +1 -0
- package/dist/replication/rebinding.js +52 -0
- package/dist/replication/rebinding.js.map +1 -0
- package/dist/replication/receive.d.ts +57 -0
- package/dist/replication/receive.d.ts.map +1 -0
- package/dist/replication/receive.js +114 -0
- package/dist/replication/receive.js.map +1 -0
- package/dist/replication/record-reducer.d.ts +16 -0
- package/dist/replication/record-reducer.d.ts.map +1 -0
- package/dist/replication/record-reducer.js +52 -0
- package/dist/replication/record-reducer.js.map +1 -0
- package/dist/replication/revision.d.ts +24 -0
- package/dist/replication/revision.d.ts.map +1 -0
- package/dist/replication/revision.js +35 -0
- package/dist/replication/revision.js.map +1 -0
- package/dist/replication/roster.d.ts +53 -0
- package/dist/replication/roster.d.ts.map +1 -0
- package/dist/replication/roster.js +115 -0
- package/dist/replication/roster.js.map +1 -0
- package/dist/replication/schedule.d.ts +73 -0
- package/dist/replication/schedule.d.ts.map +1 -0
- package/dist/replication/schedule.js +182 -0
- package/dist/replication/schedule.js.map +1 -0
- package/dist/replication/stream.d.ts +73 -0
- package/dist/replication/stream.d.ts.map +1 -0
- package/dist/replication/stream.js +542 -0
- package/dist/replication/stream.js.map +1 -0
- package/dist/replication/transport.d.ts +236 -0
- package/dist/replication/transport.d.ts.map +1 -0
- package/dist/replication/transport.js +440 -0
- package/dist/replication/transport.js.map +1 -0
- package/dist/replication/visibility-engine.d.ts +337 -0
- package/dist/replication/visibility-engine.d.ts.map +1 -0
- package/dist/replication/visibility-engine.js +602 -0
- package/dist/replication/visibility-engine.js.map +1 -0
- package/dist/replication/visibility-policy.d.ts +26 -0
- package/dist/replication/visibility-policy.d.ts.map +1 -0
- package/dist/replication/visibility-policy.js +33 -0
- package/dist/replication/visibility-policy.js.map +1 -0
- package/dist/replication/writer-identity-store.d.ts +100 -0
- package/dist/replication/writer-identity-store.d.ts.map +1 -0
- package/dist/replication/writer-identity-store.js +218 -0
- package/dist/replication/writer-identity-store.js.map +1 -0
- package/dist/replication/writer-identity.d.ts +252 -0
- package/dist/replication/writer-identity.d.ts.map +1 -0
- package/dist/replication/writer-identity.js +281 -0
- package/dist/replication/writer-identity.js.map +1 -0
- package/dist/replication/writer-identity.test-utils.d.ts +24 -0
- package/dist/replication/writer-identity.test-utils.d.ts.map +1 -0
- package/dist/replication/writer-identity.test-utils.js +86 -0
- package/dist/replication/writer-identity.test-utils.js.map +1 -0
- package/dist/schemas/server-config.d.ts +13 -0
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +24 -0
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/storage/adapters/index.d.ts +3 -1
- package/dist/storage/adapters/index.d.ts.map +1 -1
- package/dist/storage/adapters/index.js +2 -0
- package/dist/storage/adapters/index.js.map +1 -1
- package/dist/storage/adapters/interface.d.ts +18 -1
- package/dist/storage/adapters/interface.d.ts.map +1 -1
- package/dist/storage/adapters/interface.js +13 -1
- package/dist/storage/adapters/interface.js.map +1 -1
- package/dist/storage/adapters/sdk.d.ts +2 -1
- package/dist/storage/adapters/sdk.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.js +21 -3
- package/dist/storage/adapters/sdk.js.map +1 -1
- package/dist/storage/adapters/vana-audience.d.ts +17 -5
- package/dist/storage/adapters/vana-audience.d.ts.map +1 -1
- package/dist/storage/adapters/vana-audience.js +24 -9
- package/dist/storage/adapters/vana-audience.js.map +1 -1
- package/dist/storage/adapters/vana.d.ts +2 -2
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +30 -22
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/canonical-legacy-projection.d.ts +1 -1
- package/dist/storage/canonical-legacy-projection.d.ts.map +1 -1
- package/dist/storage/canonical-legacy-projection.js +13 -15
- package/dist/storage/canonical-legacy-projection.js.map +1 -1
- package/dist/storage/legacy-projection.d.ts.map +1 -1
- package/dist/storage/legacy-projection.js +16 -1
- package/dist/storage/legacy-projection.js.map +1 -1
- package/dist/storage/legacy-scope-coverage-exceptions.d.ts +5 -0
- package/dist/storage/legacy-scope-coverage-exceptions.d.ts.map +1 -1
- package/dist/storage/legacy-scope-coverage-exceptions.js +4 -0
- package/dist/storage/legacy-scope-coverage-exceptions.js.map +1 -1
- package/dist/storage/legacy-scope-coverage.d.ts.map +1 -1
- package/dist/storage/legacy-scope-coverage.js +14 -1
- package/dist/storage/legacy-scope-coverage.js.map +1 -1
- package/dist/storage/legacy-scope-entity-keys.d.ts +14 -1
- package/dist/storage/legacy-scope-entity-keys.d.ts.map +1 -1
- package/dist/storage/legacy-scope-entity-keys.js +18 -1
- package/dist/storage/legacy-scope-entity-keys.js.map +1 -1
- package/dist/storage/legacy-scope-view.d.ts +57 -5
- package/dist/storage/legacy-scope-view.d.ts.map +1 -1
- package/dist/storage/legacy-scope-view.js +644 -318
- package/dist/storage/legacy-scope-view.js.map +1 -1
- package/dist/storage/pdpp-records/cursor.d.ts +47 -2
- package/dist/storage/pdpp-records/cursor.d.ts.map +1 -1
- package/dist/storage/pdpp-records/cursor.js +7 -0
- package/dist/storage/pdpp-records/cursor.js.map +1 -1
- package/dist/storage/pdpp-records/enforcement.d.ts +3 -16
- package/dist/storage/pdpp-records/enforcement.d.ts.map +1 -1
- package/dist/storage/pdpp-records/enforcement.js +40 -13
- package/dist/storage/pdpp-records/enforcement.js.map +1 -1
- package/dist/storage/pdpp-records/index.d.ts +2 -0
- package/dist/storage/pdpp-records/index.d.ts.map +1 -1
- package/dist/storage/pdpp-records/index.js +1 -0
- package/dist/storage/pdpp-records/index.js.map +1 -1
- package/dist/storage/pdpp-records/interface.d.ts +8 -1
- package/dist/storage/pdpp-records/interface.d.ts.map +1 -1
- package/dist/storage/pdpp-records/list-page.d.ts +35 -0
- package/dist/storage/pdpp-records/list-page.d.ts.map +1 -0
- package/dist/storage/pdpp-records/list-page.js +106 -0
- package/dist/storage/pdpp-records/list-page.js.map +1 -0
- package/dist/storage/pdpp-records/memory-store.d.ts.map +1 -1
- package/dist/storage/pdpp-records/memory-store.js +95 -99
- package/dist/storage/pdpp-records/memory-store.js.map +1 -1
- package/dist/storage/pdpp-records/portable.d.ts +3 -0
- package/dist/storage/pdpp-records/portable.d.ts.map +1 -1
- package/dist/storage/pdpp-records/portable.js +3 -0
- package/dist/storage/pdpp-records/portable.js.map +1 -1
- package/dist/storage/pdpp-records/replication-journal.d.ts +20 -0
- package/dist/storage/pdpp-records/replication-journal.d.ts.map +1 -0
- package/dist/storage/pdpp-records/replication-journal.js +2 -0
- package/dist/storage/pdpp-records/replication-journal.js.map +1 -0
- package/dist/storage/pdpp-records/stream-declaration.d.ts +0 -2
- package/dist/storage/pdpp-records/stream-declaration.d.ts.map +1 -1
- package/dist/storage/pdpp-records/stream-declaration.js +0 -9
- package/dist/storage/pdpp-records/stream-declaration.js.map +1 -1
- package/dist/storage/pdpp-records/sync-selection.d.ts +4 -0
- package/dist/storage/pdpp-records/sync-selection.d.ts.map +1 -0
- package/dist/storage/pdpp-records/sync-selection.js +37 -0
- package/dist/storage/pdpp-records/sync-selection.js.map +1 -0
- package/dist/storage/pdpp-records/types.d.ts +64 -0
- package/dist/storage/pdpp-records/types.d.ts.map +1 -1
- package/dist/storage/pdpp-records/types.js.map +1 -1
- package/dist/sync/delete-data-port.d.ts +15 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -1
- package/dist/sync/delete-data-port.js +19 -7
- package/dist/sync/delete-data-port.js.map +1 -1
- package/dist/sync/index.d.ts +2 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/pending-blob-deletions.d.ts +18 -2
- package/dist/sync/pending-blob-deletions.d.ts.map +1 -1
- package/dist/sync/pending-blob-deletions.js +69 -23
- package/dist/sync/pending-blob-deletions.js.map +1 -1
- package/dist/sync/workers/delete.d.ts.map +1 -1
- package/dist/sync/workers/delete.js +332 -142
- package/dist/sync/workers/delete.js.map +1 -1
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +53 -5
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +24 -5
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +160 -275
- package/dist/sync/workers/upload.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts.map +1 -1
- package/dist/test-utils/memory-storage.js +17 -2
- package/dist/test-utils/memory-storage.js.map +1 -1
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
:
|
|
181
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
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: "
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
291
|
-
cached:
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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 (!
|
|
332
|
-
if (
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
712
|
+
detail: "indexed legacy head has another scope",
|
|
462
713
|
},
|
|
463
714
|
};
|
|
464
715
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
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
|
|
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(() =>
|
|
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.
|
|
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,
|