@murphai/murph 1.3.1 → 1.3.2
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/CHANGELOG.md +808 -0
- package/config.schema.json +91 -40
- package/dist/bin.js +6 -4
- package/dist/bin.js.map +1 -1
- package/dist/cli-entry.d.ts +8 -1
- package/dist/cli-entry.d.ts.map +1 -1
- package/dist/cli-entry.js +79 -3
- package/dist/cli-entry.js.map +1 -1
- package/dist/commands/audit-command-helpers.d.ts.map +1 -1
- package/dist/commands/audit-command-helpers.js +7 -5
- package/dist/commands/audit-command-helpers.js.map +1 -1
- package/dist/commands/batch.d.ts +5 -5
- package/dist/commands/batch.d.ts.map +1 -1
- package/dist/commands/batch.js +2 -21
- package/dist/commands/batch.js.map +1 -1
- package/dist/commands/commons.d.ts.map +1 -1
- package/dist/commands/commons.js +2 -4
- package/dist/commands/commons.js.map +1 -1
- package/dist/commands/exercise.d.ts.map +1 -1
- package/dist/commands/exercise.js +16 -3
- package/dist/commands/exercise.js.map +1 -1
- package/dist/commands/experiment-progress-card-image.d.ts.map +1 -1
- package/dist/commands/experiment-progress-card-image.js +63 -12
- package/dist/commands/experiment-progress-card-image.js.map +1 -1
- package/dist/commands/experiment.d.ts.map +1 -1
- package/dist/commands/experiment.js +45 -5
- package/dist/commands/experiment.js.map +1 -1
- package/dist/commands/export-intake-read-helpers.d.ts.map +1 -1
- package/dist/commands/export-intake-read-helpers.js +2 -3
- package/dist/commands/export-intake-read-helpers.js.map +1 -1
- package/dist/commands/food.d.ts.map +1 -1
- package/dist/commands/food.js +1 -7
- package/dist/commands/food.js.map +1 -1
- package/dist/commands/habitat.d.ts.map +1 -1
- package/dist/commands/habitat.js +120 -9
- package/dist/commands/habitat.js.map +1 -1
- package/dist/commands/health-blood-test-save.d.ts.map +1 -1
- package/dist/commands/health-blood-test-save.js +42 -8
- package/dist/commands/health-blood-test-save.js.map +1 -1
- package/dist/commands/meal.d.ts.map +1 -1
- package/dist/commands/meal.js +4 -1
- package/dist/commands/meal.js.map +1 -1
- package/dist/commands/measurement.d.ts.map +1 -1
- package/dist/commands/measurement.js +3 -0
- package/dist/commands/measurement.js.map +1 -1
- package/dist/commands/murph-age.d.ts +9 -1
- package/dist/commands/murph-age.d.ts.map +1 -1
- package/dist/commands/murph-age.js +132 -7
- package/dist/commands/murph-age.js.map +1 -1
- package/dist/commands/public-validation-issue.d.ts +7 -0
- package/dist/commands/public-validation-issue.d.ts.map +1 -0
- package/dist/commands/public-validation-issue.js +7 -0
- package/dist/commands/public-validation-issue.js.map +1 -0
- package/dist/commands/scheduled-log.d.ts +12 -0
- package/dist/commands/scheduled-log.d.ts.map +1 -1
- package/dist/commands/scheduled-log.js +456 -106
- package/dist/commands/scheduled-log.js.map +1 -1
- package/dist/commands/wearables.d.ts +286 -0
- package/dist/commands/wearables.d.ts.map +1 -1
- package/dist/commands/wearables.js +33 -1
- package/dist/commands/wearables.js.map +1 -1
- package/dist/commands/workout-live.d.ts.map +1 -1
- package/dist/commands/workout-live.js +123 -42
- package/dist/commands/workout-live.js.map +1 -1
- package/dist/commands/workout-typed-options.d.ts +38 -0
- package/dist/commands/workout-typed-options.d.ts.map +1 -0
- package/dist/commands/workout-typed-options.js +90 -0
- package/dist/commands/workout-typed-options.js.map +1 -0
- package/dist/commands/workout.d.ts.map +1 -1
- package/dist/commands/workout.js +80 -73
- package/dist/commands/workout.js.map +1 -1
- package/dist/hosted-data-api-labels.d.ts.map +1 -1
- package/dist/hosted-data-api-labels.js +161 -30
- package/dist/hosted-data-api-labels.js.map +1 -1
- package/dist/incur-error-bridge.d.ts.map +1 -1
- package/dist/incur-error-bridge.js +7 -15
- package/dist/incur-error-bridge.js.map +1 -1
- package/dist/incur.generated.d.ts +22 -13
- package/dist/incur.generated.d.ts.map +1 -1
- package/dist/mapbox-address.d.ts.map +1 -1
- package/dist/mapbox-address.js +3 -4
- package/dist/mapbox-address.js.map +1 -1
- package/dist/mapbox-route-client.d.ts +2 -2
- package/dist/mapbox-route-client.d.ts.map +1 -1
- package/dist/mapbox-route-client.js +49 -12
- package/dist/mapbox-route-client.js.map +1 -1
- package/dist/mapbox-route-directions.d.ts.map +1 -1
- package/dist/mapbox-route-directions.js +0 -1
- package/dist/mapbox-route-directions.js.map +1 -1
- package/dist/mapbox-route-elevation.js +0 -1
- package/dist/mapbox-route-elevation.js.map +1 -1
- package/dist/mapbox-route-points.js +0 -2
- package/dist/mapbox-route-points.js.map +1 -1
- package/dist/mapbox-route.d.ts.map +1 -1
- package/dist/mapbox-route.js +1 -1
- package/dist/mapbox-route.js.map +1 -1
- package/dist/vault-cli-command-manifest.d.ts +304 -6
- package/dist/vault-cli-command-manifest.d.ts.map +1 -1
- package/dist/vault-cli-skill-hash.generated.d.ts +1 -1
- package/dist/vault-cli-skill-hash.generated.js +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant/ui/ink.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant/ui/ink.js +6 -2
- package/node_modules/@murphai/assistant-cli/dist/assistant/ui/ink.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/README.md +4 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-controller.d.ts +15 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-controller.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-controller.js +121 -24
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-controller.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-journal.js +2 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/active-turn-input-journal.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/ask-continuation.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/ask-continuation.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/ask-continuation.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.js +55 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.js +9 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/cli-surface-contract.generated.json +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-base-instructions.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-base-instructions.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-base-instructions.js +7 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-base-instructions.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn/planning.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn/planning.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn/planning.js +123 -22
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn/planning.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn-runner.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn-runner.js +48 -19
- package/node_modules/@murphai/assistant-engine/dist/assistant/codex-turn-runner.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/context-snapshot.js +3 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/context-snapshot.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/execution.d.ts +5 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/execution.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/execution.js +24 -10
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/execution.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/schedule.d.ts +2 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/schedule.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/schedule.js +12 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/cron/schedule.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/delivery-service.d.ts +5 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/delivery-service.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/delivery-service.js +9 -10
- package/node_modules/@murphai/assistant-engine/dist/assistant/delivery-service.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/execution-context.d.ts +41 -9
- package/node_modules/@murphai/assistant-engine/dist/assistant/execution-context.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/execution-context.js +9 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/execution-context.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/experiment-support-automations.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/experiment-support-automations.js +2 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/experiment-support-automations.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/hosted-tool-context.d.ts +3 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/hosted-tool-context.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/hosted-tool-context.js +8 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/hosted-tool-context.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/issue-reporting.d.ts +3 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/issue-reporting.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/issue-reporting.js +26 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/issue-reporting.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.js +687 -370
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/managed-automations.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/managed-automations.js +54 -18
- package/node_modules/@murphai/assistant-engine/dist/assistant/managed-automations.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/model-behavior.d.ts +2 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant/model-behavior.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/model-behavior.js +10 -9
- package/node_modules/@murphai/assistant-engine/dist/assistant/model-behavior.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/notification-turn.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/notification-turn.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/notification-turn.js +146 -18
- package/node_modules/@murphai/assistant-engine/dist/assistant/notification-turn.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-followup-seed.d.ts +25 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-followup-seed.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-followup-seed.js +72 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-followup-seed.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-state.d.ts +4 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-state.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-state.js +14 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/onboarding-state.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox/store.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox/store.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox/store.js +8 -5
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox/store.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox.d.ts +4 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox.js +8 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/outbox.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/private-completion-continuity.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/private-completion-continuity.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/codex-cli.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/codex-cli.js +25 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/codex-cli.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/helpers.d.ts +11 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/helpers.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/helpers.js +67 -176
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/helpers.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/types.d.ts +17 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/types.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/providers/types.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/reply-delivery-context.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/reply-delivery-context.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/reply-delivery-context.js +10 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/reply-delivery-context.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/runtime-residue.d.ts +19 -4
- package/node_modules/@murphai/assistant-engine/dist/assistant/runtime-residue.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/runtime-residue.js +76 -40
- package/node_modules/@murphai/assistant-engine/dist/assistant/runtime-residue.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-contracts.d.ts +3 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-usage.d.ts +4 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-usage.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-usage.js +8 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/service-usage.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/shared.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/shared.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/shared.js +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/shared.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/store/types.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/store/types.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/store.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/store.js +6 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/store.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.d.ts +3 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.js +57 -29
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-digest.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-digest.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-digest.js +17 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-digest.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-feedback.d.ts +4 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-feedback.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-feedback.js +21 -6
- package/node_modules/@murphai/assistant-engine/dist/assistant/tool-validation-feedback.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-finalizer.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-finalizer.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-finalizer.js +6 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-finalizer.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-input.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turn-input.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/usage-attribution.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/usage-attribution.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/usage-attribution.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-ask.js +8 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-ask.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-cli-access.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-cli-access.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-cli-access.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/analyze-video-tool.d.ts +49 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/analyze-video-tool.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/analyze-video-tool.js +387 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/analyze-video-tool.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-protocol.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-protocol.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-protocol.js +12 -13
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-protocol.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-requests.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-requests.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-requests.js +16 -5
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-requests.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-rpc.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-rpc.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-rpc.js +2 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/app-server-rpc.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/command-family.d.ts +4 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/command-family.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/command-family.js +26 -13
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/command-family.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tool-catalog.d.ts +1024 -1091
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tool-catalog.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tool-catalog.js +354 -330
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tool-catalog.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/analyze-video.d.ts +50 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/analyze-video.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/analyze-video.js +79 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/analyze-video.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation-model-input-schema.d.ts +4 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation-model-input-schema.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation-model-input-schema.js +153 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation-model-input-schema.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation.d.ts +4 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation.js +29 -35
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/automation.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/device.d.ts +4 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/device.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/device.js +43 -9
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/device.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/dynamic-tool-wrapper.d.ts +6 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/dynamic-tool-wrapper.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/dynamic-tool-wrapper.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/member-memory.d.ts +60 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/member-memory.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/member-memory.js +91 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/member-memory.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/physical-notes.d.ts +41 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/physical-notes.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/physical-notes.js +85 -9
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools/physical-notes.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools.d.ts +25 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools.js +280 -89
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/dynamic-tools.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/failures.js +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/response-card-validation-feedback.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/response-card-validation-feedback.js +1 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/response-card-validation-feedback.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/workout-delivery-context.d.ts +19 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/workout-delivery-context.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/workout-delivery-context.js +203 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex/workout-delivery-context.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex.d.ts +9 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex.js +220 -130
- package/node_modules/@murphai/assistant-engine/dist/assistant-codex.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-runtime-residue.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-runtime-residue.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-runtime-residue.js +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-runtime-residue.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-skill-assets.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-skill-assets.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-skill-assets.js +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-skill-assets.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/index.d.ts +2 -1
- package/node_modules/@murphai/assistant-engine/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/index.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/index.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/skills/automatic-meal-capture/SKILL.md +111 -68
- package/node_modules/@murphai/assistant-engine/skills/behavior-followthrough/SKILL.md +0 -2
- package/node_modules/@murphai/assistant-engine/skills/daily-activity/SKILL.md +11 -4
- package/node_modules/@murphai/assistant-engine/skills/food-journal/SKILL.md +83 -22
- package/node_modules/@murphai/assistant-engine/skills/hosted-low-usage/SKILL.md +3 -3
- package/node_modules/@murphai/assistant-engine/skills/murph-family/SKILL.md +1 -1
- package/node_modules/@murphai/assistant-engine/skills/nutrition-strategy/SKILL.md +17 -11
- package/node_modules/@murphai/assistant-engine/skills/nutrition-strategy/references/daily-nutrition-card-goals.md +62 -34
- package/node_modules/@murphai/assistant-engine/skills/physical-notes/SKILL.md +47 -0
- package/node_modules/@murphai/assistant-engine/skills/shared/exercise-catalog-runtime.md +6 -0
- package/node_modules/@murphai/assistant-engine/skills/signup-link/SKILL.md +1 -1
- package/node_modules/@murphai/assistant-engine/skills/strength-training/SKILL.md +12 -5
- package/node_modules/@murphai/assistant-engine/skills/tracked-table/SKILL.md +156 -66
- package/node_modules/@murphai/core/dist/automation.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/automation.js +17 -9
- package/node_modules/@murphai/core/dist/automation.js.map +1 -1
- package/node_modules/@murphai/core/dist/bank/habitat.d.ts +2 -0
- package/node_modules/@murphai/core/dist/bank/habitat.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/bank/habitat.js +27 -0
- package/node_modules/@murphai/core/dist/bank/habitat.js.map +1 -1
- package/node_modules/@murphai/core/dist/bank/regimens.js +1 -1
- package/node_modules/@murphai/core/dist/bank/regimens.js.map +1 -1
- package/node_modules/@murphai/core/dist/domains/events/ledger.d.ts +21 -0
- package/node_modules/@murphai/core/dist/domains/events/ledger.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/domains/events/ledger.js +102 -8
- package/node_modules/@murphai/core/dist/domains/events/ledger.js.map +1 -1
- package/node_modules/@murphai/core/dist/domains/events.d.ts +2 -2
- package/node_modules/@murphai/core/dist/domains/events.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/domains/events.js +1 -1
- package/node_modules/@murphai/core/dist/domains/events.js.map +1 -1
- package/node_modules/@murphai/core/dist/history/api.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/history/api.js +18 -3
- package/node_modules/@murphai/core/dist/history/api.js.map +1 -1
- package/node_modules/@murphai/core/dist/index.d.ts +4 -2
- package/node_modules/@murphai/core/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/index.js +3 -1
- package/node_modules/@murphai/core/dist/index.js.map +1 -1
- package/node_modules/@murphai/core/dist/mutations.d.ts +25 -2
- package/node_modules/@murphai/core/dist/mutations.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/mutations.js +1588 -78
- package/node_modules/@murphai/core/dist/mutations.js.map +1 -1
- package/node_modules/@murphai/core/dist/operations/index.d.ts +1 -1
- package/node_modules/@murphai/core/dist/operations/index.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/operations/index.js +1 -1
- package/node_modules/@murphai/core/dist/operations/index.js.map +1 -1
- package/node_modules/@murphai/core/dist/operations/write-batch.d.ts +2 -0
- package/node_modules/@murphai/core/dist/operations/write-batch.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/operations/write-batch.js +7 -0
- package/node_modules/@murphai/core/dist/operations/write-batch.js.map +1 -1
- package/node_modules/@murphai/core/dist/protocols.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/protocols.js +52 -9
- package/node_modules/@murphai/core/dist/protocols.js.map +1 -1
- package/node_modules/@murphai/core/dist/public-mutations.d.ts +1 -1
- package/node_modules/@murphai/core/dist/public-mutations.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/public-mutations.js +2 -2
- package/node_modules/@murphai/core/dist/public-mutations.js.map +1 -1
- package/node_modules/@murphai/core/dist/scheduled-logs.d.ts +3 -0
- package/node_modules/@murphai/core/dist/scheduled-logs.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/scheduled-logs.js +65 -29
- package/node_modules/@murphai/core/dist/scheduled-logs.js.map +1 -1
- package/node_modules/@murphai/device-syncd/README.md +140 -46
- package/node_modules/@murphai/device-syncd/dist/client.d.ts +14 -0
- package/node_modules/@murphai/device-syncd/dist/client.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/client.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/config/connect-routes.d.ts +4 -1
- package/node_modules/@murphai/device-syncd/dist/config/connect-routes.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/config/connect-routes.js +24 -2
- package/node_modules/@murphai/device-syncd/dist/config/connect-routes.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/config/junction-connect-sources.js +2 -2
- package/node_modules/@murphai/device-syncd/dist/config/junction-connect-sources.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/config/provider-manifests.d.ts +53 -0
- package/node_modules/@murphai/device-syncd/dist/config/provider-manifests.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/config/provider-manifests.js +13 -0
- package/node_modules/@murphai/device-syncd/dist/config/provider-manifests.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/connect-config.d.ts +1 -1
- package/node_modules/@murphai/device-syncd/dist/connect-config.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/connect-config.js +1 -1
- package/node_modules/@murphai/device-syncd/dist/connect-config.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/fitbit-migration.d.ts +57 -0
- package/node_modules/@murphai/device-syncd/dist/fitbit-migration.d.ts.map +1 -0
- package/node_modules/@murphai/device-syncd/dist/fitbit-migration.js +166 -0
- package/node_modules/@murphai/device-syncd/dist/fitbit-migration.js.map +1 -0
- package/node_modules/@murphai/device-syncd/dist/hosted-runtime.d.ts +43 -0
- package/node_modules/@murphai/device-syncd/dist/hosted-runtime.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/hosted-runtime.js +129 -5
- package/node_modules/@murphai/device-syncd/dist/hosted-runtime.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-historical-backfill-progress.d.ts +3 -0
- package/node_modules/@murphai/device-syncd/dist/junction-historical-backfill-progress.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-historical-backfill-progress.js +36 -16
- package/node_modules/@murphai/device-syncd/dist/junction-historical-backfill-progress.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-inline-authority.d.ts +17 -2
- package/node_modules/@murphai/device-syncd/dist/junction-inline-authority.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-inline-authority.js +99 -40
- package/node_modules/@murphai/device-syncd/dist/junction-inline-authority.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-source-reconnect.d.ts +17 -0
- package/node_modules/@murphai/device-syncd/dist/junction-source-reconnect.d.ts.map +1 -0
- package/node_modules/@murphai/device-syncd/dist/junction-source-reconnect.js +45 -0
- package/node_modules/@murphai/device-syncd/dist/junction-source-reconnect.js.map +1 -0
- package/node_modules/@murphai/device-syncd/dist/junction-timeseries-progress.d.ts +8 -1
- package/node_modules/@murphai/device-syncd/dist/junction-timeseries-progress.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/junction-timeseries-progress.js +12 -1
- package/node_modules/@murphai/device-syncd/dist/junction-timeseries-progress.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/providers/junction-client.d.ts +7 -0
- package/node_modules/@murphai/device-syncd/dist/providers/junction-client.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/providers/junction-client.js +285 -46
- package/node_modules/@murphai/device-syncd/dist/providers/junction-client.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/providers/junction.d.ts +9 -0
- package/node_modules/@murphai/device-syncd/dist/providers/junction.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/providers/junction.js +2045 -367
- package/node_modules/@murphai/device-syncd/dist/providers/junction.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/public-account.d.ts +2 -0
- package/node_modules/@murphai/device-syncd/dist/public-account.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/public-account.js +96 -0
- package/node_modules/@murphai/device-syncd/dist/public-account.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/public-ingress.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/public-ingress.js +6 -4
- package/node_modules/@murphai/device-syncd/dist/public-ingress.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/service.d.ts +6 -2
- package/node_modules/@murphai/device-syncd/dist/service.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/service.js +385 -44
- package/node_modules/@murphai/device-syncd/dist/service.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/source-staleness.d.ts +15 -0
- package/node_modules/@murphai/device-syncd/dist/source-staleness.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/source-staleness.js +25 -1
- package/node_modules/@murphai/device-syncd/dist/source-staleness.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/jobs.d.ts +10 -1
- package/node_modules/@murphai/device-syncd/dist/store/jobs.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/jobs.js +95 -1
- package/node_modules/@murphai/device-syncd/dist/store/jobs.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/schema.d.ts +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/schema.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/schema.js +25 -3
- package/node_modules/@murphai/device-syncd/dist/store/schema.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/sources.d.ts +24 -2
- package/node_modules/@murphai/device-syncd/dist/store/sources.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store/sources.js +223 -14
- package/node_modules/@murphai/device-syncd/dist/store/sources.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store.d.ts +13 -3
- package/node_modules/@murphai/device-syncd/dist/store.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/store.js +104 -22
- package/node_modules/@murphai/device-syncd/dist/store.js.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/types.d.ts +96 -9
- package/node_modules/@murphai/device-syncd/dist/types.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/types.js +4 -0
- package/node_modules/@murphai/device-syncd/dist/types.js.map +1 -1
- package/node_modules/@murphai/device-syncd/package.json +8 -0
- package/node_modules/@murphai/health-metrics/dist/definitions/activity.d.ts.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/definitions/activity.js +66 -0
- package/node_modules/@murphai/health-metrics/dist/definitions/activity.js.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/definitions/body.js +2 -2
- package/node_modules/@murphai/health-metrics/dist/definitions/body.js.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/normalize.d.ts.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/normalize.js +3 -0
- package/node_modules/@murphai/health-metrics/dist/normalize.js.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-metric-catalog.d.ts +3 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-metric-catalog.d.ts.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-metric-catalog.js +5 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-metric-catalog.js.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-provider-catalog.d.ts +79 -0
- package/node_modules/@murphai/health-metrics/dist/wearable-provider-catalog.d.ts.map +1 -1
- package/node_modules/@murphai/health-metrics/dist/wearable-provider-catalog.js +19 -0
- package/node_modules/@murphai/health-metrics/dist/wearable-provider-catalog.js.map +1 -1
- package/node_modules/@murphai/importers/dist/core-port.d.ts +15 -1
- package/node_modules/@murphai/importers/dist/core-port.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/core-port.js.map +1 -1
- package/node_modules/@murphai/importers/dist/create-importers.d.ts +9 -1
- package/node_modules/@murphai/importers/dist/create-importers.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/create-importers.js +12 -2
- package/node_modules/@murphai/importers/dist/create-importers.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/import-device-provider-snapshot.d.ts +17 -3
- package/node_modules/@murphai/importers/dist/device-providers/import-device-provider-snapshot.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/import-device-provider-snapshot.js +102 -8
- package/node_modules/@murphai/importers/dist/device-providers/import-device-provider-snapshot.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/index.d.ts +5 -4
- package/node_modules/@murphai/importers/dist/device-providers/index.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/index.js +4 -3
- package/node_modules/@murphai/importers/dist/device-providers/index.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-bounded-features.d.ts +11 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-bounded-features.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-bounded-features.js +420 -27
- package/node_modules/@murphai/importers/dist/device-providers/junction-bounded-features.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-origin.d.ts +1 -0
- package/node_modules/@murphai/importers/dist/device-providers/junction-origin.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-origin.js +7 -2
- package/node_modules/@murphai/importers/dist/device-providers/junction-origin.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-resources.d.ts +4 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-resources.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-resources.js +42 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-resources.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction-timeseries-features.d.ts +79 -0
- package/node_modules/@murphai/importers/dist/device-providers/junction-timeseries-features.d.ts.map +1 -0
- package/node_modules/@murphai/importers/dist/device-providers/junction-timeseries-features.js +356 -0
- package/node_modules/@murphai/importers/dist/device-providers/junction-timeseries-features.js.map +1 -0
- package/node_modules/@murphai/importers/dist/device-providers/junction.d.ts +76 -3
- package/node_modules/@murphai/importers/dist/device-providers/junction.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/junction.js +1676 -347
- package/node_modules/@murphai/importers/dist/device-providers/junction.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/shared-normalization.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/shared-normalization.js +1 -0
- package/node_modules/@murphai/importers/dist/device-providers/shared-normalization.js.map +1 -1
- package/node_modules/@murphai/importers/dist/device-providers/types.d.ts +9 -1
- package/node_modules/@murphai/importers/dist/device-providers/types.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/index.d.ts +2 -2
- package/node_modules/@murphai/importers/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/importers/dist/index.js +1 -1
- package/node_modules/@murphai/importers/dist/index.js.map +1 -1
- package/node_modules/@murphai/inboxd/README.md +1 -1
- package/node_modules/@murphai/inboxd/dist/indexing/retention.d.ts +6 -0
- package/node_modules/@murphai/inboxd/dist/indexing/retention.d.ts.map +1 -1
- package/node_modules/@murphai/inboxd/dist/indexing/retention.js +64 -18
- package/node_modules/@murphai/inboxd/dist/indexing/retention.js.map +1 -1
- package/node_modules/@murphai/inboxd/dist/retention.d.ts +1 -1
- package/node_modules/@murphai/inboxd/dist/retention.d.ts.map +1 -1
- package/node_modules/@murphai/inboxd/dist/retention.js +1 -1
- package/node_modules/@murphai/inboxd/dist/retention.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.d.ts +1 -0
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.js +5 -0
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-response-cards.d.ts +175 -152
- package/node_modules/@murphai/operator-config/dist/assistant-response-cards.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-response-cards.js +62 -41
- package/node_modules/@murphai/operator-config/dist/assistant-response-cards.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/device-sync-client.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/device-sync-client.js +1 -5
- package/node_modules/@murphai/operator-config/dist/device-sync-client.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/vault-cli-contracts.d.ts +66 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/vault-cli-contracts.js +58 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-contracts.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/vault-cli-error-projection.d.ts +24 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-error-projection.d.ts.map +1 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-error-projection.js +219 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-error-projection.js.map +1 -0
- package/node_modules/@murphai/operator-config/dist/vault-cli-errors.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/vault-cli-errors.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/workout-action-binding.d.ts +9 -0
- package/node_modules/@murphai/operator-config/dist/workout-action-binding.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/workout-action-binding.js +48 -5
- package/node_modules/@murphai/operator-config/dist/workout-action-binding.js.map +1 -1
- package/node_modules/@murphai/operator-config/package.json +5 -0
- package/node_modules/@murphai/query/README.md +20 -5
- package/node_modules/@murphai/query/dist/browser-replica/build.d.ts +1 -1
- package/node_modules/@murphai/query/dist/browser-replica/build.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/browser-replica/build.js +25 -16
- package/node_modules/@murphai/query/dist/browser-replica/build.js.map +1 -1
- package/node_modules/@murphai/query/dist/browser-replica/json.d.ts +6 -0
- package/node_modules/@murphai/query/dist/browser-replica/json.d.ts.map +1 -0
- package/node_modules/@murphai/query/dist/browser-replica/json.js +103 -0
- package/node_modules/@murphai/query/dist/browser-replica/json.js.map +1 -0
- package/node_modules/@murphai/query/dist/browser-replica/shared.d.ts +1 -0
- package/node_modules/@murphai/query/dist/browser-replica/shared.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/browser-replica/source.d.ts +13 -0
- package/node_modules/@murphai/query/dist/browser-replica/source.d.ts.map +1 -0
- package/node_modules/@murphai/query/dist/browser-replica/source.js +45 -0
- package/node_modules/@murphai/query/dist/browser-replica/source.js.map +1 -0
- package/node_modules/@murphai/query/dist/browser-replica-server.d.ts +2 -0
- package/node_modules/@murphai/query/dist/browser-replica-server.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/browser-replica-server.js +2 -0
- package/node_modules/@murphai/query/dist/browser-replica-server.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/blood-tests.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/blood-tests.js +3 -2
- package/node_modules/@murphai/query/dist/health/blood-tests.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/canonical-collector.d.ts +11 -1
- package/node_modules/@murphai/query/dist/health/canonical-collector.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/canonical-collector.js +39 -16
- package/node_modules/@murphai/query/dist/health/canonical-collector.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/entity-slices.d.ts +9 -3
- package/node_modules/@murphai/query/dist/health/entity-slices.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/entity-slices.js +6 -6
- package/node_modules/@murphai/query/dist/health/entity-slices.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/immunizations.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/immunizations.js +14 -11
- package/node_modules/@murphai/query/dist/health/immunizations.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/loaders.d.ts +21 -7
- package/node_modules/@murphai/query/dist/health/loaders.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/loaders.js +53 -15
- package/node_modules/@murphai/query/dist/health/loaders.js.map +1 -1
- package/node_modules/@murphai/query/dist/health/registries.d.ts +1 -0
- package/node_modules/@murphai/query/dist/health/registries.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/health/registries.js.map +1 -1
- package/node_modules/@murphai/query/dist/index.d.ts +4 -3
- package/node_modules/@murphai/query/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/index.js +4 -3
- package/node_modules/@murphai/query/dist/index.js.map +1 -1
- package/node_modules/@murphai/query/dist/meal-nutrition.d.ts +1 -1
- package/node_modules/@murphai/query/dist/meal-nutrition.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/meal-nutrition.js +10 -3
- package/node_modules/@murphai/query/dist/meal-nutrition.js.map +1 -1
- package/node_modules/@murphai/query/dist/metrics/projection.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/metrics/projection.js +19 -6
- package/node_modules/@murphai/query/dist/metrics/projection.js.map +1 -1
- package/node_modules/@murphai/query/dist/murph-age.d.ts +5 -1
- package/node_modules/@murphai/query/dist/murph-age.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/murph-age.js +22 -8
- package/node_modules/@murphai/query/dist/murph-age.js.map +1 -1
- package/node_modules/@murphai/query/dist/projection/entity-store.js +4 -4
- package/node_modules/@murphai/query/dist/projection/entity-store.js.map +1 -1
- package/node_modules/@murphai/query/dist/projection/schema.d.ts +1 -1
- package/node_modules/@murphai/query/dist/projection/schema.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/projection/schema.js +3 -1
- package/node_modules/@murphai/query/dist/projection/schema.js.map +1 -1
- package/node_modules/@murphai/query/dist/projection/wearable-summary-compose.js +4 -0
- package/node_modules/@murphai/query/dist/projection/wearable-summary-compose.js.map +1 -1
- package/node_modules/@murphai/query/dist/projection/wearable-summary-projector.js +4 -0
- package/node_modules/@murphai/query/dist/projection/wearable-summary-projector.js.map +1 -1
- package/node_modules/@murphai/query/dist/query-visibility.d.ts +5 -0
- package/node_modules/@murphai/query/dist/query-visibility.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/query-visibility.js +11 -5
- package/node_modules/@murphai/query/dist/query-visibility.js.map +1 -1
- package/node_modules/@murphai/query/dist/read-model.d.ts +8 -0
- package/node_modules/@murphai/query/dist/read-model.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/read-model.js +11 -1
- package/node_modules/@murphai/query/dist/read-model.js.map +1 -1
- package/node_modules/@murphai/query/dist/scheduled-logs.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/scheduled-logs.js +55 -79
- package/node_modules/@murphai/query/dist/scheduled-logs.js.map +1 -1
- package/node_modules/@murphai/query/dist/vault-source.d.ts +22 -4
- package/node_modules/@murphai/query/dist/vault-source.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/vault-source.js +188 -59
- package/node_modules/@murphai/query/dist/vault-source.js.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/candidates.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/candidates.js +26 -15
- package/node_modules/@murphai/query/dist/wearables/candidates.js.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/public-output.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/public-output.js +6 -0
- package/node_modules/@murphai/query/dist/wearables/public-output.js.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/source-health.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/source-health.js +4 -0
- package/node_modules/@murphai/query/dist/wearables/source-health.js.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/types.d.ts +41 -4
- package/node_modules/@murphai/query/dist/wearables/types.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/types.js +2 -0
- package/node_modules/@murphai/query/dist/wearables/types.js.map +1 -1
- package/node_modules/@murphai/query/dist/wearables/workout-features.d.ts +5 -0
- package/node_modules/@murphai/query/dist/wearables/workout-features.d.ts.map +1 -0
- package/node_modules/@murphai/query/dist/wearables/workout-features.js +179 -0
- package/node_modules/@murphai/query/dist/wearables/workout-features.js.map +1 -0
- package/node_modules/@murphai/query/dist/wearables.d.ts.map +1 -1
- package/node_modules/@murphai/query/dist/wearables.js +23 -6
- package/node_modules/@murphai/query/dist/wearables.js.map +1 -1
- package/node_modules/@murphai/query/package.json +5 -0
- package/node_modules/@murphai/runtime-state/README.md +1 -1
- package/node_modules/@murphai/runtime-state/dist/assistant-runtime-issues.d.ts +4 -0
- package/node_modules/@murphai/runtime-state/dist/assistant-runtime-issues.d.ts.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/assistant-runtime-issues.js +16 -3
- package/node_modules/@murphai/runtime-state/dist/assistant-runtime-issues.js.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/hosted-bundles.d.ts +1 -0
- package/node_modules/@murphai/runtime-state/dist/hosted-bundles.d.ts.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/hosted-bundles.js +31 -0
- package/node_modules/@murphai/runtime-state/dist/hosted-bundles.js.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/hosted-email.d.ts +5 -0
- package/node_modules/@murphai/runtime-state/dist/hosted-email.d.ts.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/hosted-email.js +10 -0
- package/node_modules/@murphai/runtime-state/dist/hosted-email.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/incur-error-bridge.d.ts.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/incur-error-bridge.js +6 -15
- package/node_modules/@murphai/setup-cli/dist/incur-error-bridge.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/README.md +9 -0
- package/node_modules/@murphai/vault-usecases/dist/commands/query-record-command-helpers.d.ts +6 -0
- package/node_modules/@murphai/vault-usecases/dist/commands/query-record-command-helpers.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/commands/query-record-command-helpers.js +49 -1
- package/node_modules/@murphai/vault-usecases/dist/commands/query-record-command-helpers.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/capture.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/capture.js +22 -12
- package/node_modules/@murphai/vault-usecases/dist/usecases/capture.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/clinical-imports.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/clinical-imports.js +1 -3
- package/node_modules/@murphai/vault-usecases/dist/usecases/clinical-imports.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/document-meal-read.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/document-meal-read.js +24 -18
- package/node_modules/@murphai/vault-usecases/dist/usecases/document-meal-read.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/encounter.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/encounter.js +1 -3
- package/node_modules/@murphai/vault-usecases/dist/usecases/encounter.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/event-record-mutations.d.ts +8 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/event-record-mutations.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/event-record-mutations.js +56 -5
- package/node_modules/@murphai/vault-usecases/dist/usecases/event-record-mutations.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/exact-event-record.d.ts +20 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/exact-event-record.d.ts.map +1 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/exact-event-record.js +72 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/exact-event-record.js.map +1 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/experiment-journal-vault.d.ts +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/experiment-journal-vault.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/experiment-journal-vault.js +42 -9
- package/node_modules/@murphai/vault-usecases/dist/usecases/experiment-journal-vault.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/explicit-health-family-services.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/explicit-health-family-services.js +158 -24
- package/node_modules/@murphai/vault-usecases/dist/usecases/explicit-health-family-services.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/food.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/food.js +98 -5
- package/node_modules/@murphai/vault-usecases/dist/usecases/food.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/integrated-services.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/integrated-services.js +20 -4
- package/node_modules/@murphai/vault-usecases/dist/usecases/integrated-services.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/intervention-experiment-link.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/intervention-experiment-link.js +49 -24
- package/node_modules/@murphai/vault-usecases/dist/usecases/intervention-experiment-link.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/measurement-read.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/measurement-read.js +13 -11
- package/node_modules/@murphai/vault-usecases/dist/usecases/measurement-read.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/provider-event.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/provider-event.js +18 -11
- package/node_modules/@murphai/vault-usecases/dist/usecases/provider-event.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/recipe.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/recipe.js +43 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/recipe.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/shared.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/shared.js +1 -3
- package/node_modules/@murphai/vault-usecases/dist/usecases/shared.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/vault-usecase-helpers.d.ts +2 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/vault-usecase-helpers.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/vault-usecase-helpers.js +24 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/vault-usecase-helpers.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-format.d.ts +2 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-format.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-model.d.ts +36 -4
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-model.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-model.js +48 -12
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-model.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-state.d.ts +14 -14
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-state.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-state.js +84 -82
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live-state.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live.d.ts +16 -4
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live.js +316 -79
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-live.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-read.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-read.js +19 -17
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-read.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout.d.ts +20 -7
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout.js +56 -31
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout.js.map +1 -1
- package/node_modules/incur/dist/Cli.js +7 -32
- package/node_modules/incur/dist/Errors.d.ts +17 -0
- package/node_modules/incur/dist/Errors.js +23 -0
- package/node_modules/incur/dist/internal/command.d.ts +2 -0
- package/node_modules/incur/dist/internal/command.js +2 -6
- package/node_modules/incur/src/Cli.ts +11 -32
- package/node_modules/incur/src/Errors.ts +36 -0
- package/node_modules/incur/src/internal/command.ts +4 -6
- package/package.json +4 -4
- package/node_modules/@murphai/assistant-engine/skills/nutrition-strategy/references/daily-nutrition-card-safety.md +0 -211
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import { performance } from "node:perf_hooks";
|
|
5
|
+
import { auditRecordSchema, assertContractId, collectEventRawReferencePaths, compareIsoTimestampsAscending, deviceDataOriginSchema, experimentFrontmatterSchema, externalRefSchema, journalDayFrontmatterSchema, eventRecordSchema, eventImportDecisionSchema, isWritableIsoDateTime, resolveFloatingIsoTimestampInTimeZone, safeParseContract, sampleRecordSchema, } from "@murphai/contracts";
|
|
4
6
|
import { BASELINE_EVENT_KINDS, BASELINE_SAMPLE_STREAMS, EVENT_SCHEMA_VERSION, EVENT_SOURCES, FRONTMATTER_SCHEMA_VERSIONS, ID_PREFIXES, SAMPLE_QUALITIES, SAMPLE_SCHEMA_VERSION, SAMPLE_SOURCES, VAULT_LAYOUT, } from "./constants.js";
|
|
5
7
|
import { emitAuditRecord } from "./audit.js";
|
|
6
8
|
import { prepareEventAttachments, stagePreparedEventAttachmentsInBatch, } from "./event-attachments.js";
|
|
@@ -26,6 +28,7 @@ import { loadVault } from "./vault.js";
|
|
|
26
28
|
import { statAndHashVaultFile } from "./raw-artifact-integrity.js";
|
|
27
29
|
const DENSE_DEVICE_TELEMETRY_NOT_ALLOWED_CODE = "VAULT_DENSE_DEVICE_TELEMETRY_NOT_ALLOWED";
|
|
28
30
|
const DENSE_DEVICE_SAMPLES_NOT_ALLOWED_LEGACY_CODE = "VAULT_DENSE_DEVICE_SAMPLES_NOT_ALLOWED";
|
|
31
|
+
const MAX_JUNCTION_DAILY_ALIAS_REPAIR_REVISIONS = 64;
|
|
29
32
|
const RESERVED_DEVICE_EVENT_FIELD_NAMES = new Set([
|
|
30
33
|
"schemaVersion",
|
|
31
34
|
"id",
|
|
@@ -49,6 +52,9 @@ const RESERVED_DEVICE_EVENT_FIELD_NAMES = new Set([
|
|
|
49
52
|
"visibility",
|
|
50
53
|
]);
|
|
51
54
|
const MAX_DEVICE_PROVIDER_SAMPLE_ROWS_DEFAULT = 1_000;
|
|
55
|
+
// Junction menstrual resources admit 512 dated non-BBT facts plus the
|
|
56
|
+
// period-length and cycle-length scalar facets for the same resource version.
|
|
57
|
+
const MAX_DEVICE_AUTHORITATIVE_CURRENT_FACETS = 514;
|
|
52
58
|
const EVENT_KIND_SET = new Set(BASELINE_EVENT_KINDS);
|
|
53
59
|
const EVENT_SOURCE_SET = new Set(EVENT_SOURCES);
|
|
54
60
|
const SAMPLE_STREAM_SET = new Set(BASELINE_SAMPLE_STREAMS);
|
|
@@ -543,7 +549,8 @@ function normalizeDeviceEventInputs(eventInputs, context) {
|
|
|
543
549
|
const inputTimeZone = typeof eventInput.timeZone === "string" ? eventInput.timeZone : undefined;
|
|
544
550
|
const preservesProviderDayWithoutTimeZone = Boolean(inputDayKey &&
|
|
545
551
|
!inputTimeZone &&
|
|
546
|
-
(
|
|
552
|
+
(context.provider === "junction"
|
|
553
|
+
|| isJunctionSleepStageExternalRefInput(eventInput.externalRef)
|
|
547
554
|
|| isDateOnlyFloatingProviderDayInput(inputDayKey, eventInput.dataOrigin)));
|
|
548
555
|
const seed = buildNormalizedEventSeed({
|
|
549
556
|
kind,
|
|
@@ -658,7 +665,65 @@ function normalizeDeviceBatchObjectArray(input) {
|
|
|
658
665
|
}
|
|
659
666
|
return input.value.map((entry, index) => assertPlainObject(entry, input.itemCode, `${input.itemLabel} ${index + 1} must be a plain object.`));
|
|
660
667
|
}
|
|
661
|
-
function
|
|
668
|
+
function normalizeDeviceAuthoritativeEventSets(inputs, events) {
|
|
669
|
+
if (inputs.length > 128) {
|
|
670
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SETS", "Device batch authoritativeEventSets exceeds 128 resources.");
|
|
671
|
+
}
|
|
672
|
+
const sets = inputs.map((input, index) => {
|
|
673
|
+
const parsedRef = safeParseContract(externalRefSchema, {
|
|
674
|
+
system: input.system,
|
|
675
|
+
resourceType: input.resourceType,
|
|
676
|
+
resourceId: input.resourceId,
|
|
677
|
+
version: input.version,
|
|
678
|
+
});
|
|
679
|
+
if (!parsedRef.success || !parsedRef.data.version || !isWritableIsoDateTime(parsedRef.data.version)) {
|
|
680
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SET", `Device authoritative event set ${index + 1} requires a valid versioned externalRef identity.`);
|
|
681
|
+
}
|
|
682
|
+
const facetPrefixes = normalizeUniqueTextList(input.facetPrefixes, `authoritativeEventSets[${index}].facetPrefixes`, 32, 200);
|
|
683
|
+
const currentFacets = normalizeUniqueTextList(input.currentFacets, `authoritativeEventSets[${index}].currentFacets`, MAX_DEVICE_AUTHORITATIVE_CURRENT_FACETS, 200) ?? [];
|
|
684
|
+
if (!facetPrefixes || facetPrefixes.length === 0) {
|
|
685
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SET", `Device authoritative event set ${index + 1} requires at least one facet prefix.`);
|
|
686
|
+
}
|
|
687
|
+
const ownsFacet = (facet) => facetPrefixes.some((prefix) => facet === prefix || facet.startsWith(`${prefix}-`));
|
|
688
|
+
if (currentFacets.some((facet) => !ownsFacet(facet))) {
|
|
689
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SET", `Device authoritative event set ${index + 1} contains a current facet outside its owned prefixes.`);
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
system: parsedRef.data.system,
|
|
693
|
+
resourceType: parsedRef.data.resourceType,
|
|
694
|
+
resourceId: parsedRef.data.resourceId,
|
|
695
|
+
version: parsedRef.data.version,
|
|
696
|
+
facetPrefixes: [...facetPrefixes].sort(),
|
|
697
|
+
currentFacets: new Set(currentFacets),
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
const identityKeys = sets.map((set) => stableStringify({
|
|
701
|
+
system: set.system,
|
|
702
|
+
resourceType: set.resourceType,
|
|
703
|
+
resourceId: set.resourceId,
|
|
704
|
+
}));
|
|
705
|
+
if (new Set(identityKeys).size !== identityKeys.length) {
|
|
706
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SETS", "Device batch authoritativeEventSets contains duplicate resource identities.");
|
|
707
|
+
}
|
|
708
|
+
for (const set of sets) {
|
|
709
|
+
for (const facet of set.currentFacets) {
|
|
710
|
+
// Unversioned members follow complete-set reconciliation: the set's
|
|
711
|
+
// version orders only retraction tombstones, while member events collapse
|
|
712
|
+
// by semantic content and reassert through the serialized set seam.
|
|
713
|
+
const hasCurrentEvent = events.some(({ seed }) => seed.externalRef?.system === set.system
|
|
714
|
+
&& seed.externalRef.resourceType === set.resourceType
|
|
715
|
+
&& seed.externalRef.resourceId === set.resourceId
|
|
716
|
+
&& (seed.externalRef.version === set.version
|
|
717
|
+
|| seed.externalRef.version === undefined)
|
|
718
|
+
&& seed.externalRef.facet === facet);
|
|
719
|
+
if (!hasCurrentEvent) {
|
|
720
|
+
throw new VaultError("VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SET", `Device authoritative event set current facet "${facet}" has no matching current event.`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return sets;
|
|
725
|
+
}
|
|
726
|
+
function normalizeDeviceBatchInputs({ provider, accountId, importedAt = new Date(), defaultTimeZone: fallbackTimeZone, source = "device", events = [], samples = [], evidenceParts = [], authoritativeEventSets = [], ingestReceipt, provenance, }) {
|
|
662
727
|
const normalizedProvider = sanitizePathSegment(provider, "provider");
|
|
663
728
|
const normalizedAccountId = typeof accountId === "string" && accountId.trim() ? accountId.trim() : undefined;
|
|
664
729
|
const normalizedImportedAt = toIsoTimestamp(importedAt, "importedAt");
|
|
@@ -688,12 +753,26 @@ function normalizeDeviceBatchInputs({ provider, accountId, importedAt = new Date
|
|
|
688
753
|
itemCode: "VAULT_INVALID_RAW_ARTIFACT",
|
|
689
754
|
itemLabel: "Device raw artifact",
|
|
690
755
|
});
|
|
756
|
+
const authoritativeEventSetInputs = normalizeDeviceBatchObjectArray({
|
|
757
|
+
value: authoritativeEventSets,
|
|
758
|
+
code: "VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SETS",
|
|
759
|
+
message: "Device batch authoritativeEventSets must be an array when provided.",
|
|
760
|
+
itemCode: "VAULT_INVALID_DEVICE_AUTHORITATIVE_EVENT_SET",
|
|
761
|
+
itemLabel: "Device authoritative event set",
|
|
762
|
+
});
|
|
691
763
|
if (eventInputs.length === 0
|
|
692
764
|
&& sampleInputs.length === 0
|
|
693
765
|
&& evidencePartInputs.length === 0
|
|
694
766
|
&& !normalizedIngestReceipt) {
|
|
695
767
|
throw new VaultError("VAULT_INVALID_DEVICE_BATCH", "importDeviceBatch requires at least one event, sample, evidence part, or ingest receipt.");
|
|
696
768
|
}
|
|
769
|
+
const normalizedEvents = normalizeDeviceEventInputs(eventInputs, {
|
|
770
|
+
provider: normalizedProvider,
|
|
771
|
+
accountId: normalizedAccountId,
|
|
772
|
+
importedAt: normalizedImportedAt,
|
|
773
|
+
source: normalizedSource,
|
|
774
|
+
defaultTimeZone,
|
|
775
|
+
});
|
|
697
776
|
return {
|
|
698
777
|
provider: normalizedProvider,
|
|
699
778
|
accountId: normalizedAccountId,
|
|
@@ -703,13 +782,7 @@ function normalizeDeviceBatchInputs({ provider, accountId, importedAt = new Date
|
|
|
703
782
|
provenance: normalizedProvenance,
|
|
704
783
|
ingestReceipt: normalizedIngestReceipt,
|
|
705
784
|
legacyIngestReceipt,
|
|
706
|
-
events:
|
|
707
|
-
provider: normalizedProvider,
|
|
708
|
-
accountId: normalizedAccountId,
|
|
709
|
-
importedAt: normalizedImportedAt,
|
|
710
|
-
source: normalizedSource,
|
|
711
|
-
defaultTimeZone,
|
|
712
|
-
}),
|
|
785
|
+
events: normalizedEvents,
|
|
713
786
|
samples: normalizeDeviceSampleInputs(sampleInputs, {
|
|
714
787
|
provider: normalizedProvider,
|
|
715
788
|
accountId: normalizedAccountId,
|
|
@@ -717,6 +790,7 @@ function normalizeDeviceBatchInputs({ provider, accountId, importedAt = new Date
|
|
|
717
790
|
defaultTimeZone,
|
|
718
791
|
}),
|
|
719
792
|
evidenceParts: normalizeDeviceEvidencePartInputs(evidencePartInputs, normalizedProvider),
|
|
793
|
+
authoritativeEventSets: normalizeDeviceAuthoritativeEventSets(authoritativeEventSetInputs, normalizedEvents),
|
|
720
794
|
};
|
|
721
795
|
}
|
|
722
796
|
function prepareDeviceEvidenceParts(evidenceParts) {
|
|
@@ -863,10 +937,43 @@ function junctionSparseAffectedDayKeys(targets) {
|
|
|
863
937
|
}
|
|
864
938
|
// Device-sync content equality ignores per-import identity (id, lifecycle,
|
|
865
939
|
// recordedAt) AND rawRefs, because device imports mint fresh raw-artifact
|
|
866
|
-
// paths on every sync run.
|
|
867
|
-
|
|
940
|
+
// paths on every sync run. Member-edit comparison additionally excludes the
|
|
941
|
+
// immutable provider attribution fields while retaining every public edit.
|
|
942
|
+
function eventContentKey(record, options) {
|
|
868
943
|
const { id: _id, rawRefs: _rawRefs, lifecycle: _lifecycle, recordedAt: _recordedAt, ...content } = record;
|
|
869
|
-
|
|
944
|
+
if (options.includeProviderAttribution) {
|
|
945
|
+
return stableStringify(content);
|
|
946
|
+
}
|
|
947
|
+
const { dataOrigin: _dataOrigin, externalRef: _externalRef, ...memberMutableContent } = content;
|
|
948
|
+
return stableStringify(memberMutableContent);
|
|
949
|
+
}
|
|
950
|
+
function deviceEventContentKey(record) {
|
|
951
|
+
const { id: _id, rawRefs: _rawRefs, lifecycle: _lifecycle, recordedAt: _recordedAt, externalRef, dataOrigin, ...semanticContent } = record;
|
|
952
|
+
return stableStringify({
|
|
953
|
+
...semanticContent,
|
|
954
|
+
// Identity and source ordering are enforced independently. Keeping them
|
|
955
|
+
// out of semantic equality prevents a newer provider capture/version from
|
|
956
|
+
// masquerading as a health-data change over a member-authored revision.
|
|
957
|
+
externalRef: externalRef
|
|
958
|
+
? {
|
|
959
|
+
system: externalRef.system,
|
|
960
|
+
resourceType: externalRef.resourceType,
|
|
961
|
+
resourceId: externalRef.resourceId,
|
|
962
|
+
facet: externalRef.facet ?? null,
|
|
963
|
+
}
|
|
964
|
+
: null,
|
|
965
|
+
dataOrigin: dataOrigin
|
|
966
|
+
? {
|
|
967
|
+
// This is the fixed data-origin schema discriminator, not the
|
|
968
|
+
// provider resource version used for ordering.
|
|
969
|
+
version: dataOrigin.version,
|
|
970
|
+
aggregatorProvider: dataOrigin.aggregatorProvider ?? null,
|
|
971
|
+
sourceProviderSlug: dataOrigin.sourceProviderSlug ?? null,
|
|
972
|
+
sourceType: dataOrigin.sourceType ?? null,
|
|
973
|
+
sourceInstanceId: dataOrigin.sourceInstanceId ?? null,
|
|
974
|
+
}
|
|
975
|
+
: null,
|
|
976
|
+
});
|
|
870
977
|
}
|
|
871
978
|
function deviceEventContentFingerprint(record) {
|
|
872
979
|
return createHash("sha256").update(deviceEventContentKey(record)).digest("hex");
|
|
@@ -948,8 +1055,11 @@ function orderEventImportDecisionsBySourceVersion(decisions) {
|
|
|
948
1055
|
return ordered;
|
|
949
1056
|
}
|
|
950
1057
|
async function indexLatestEventsByExternalRef(vaultRoot, relativePaths, signal) {
|
|
1058
|
+
const aliasRepairContaminatedEventIds = new Set();
|
|
1059
|
+
const aliasRepairContaminatedRefKeys = new Set();
|
|
951
1060
|
const deviceOwnerRevisionsByRefKeyAndFingerprint = new Map();
|
|
952
1061
|
const junctionSparseDayHistoryById = new Map();
|
|
1062
|
+
const liveOwnerIdsByRefKey = new Map();
|
|
953
1063
|
const latestByRefKey = new Map();
|
|
954
1064
|
const latestById = new Map();
|
|
955
1065
|
const maxRevisionById = new Map();
|
|
@@ -969,6 +1079,15 @@ async function indexLatestEventsByExternalRef(vaultRoot, relativePaths, signal)
|
|
|
969
1079
|
signal?.throwIfAborted();
|
|
970
1080
|
const parsed = safeParseContract(eventRecordSchema, raw);
|
|
971
1081
|
if (!parsed.success) {
|
|
1082
|
+
if (isLooseRecordValue(raw)) {
|
|
1083
|
+
if (typeof raw.id === "string" && raw.id.length > 0) {
|
|
1084
|
+
aliasRepairContaminatedEventIds.add(raw.id);
|
|
1085
|
+
}
|
|
1086
|
+
const rejectedRefKey = readRejectedEventExternalRefKey(raw.externalRef);
|
|
1087
|
+
if (rejectedRefKey) {
|
|
1088
|
+
aliasRepairContaminatedRefKeys.add(rejectedRefKey);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
972
1091
|
return;
|
|
973
1092
|
}
|
|
974
1093
|
const entry = { relativePath, record: parsed.data };
|
|
@@ -1045,13 +1164,19 @@ async function indexLatestEventsByExternalRef(vaultRoot, relativePaths, signal)
|
|
|
1045
1164
|
const refKey = eventExternalRefKey(externalRefEntry.record.externalRef);
|
|
1046
1165
|
const indexedExternalRefEntry = state.latestDeviceExternalRefEntryByRefKey.get(refKey) ?? externalRefEntry;
|
|
1047
1166
|
const refGroup = groupedByRefKey.get(refKey) ?? [];
|
|
1048
|
-
|
|
1167
|
+
const indexedMatch = {
|
|
1049
1168
|
indexedExternalRef: indexedExternalRefEntry.record.externalRef ?? externalRefEntry.record.externalRef,
|
|
1050
1169
|
indexedRecord: indexedExternalRefEntry.record,
|
|
1051
1170
|
relativePath: latestForId.relativePath,
|
|
1052
1171
|
record: latestForId.record,
|
|
1053
|
-
}
|
|
1172
|
+
};
|
|
1173
|
+
refGroup.push(indexedMatch);
|
|
1054
1174
|
groupedByRefKey.set(refKey, refGroup);
|
|
1175
|
+
if (!isDeletedEventSpineRecord(latestForId.record)) {
|
|
1176
|
+
const liveOwnerIds = liveOwnerIdsByRefKey.get(refKey) ?? new Set();
|
|
1177
|
+
liveOwnerIds.add(latestForId.record.id);
|
|
1178
|
+
liveOwnerIdsByRefKey.set(refKey, liveOwnerIds);
|
|
1179
|
+
}
|
|
1055
1180
|
}
|
|
1056
1181
|
for (const [refKey, group] of groupedByRefKey) {
|
|
1057
1182
|
signal?.throwIfAborted();
|
|
@@ -1062,15 +1187,45 @@ async function indexLatestEventsByExternalRef(vaultRoot, relativePaths, signal)
|
|
|
1062
1187
|
latestByRefKey.set(refKey, latest);
|
|
1063
1188
|
}
|
|
1064
1189
|
}
|
|
1190
|
+
const junctionNoIdProfilePredecessorsByScope = indexJunctionNoIdProfilePredecessors(latestByRefKey.values());
|
|
1065
1191
|
return {
|
|
1192
|
+
aliasRepairContaminatedEventIds,
|
|
1193
|
+
aliasRepairContaminatedRefKeys,
|
|
1194
|
+
aliasRepairHistoryById: new Map(),
|
|
1066
1195
|
deviceOwnerRevisionsByRefKeyAndFingerprint,
|
|
1196
|
+
junctionNoIdProfilePredecessorsByScope,
|
|
1067
1197
|
junctionSparseDayHistoryById,
|
|
1068
1198
|
latestByRefKey,
|
|
1069
1199
|
latestById,
|
|
1200
|
+
liveOwnerIdsByRefKey,
|
|
1070
1201
|
maxRevisionById,
|
|
1071
1202
|
revisionsById,
|
|
1072
1203
|
};
|
|
1073
1204
|
}
|
|
1205
|
+
async function loadBoundedAliasRepairHistories(vaultRoot, relativePaths, eventIds) {
|
|
1206
|
+
const histories = new Map();
|
|
1207
|
+
for (const relativePath of relativePaths) {
|
|
1208
|
+
await visitJsonlRecordsInterruptible({
|
|
1209
|
+
vaultRoot,
|
|
1210
|
+
relativePath,
|
|
1211
|
+
visit(raw) {
|
|
1212
|
+
const parsed = safeParseContract(eventRecordSchema, raw);
|
|
1213
|
+
if (!parsed.success || !eventIds.has(parsed.data.id)) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const history = histories.get(parsed.data.id) ?? [];
|
|
1217
|
+
if (history.length <= MAX_JUNCTION_DAILY_ALIAS_REPAIR_REVISIONS) {
|
|
1218
|
+
history.push({ relativePath, record: parsed.data });
|
|
1219
|
+
histories.set(parsed.data.id, history);
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
for (const history of histories.values()) {
|
|
1225
|
+
history.sort((left, right) => eventSpineRevision(left.record) - eventSpineRevision(right.record));
|
|
1226
|
+
}
|
|
1227
|
+
return histories;
|
|
1228
|
+
}
|
|
1074
1229
|
function isSameObservationFacet(existing, incoming) {
|
|
1075
1230
|
return existing.kind === "observation" &&
|
|
1076
1231
|
incoming.kind === "observation" &&
|
|
@@ -1092,6 +1247,8 @@ const JUNCTION_SLEEP_STAGE_METRIC_FACETS = new Set([
|
|
|
1092
1247
|
]);
|
|
1093
1248
|
const JUNCTION_SLEEP_STAGE_SUMMARY_NORMALIZER_VERSION = "junction-sleep-stage-summary.v1";
|
|
1094
1249
|
const JUNCTION_SLEEP_STAGE_CYCLE_FALLBACK_NORMALIZER_VERSION = "junction-sleep-stage-cycle-fallback.v1";
|
|
1250
|
+
const JUNCTION_NO_ID_PROFILE_NORMALIZER_VERSION = "junction-no-id-profile.v1";
|
|
1251
|
+
const JUNCTION_STABLE_PROFILE_CREATED_AT_NORMALIZER_VERSION = "junction-stable-profile-created-at.v1";
|
|
1095
1252
|
function deviceDataOriginSourceMatches(existing, incoming) {
|
|
1096
1253
|
if (!existing || !incoming) {
|
|
1097
1254
|
return false;
|
|
@@ -1110,6 +1267,193 @@ function deviceDataOriginSourceMatches(existing, incoming) {
|
|
|
1110
1267
|
}
|
|
1111
1268
|
return compared;
|
|
1112
1269
|
}
|
|
1270
|
+
function junctionNoIdProfileScopeKey(externalRef, origin) {
|
|
1271
|
+
if (externalRef?.system !== "junction"
|
|
1272
|
+
|| !externalRef.resourceType.endsWith("-profile")
|
|
1273
|
+
|| typeof externalRef.facet !== "string"
|
|
1274
|
+
|| !/^profile-[a-f0-9]{16}$/u.test(externalRef.resourceId)
|
|
1275
|
+
|| origin?.sourceProviderSlug === undefined) {
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
return stableStringify({
|
|
1279
|
+
resourceType: externalRef.resourceType,
|
|
1280
|
+
facet: externalRef.facet,
|
|
1281
|
+
aggregatorProvider: origin.aggregatorProvider,
|
|
1282
|
+
sourceProviderSlug: origin.sourceProviderSlug,
|
|
1283
|
+
sourceType: origin.sourceType,
|
|
1284
|
+
sourceInstanceId: origin.sourceInstanceId,
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
function junctionNoIdProfileResourceId(origin, occurredAt) {
|
|
1288
|
+
return `profile-${createHash("sha256")
|
|
1289
|
+
.update(JSON.stringify([
|
|
1290
|
+
"profile",
|
|
1291
|
+
origin.sourceProviderSlug,
|
|
1292
|
+
origin.sourceType,
|
|
1293
|
+
origin.sourceInstanceId,
|
|
1294
|
+
occurredAt,
|
|
1295
|
+
]))
|
|
1296
|
+
.digest("hex")
|
|
1297
|
+
.slice(0, 16)}`;
|
|
1298
|
+
}
|
|
1299
|
+
function junctionNoIdProfileProviderBaselineRevision(match) {
|
|
1300
|
+
const externalRef = match.indexedExternalRef;
|
|
1301
|
+
const providerRecord = match.indexedRecord;
|
|
1302
|
+
const origin = providerRecord.dataOrigin;
|
|
1303
|
+
const isCurrentNoIdProfile = origin?.normalizerVersion
|
|
1304
|
+
=== JUNCTION_NO_ID_PROFILE_NORMALIZER_VERSION;
|
|
1305
|
+
const isLegacyNoIdProfile = origin?.normalizerVersion === "junction-normalizer.v1";
|
|
1306
|
+
const persistedRevision = isCurrentNoIdProfile
|
|
1307
|
+
? externalRef.version
|
|
1308
|
+
: externalRef.version ?? providerRecord.occurredAt;
|
|
1309
|
+
if ((!isCurrentNoIdProfile && !isLegacyNoIdProfile)
|
|
1310
|
+
|| !junctionNoIdProfileScopeKey(externalRef, origin)
|
|
1311
|
+
|| persistedRevision === undefined
|
|
1312
|
+
|| !isWritableIsoDateTime(persistedRevision)
|
|
1313
|
+
|| !isWritableIsoDateTime(providerRecord.occurredAt)
|
|
1314
|
+
|| origin?.observedAtRaw !== providerRecord.occurredAt
|
|
1315
|
+
|| (isLegacyNoIdProfile && providerRecord.occurredAt !== persistedRevision)) {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
const expectedResourceId = junctionNoIdProfileResourceId(origin, providerRecord.occurredAt);
|
|
1319
|
+
return externalRef.resourceId === expectedResourceId ? persistedRevision : null;
|
|
1320
|
+
}
|
|
1321
|
+
function junctionNoIdProfilePredecessorRevision(match) {
|
|
1322
|
+
return isDeletedEventSpineRecord(match.record)
|
|
1323
|
+
? null
|
|
1324
|
+
: junctionNoIdProfileProviderBaselineRevision(match);
|
|
1325
|
+
}
|
|
1326
|
+
function isJunctionNoIdProfilePredecessor(match) {
|
|
1327
|
+
return junctionNoIdProfileProviderBaselineRevision(match) !== null;
|
|
1328
|
+
}
|
|
1329
|
+
function indexJunctionNoIdProfilePredecessors(matches) {
|
|
1330
|
+
const byScope = new Map();
|
|
1331
|
+
for (const match of matches) {
|
|
1332
|
+
if (!isJunctionNoIdProfilePredecessor(match)) {
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
const scopeKey = junctionNoIdProfileScopeKey(match.indexedExternalRef, match.indexedRecord.dataOrigin);
|
|
1336
|
+
if (!scopeKey) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const scopedMatches = byScope.get(scopeKey) ?? [];
|
|
1340
|
+
scopedMatches.push(match);
|
|
1341
|
+
byScope.set(scopeKey, scopedMatches);
|
|
1342
|
+
}
|
|
1343
|
+
return byScope;
|
|
1344
|
+
}
|
|
1345
|
+
function isIncomingJunctionNoIdProfile(record) {
|
|
1346
|
+
return record.dataOrigin?.normalizerVersion === JUNCTION_NO_ID_PROFILE_NORMALIZER_VERSION
|
|
1347
|
+
&& junctionNoIdProfileScopeKey(record.externalRef, record.dataOrigin) !== null;
|
|
1348
|
+
}
|
|
1349
|
+
function isIncomingJunctionStableProfileCreatedAt(record) {
|
|
1350
|
+
return record.dataOrigin?.normalizerVersion === JUNCTION_STABLE_PROFILE_CREATED_AT_NORMALIZER_VERSION
|
|
1351
|
+
&& junctionNoIdProfileScopeKey(record.externalRef, record.dataOrigin) !== null;
|
|
1352
|
+
}
|
|
1353
|
+
function isExactJunctionProfileCreatedAtTimestampReplay(existing, incoming) {
|
|
1354
|
+
const existingRef = existing.externalRef;
|
|
1355
|
+
const incomingRef = incoming.externalRef;
|
|
1356
|
+
const existingOrigin = existing.dataOrigin;
|
|
1357
|
+
const incomingOrigin = incoming.dataOrigin;
|
|
1358
|
+
const existingRevision = existingRef?.version ?? existing.occurredAt;
|
|
1359
|
+
const existingScopeKey = junctionNoIdProfileScopeKey(existingRef, existingOrigin);
|
|
1360
|
+
const incomingScopeKey = junctionNoIdProfileScopeKey(incomingRef, incomingOrigin);
|
|
1361
|
+
if (!existingOrigin
|
|
1362
|
+
|| !incomingOrigin
|
|
1363
|
+
|| existingScopeKey === null
|
|
1364
|
+
|| existingScopeKey !== incomingScopeKey
|
|
1365
|
+
|| !existingRef
|
|
1366
|
+
|| !incomingRef?.version
|
|
1367
|
+
|| !isWritableIsoDateTime(existingRevision)
|
|
1368
|
+
|| !isWritableIsoDateTime(incomingRef.version)
|
|
1369
|
+
|| !isWritableIsoDateTime(existing.occurredAt)
|
|
1370
|
+
|| !isWritableIsoDateTime(existing.recordedAt)
|
|
1371
|
+
|| !isWritableIsoDateTime(incoming.occurredAt)
|
|
1372
|
+
|| !isWritableIsoDateTime(incoming.recordedAt)
|
|
1373
|
+
|| compareIsoTimestampsAscending(incomingRef.version, existingRevision) !== 0
|
|
1374
|
+
|| (!isDeletedEventSpineRecord(existing)
|
|
1375
|
+
&& compareIsoTimestampsAscending(existing.recordedAt, existing.occurredAt) !== 0)
|
|
1376
|
+
|| (isDeletedEventSpineRecord(existing)
|
|
1377
|
+
&& compareIsoTimestampsAscending(existing.recordedAt, existing.occurredAt) < 0)
|
|
1378
|
+
|| existing.dayKey !== toIsoTimestamp(existing.occurredAt, "existing.occurredAt").slice(0, 10)
|
|
1379
|
+
|| compareIsoTimestampsAscending(incoming.occurredAt, existing.occurredAt) >= 0
|
|
1380
|
+
|| compareIsoTimestampsAscending(incoming.recordedAt, incoming.occurredAt) !== 0
|
|
1381
|
+
|| incoming.dayKey !== toIsoTimestamp(incoming.occurredAt, "incoming.occurredAt").slice(0, 10)
|
|
1382
|
+
|| existingOrigin.observedAtRaw !== existing.occurredAt
|
|
1383
|
+
|| incomingOrigin.observedAtRaw !== incoming.occurredAt
|
|
1384
|
+
|| !deviceDataOriginSourceMatches(existingOrigin, incomingOrigin)
|
|
1385
|
+
|| existingOrigin.timeZoneOffsetMinutes !== incomingOrigin.timeZoneOffsetMinutes
|
|
1386
|
+
|| existingOrigin.timestampSemantics !== incomingOrigin.timestampSemantics
|
|
1387
|
+
|| existingOrigin.originConfidence !== incomingOrigin.originConfidence) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
return deviceEventContentKey({
|
|
1391
|
+
...existing,
|
|
1392
|
+
occurredAt: incoming.occurredAt,
|
|
1393
|
+
recordedAt: incoming.recordedAt,
|
|
1394
|
+
dayKey: incoming.dayKey,
|
|
1395
|
+
externalRef: incomingRef,
|
|
1396
|
+
dataOrigin: incomingOrigin,
|
|
1397
|
+
}) === deviceEventContentKey(incoming);
|
|
1398
|
+
}
|
|
1399
|
+
// One released Junction normalizer pinned stable-profile occurrence to the
|
|
1400
|
+
// mutable updated-at revision. The successor pins occurrence to created-at but
|
|
1401
|
+
// deliberately keeps updated-at as source ordering. Admit only that exact,
|
|
1402
|
+
// one-way timestamp-only replay so the generic equal-revision guard remains
|
|
1403
|
+
// fail-closed for health-data changes.
|
|
1404
|
+
function isJunctionStableProfileCreatedAtTimestampMigration(existing, incoming) {
|
|
1405
|
+
const existingRef = existing.externalRef;
|
|
1406
|
+
const incomingRef = incoming.externalRef;
|
|
1407
|
+
return isIncomingJunctionStableProfileCreatedAt(incoming)
|
|
1408
|
+
&& existing.dataOrigin?.normalizerVersion === "junction-normalizer.v1"
|
|
1409
|
+
&& existingRef?.version !== undefined
|
|
1410
|
+
&& incomingRef !== undefined
|
|
1411
|
+
&& eventExternalRefKey(existingRef) === eventExternalRefKey(incomingRef)
|
|
1412
|
+
&& isExactJunctionProfileCreatedAtTimestampReplay(existing, incoming);
|
|
1413
|
+
}
|
|
1414
|
+
// Revision-2 admission is account-wide, so a released no-ID profile can be
|
|
1415
|
+
// replayed after created_at becomes available while updated_at (the provider
|
|
1416
|
+
// revision) is unchanged. Its timestamp-derived resource identity changes,
|
|
1417
|
+
// but the replay must not split a member edit or bypass a deletion. Retain the
|
|
1418
|
+
// old identity only for this exact timestamp-only transition; changed health
|
|
1419
|
+
// data at the same source revision remains a conflict.
|
|
1420
|
+
function isJunctionNoIdProfileEqualRevisionTimestampReplay(existing, incoming) {
|
|
1421
|
+
const existingRef = existing.externalRef;
|
|
1422
|
+
const incomingRef = incoming.externalRef;
|
|
1423
|
+
const existingOrigin = existing.dataOrigin;
|
|
1424
|
+
const incomingOrigin = incoming.dataOrigin;
|
|
1425
|
+
if (!isIncomingJunctionNoIdProfile(incoming)
|
|
1426
|
+
|| !existingRef
|
|
1427
|
+
|| !incomingRef
|
|
1428
|
+
|| !existingOrigin
|
|
1429
|
+
|| !incomingOrigin
|
|
1430
|
+
|| eventExternalRefKey(existingRef) === eventExternalRefKey(incomingRef)
|
|
1431
|
+
|| junctionNoIdProfileResourceId(existingOrigin, existing.occurredAt)
|
|
1432
|
+
!== existingRef.resourceId
|
|
1433
|
+
|| junctionNoIdProfileResourceId(incomingOrigin, incoming.occurredAt)
|
|
1434
|
+
!== incomingRef.resourceId
|
|
1435
|
+
|| !(existingOrigin.normalizerVersion === "junction-normalizer.v1"
|
|
1436
|
+
|| existingOrigin.normalizerVersion === JUNCTION_NO_ID_PROFILE_NORMALIZER_VERSION)) {
|
|
1437
|
+
return false;
|
|
1438
|
+
}
|
|
1439
|
+
return isExactJunctionProfileCreatedAtTimestampReplay(existing, incoming);
|
|
1440
|
+
}
|
|
1441
|
+
function isMemberDeletedJunctionStableProfile(existing, incoming) {
|
|
1442
|
+
const existingRef = existing.externalRef;
|
|
1443
|
+
const incomingRef = incoming.externalRef;
|
|
1444
|
+
if (!existingRef || !incomingRef) {
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
return isDeletedEventSpineRecord(existing)
|
|
1448
|
+
&& isIncomingJunctionStableProfileCreatedAt(incoming)
|
|
1449
|
+
&& junctionNoIdProfileScopeKey(existingRef, existing.dataOrigin) !== null
|
|
1450
|
+
&& eventExternalRefKey(existingRef) === eventExternalRefKey(incomingRef)
|
|
1451
|
+
&& deviceDataOriginSourceMatches(existing.dataOrigin, incoming.dataOrigin)
|
|
1452
|
+
&& existingRef?.version !== undefined
|
|
1453
|
+
&& isWritableIsoDateTime(existingRef.version)
|
|
1454
|
+
&& isWritableIsoDateTime(existing.recordedAt)
|
|
1455
|
+
&& compareIsoTimestampsAscending(existing.recordedAt, existingRef.version) > 0;
|
|
1456
|
+
}
|
|
1113
1457
|
function isDateLikeWhoopBodyMeasurementResourceId(resourceId) {
|
|
1114
1458
|
return /^\d{4}-\d{2}-\d{2}$/u.test(resourceId) ||
|
|
1115
1459
|
/^date:\d{4}-\d{2}-\d{2}$/u.test(resourceId);
|
|
@@ -1174,6 +1518,95 @@ function shouldKeepExistingJunctionSleepStageSummaryObservation(existing, incomi
|
|
|
1174
1518
|
isJunctionSleepStageCycleFallbackObservation(incoming);
|
|
1175
1519
|
}
|
|
1176
1520
|
const JUNCTION_COMPANION_HEALTH_METADATA_SOURCE_TYPE = "companion-whoop-metadata-unverified";
|
|
1521
|
+
const JUNCTION_SPARSE_FLOATING_FALLBACK_NORMALIZER_VERSION = "junction-sparse-timeseries.floating-fallback.v2";
|
|
1522
|
+
function resolveJunctionFloatingFallbackTime(existing, incoming) {
|
|
1523
|
+
if (incoming.externalRef?.system !== "junction"
|
|
1524
|
+
|| incoming.dataOrigin?.normalizerVersion
|
|
1525
|
+
!== JUNCTION_SPARSE_FLOATING_FALLBACK_NORMALIZER_VERSION
|
|
1526
|
+
|| incoming.dataOrigin.timestampSemantics !== "floating") {
|
|
1527
|
+
return incoming;
|
|
1528
|
+
}
|
|
1529
|
+
if (!incoming.timeZone) {
|
|
1530
|
+
return incoming;
|
|
1531
|
+
}
|
|
1532
|
+
// Junction documents Libre's +00:00 values as wall clocks, not instants.
|
|
1533
|
+
// The importer carries those raw wall values through a schema-valid transient
|
|
1534
|
+
// event. Resolve them only after stable external identity lookup, so a matched
|
|
1535
|
+
// row uses its event spine's accepted zone while a new row uses the incoming
|
|
1536
|
+
// fallback zone. Each endpoint resolves independently across DST transitions.
|
|
1537
|
+
const acceptedExisting = existing?.externalRef?.system === "junction"
|
|
1538
|
+
&& existing.dataOrigin?.timestampSemantics === "floating"
|
|
1539
|
+
&& deviceDataOriginSourceMatches(existing.dataOrigin, incoming.dataOrigin)
|
|
1540
|
+
? existing
|
|
1541
|
+
: undefined;
|
|
1542
|
+
const targetTimeZone = acceptedExisting?.timeZone ?? incoming.timeZone;
|
|
1543
|
+
const startRaw = incoming.dataOrigin.observedAtRaw;
|
|
1544
|
+
if (typeof startRaw !== "string") {
|
|
1545
|
+
return incoming;
|
|
1546
|
+
}
|
|
1547
|
+
const resolvedOccurrence = resolveFloatingIsoTimestampInTimeZone(startRaw, targetTimeZone);
|
|
1548
|
+
const resolvedStart = incoming.kind === "intervention_session"
|
|
1549
|
+
&& typeof incoming.fields?.["start-at"] === "string"
|
|
1550
|
+
? resolveFloatingIsoTimestampInTimeZone(incoming.fields["start-at"], targetTimeZone)
|
|
1551
|
+
: undefined;
|
|
1552
|
+
const resolvedStartAt = resolvedStart?.timestamp;
|
|
1553
|
+
const resolvedEndAt = incoming.kind === "intervention_session"
|
|
1554
|
+
&& typeof incoming.fields?.["end-at"] === "string"
|
|
1555
|
+
? resolveFloatingIsoTimestampInTimeZone(incoming.fields["end-at"], targetTimeZone)?.timestamp
|
|
1556
|
+
: undefined;
|
|
1557
|
+
if (!resolvedOccurrence
|
|
1558
|
+
|| (incoming.kind === "intervention_session"
|
|
1559
|
+
&& (!resolvedStartAt
|
|
1560
|
+
|| !resolvedEndAt
|
|
1561
|
+
|| Date.parse(resolvedEndAt) < Date.parse(resolvedStartAt)))) {
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
const resolvedInterventionFields = incoming.kind === "intervention_session"
|
|
1565
|
+
&& incoming.fields
|
|
1566
|
+
? {
|
|
1567
|
+
...incoming.fields,
|
|
1568
|
+
...(resolvedStartAt ? { "start-at": resolvedStartAt } : {}),
|
|
1569
|
+
...(resolvedEndAt ? { "end-at": resolvedEndAt } : {}),
|
|
1570
|
+
}
|
|
1571
|
+
: undefined;
|
|
1572
|
+
const canonicalOccurrence = incoming.kind === "intervention_session"
|
|
1573
|
+
&& resolvedStartAt
|
|
1574
|
+
? resolvedStartAt
|
|
1575
|
+
: resolvedOccurrence.timestamp;
|
|
1576
|
+
const canonicalDayKey = incoming.kind === "intervention_session"
|
|
1577
|
+
&& resolvedStart
|
|
1578
|
+
? resolvedStart.dayKey
|
|
1579
|
+
: resolvedOccurrence.dayKey;
|
|
1580
|
+
return {
|
|
1581
|
+
...incoming,
|
|
1582
|
+
occurredAt: canonicalOccurrence,
|
|
1583
|
+
recordedAt: acceptedExisting?.recordedAt ?? incoming.recordedAt,
|
|
1584
|
+
dayKey: canonicalDayKey,
|
|
1585
|
+
timeZone: targetTimeZone,
|
|
1586
|
+
...(incoming.kind === "intervention_session" && resolvedInterventionFields
|
|
1587
|
+
? { fields: resolvedInterventionFields }
|
|
1588
|
+
: {}),
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function resolveJunctionFloatingFallbackEntries(entries, context) {
|
|
1592
|
+
return entries.flatMap((entry) => {
|
|
1593
|
+
if (entry.record.externalRef?.system !== "junction"
|
|
1594
|
+
|| entry.record.dataOrigin?.normalizerVersion
|
|
1595
|
+
!== JUNCTION_SPARSE_FLOATING_FALLBACK_NORMALIZER_VERSION) {
|
|
1596
|
+
return [entry];
|
|
1597
|
+
}
|
|
1598
|
+
const resolved = resolveDeviceEventIdentity(entry, context, { strict: true });
|
|
1599
|
+
const indexedProviderMatch = resolved?.matchedEntries.find((match) => match.indexedMatch.record.id === resolved.latest?.id)?.indexedMatch ?? resolved?.matchedEntries[0]?.indexedMatch;
|
|
1600
|
+
const canonicalRecord = resolveJunctionFloatingFallbackTime(indexedProviderMatch?.indexedRecord, entry.record);
|
|
1601
|
+
return canonicalRecord
|
|
1602
|
+
? [{
|
|
1603
|
+
...entry,
|
|
1604
|
+
relativePath: toEventLedgerFile(canonicalRecord.occurredAt),
|
|
1605
|
+
record: canonicalRecord,
|
|
1606
|
+
}]
|
|
1607
|
+
: [];
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1177
1610
|
function parseJunctionCompanionSyncVersion(version) {
|
|
1178
1611
|
if (!version || !/^(?:0|[1-9]\d*)$/u.test(version)) {
|
|
1179
1612
|
return undefined;
|
|
@@ -1275,12 +1708,126 @@ function toIndexedExternalRefMatch(record, externalRef, relativePath = "") {
|
|
|
1275
1708
|
record,
|
|
1276
1709
|
};
|
|
1277
1710
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1711
|
+
const deviceBatchImportSessionState = new WeakMap();
|
|
1712
|
+
export function createDeviceBatchImportSession() {
|
|
1713
|
+
const session = Object.freeze({
|
|
1714
|
+
kind: "device_batch_import_session",
|
|
1715
|
+
});
|
|
1716
|
+
deviceBatchImportSessionState.set(session, new Map());
|
|
1717
|
+
return session;
|
|
1718
|
+
}
|
|
1719
|
+
function buildDeviceEventIdentityDependency(plan, additionalEventIds = []) {
|
|
1720
|
+
const externalRefs = plan.preparedEvents.flatMap((entry) => [
|
|
1721
|
+
...(entry.record.externalRef ? [entry.record.externalRef] : []),
|
|
1722
|
+
...entry.legacyExternalRefs,
|
|
1723
|
+
]);
|
|
1724
|
+
return {
|
|
1725
|
+
authoritativeEventSets: plan.authoritativeEventSets,
|
|
1726
|
+
eventIds: new Set([
|
|
1727
|
+
...plan.preparedEvents.map((entry) => entry.record.id),
|
|
1728
|
+
...additionalEventIds,
|
|
1729
|
+
]),
|
|
1730
|
+
externalRefs,
|
|
1731
|
+
junctionNoIdProfileScopes: new Set(plan.preparedEvents.flatMap((entry) => {
|
|
1732
|
+
const externalRef = entry.record.externalRef;
|
|
1733
|
+
const scope = externalRef
|
|
1734
|
+
? junctionNoIdProfileScopeKey(externalRef, entry.record.dataOrigin)
|
|
1735
|
+
: null;
|
|
1736
|
+
return scope ? [scope] : [];
|
|
1737
|
+
})),
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function eventRefBelongsToAuthoritativeSet(externalRef, set) {
|
|
1741
|
+
if (externalRef.system !== set.system
|
|
1742
|
+
|| externalRef.resourceType !== set.resourceType
|
|
1743
|
+
|| externalRef.resourceId !== set.resourceId
|
|
1744
|
+
|| externalRef.facet === undefined) {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
return set.facetPrefixes.some((prefix) => externalRef.facet === prefix || externalRef.facet?.startsWith(`${prefix}-`));
|
|
1748
|
+
}
|
|
1749
|
+
function authoritativeEventSetsOverlap(left, right) {
|
|
1750
|
+
if (left.system !== right.system
|
|
1751
|
+
|| left.resourceType !== right.resourceType
|
|
1752
|
+
|| left.resourceId !== right.resourceId) {
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
return left.facetPrefixes.some((leftPrefix) => right.facetPrefixes.some((rightPrefix) => leftPrefix === rightPrefix
|
|
1756
|
+
|| leftPrefix.startsWith(`${rightPrefix}-`)
|
|
1757
|
+
|| rightPrefix.startsWith(`${leftPrefix}-`)));
|
|
1758
|
+
}
|
|
1759
|
+
function deviceEventIdentityDependenciesOverlap(left, right) {
|
|
1760
|
+
if ([...left.eventIds].some((eventId) => right.eventIds.has(eventId))) {
|
|
1761
|
+
return true;
|
|
1762
|
+
}
|
|
1763
|
+
const rightRefKeys = new Set(right.externalRefs.map(eventExternalRefKey));
|
|
1764
|
+
if (left.externalRefs.some((externalRef) => rightRefKeys.has(eventExternalRefKey(externalRef)))) {
|
|
1765
|
+
return true;
|
|
1766
|
+
}
|
|
1767
|
+
if ([...left.junctionNoIdProfileScopes].some((scope) => right.junctionNoIdProfileScopes.has(scope))) {
|
|
1768
|
+
return true;
|
|
1769
|
+
}
|
|
1770
|
+
if (left.externalRefs.some((externalRef) => right.authoritativeEventSets.some((set) => eventRefBelongsToAuthoritativeSet(externalRef, set)))) {
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
if (right.externalRefs.some((externalRef) => left.authoritativeEventSets.some((set) => eventRefBelongsToAuthoritativeSet(externalRef, set)))) {
|
|
1774
|
+
return true;
|
|
1775
|
+
}
|
|
1776
|
+
return left.authoritativeEventSets.some((leftSet) => right.authoritativeEventSets.some((rightSet) => authoritativeEventSetsOverlap(leftSet, rightSet)));
|
|
1777
|
+
}
|
|
1778
|
+
function readDeviceBatchImportSessionVaultState(session, vaultRoot) {
|
|
1779
|
+
return session ? deviceBatchImportSessionState.get(session)?.get(vaultRoot) : undefined;
|
|
1780
|
+
}
|
|
1781
|
+
function replaceDeviceBatchImportSessionVaultState(session, vaultRoot, state) {
|
|
1782
|
+
if (!session) {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
deviceBatchImportSessionState.get(session)?.set(vaultRoot, state);
|
|
1786
|
+
}
|
|
1787
|
+
function clearDeviceBatchImportSessionVaultState(session, vaultRoot) {
|
|
1788
|
+
if (!session) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
deviceBatchImportSessionState.get(session)?.delete(vaultRoot);
|
|
1792
|
+
}
|
|
1793
|
+
function emitDeviceBatchImportTiming(observer, timing) {
|
|
1794
|
+
try {
|
|
1795
|
+
observer?.({ ...timing });
|
|
1796
|
+
}
|
|
1797
|
+
catch {
|
|
1798
|
+
// Diagnostics must not turn an already-committed canonical write into an
|
|
1799
|
+
// apparent import failure.
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
async function buildDeviceEventLedgerFingerprint(vaultRoot) {
|
|
1803
|
+
const shardPaths = await walkVaultFiles(vaultRoot, VAULT_LAYOUT.eventLedgerDirectory, {
|
|
1804
|
+
extension: ".jsonl",
|
|
1283
1805
|
});
|
|
1806
|
+
const metadata = await Promise.all(shardPaths.map(async (relativePath) => {
|
|
1807
|
+
const shard = await stat(resolveVaultPath(vaultRoot, relativePath).absolutePath);
|
|
1808
|
+
return [
|
|
1809
|
+
relativePath,
|
|
1810
|
+
shard.dev,
|
|
1811
|
+
shard.ino,
|
|
1812
|
+
shard.size,
|
|
1813
|
+
shard.mtimeMs,
|
|
1814
|
+
shard.ctimeMs,
|
|
1815
|
+
].join("\0");
|
|
1816
|
+
}));
|
|
1817
|
+
return createHash("sha256").update(metadata.join("\n")).digest("hex");
|
|
1818
|
+
}
|
|
1819
|
+
async function tryBuildDeviceEventLedgerFingerprint(vaultRoot) {
|
|
1820
|
+
try {
|
|
1821
|
+
return await buildDeviceEventLedgerFingerprint(vaultRoot);
|
|
1822
|
+
}
|
|
1823
|
+
catch {
|
|
1824
|
+
// A failed cache fence disables reuse. The canonical import path remains
|
|
1825
|
+
// authoritative and will surface any real ledger read failure itself.
|
|
1826
|
+
return undefined;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function userAuthoredEventStateKey(record) {
|
|
1830
|
+
return eventContentKey(record, { includeProviderAttribution: false });
|
|
1284
1831
|
}
|
|
1285
1832
|
function hasHistoricalExternalRefUserAuthoredChanges(match) {
|
|
1286
1833
|
return eventSpineRevision(match.record) > eventSpineRevision(match.indexedRecord) &&
|
|
@@ -1288,40 +1835,99 @@ function hasHistoricalExternalRefUserAuthoredChanges(match) {
|
|
|
1288
1835
|
}
|
|
1289
1836
|
function buildLegacyExternalRefReservations(entries, index) {
|
|
1290
1837
|
const reservations = new Map();
|
|
1838
|
+
const reserve = (entry, refKey, indexedMatch) => {
|
|
1839
|
+
const existing = reservations.get(refKey);
|
|
1840
|
+
if (existing && existing.entry !== entry) {
|
|
1841
|
+
const externalRef = indexedMatch.indexedExternalRef;
|
|
1842
|
+
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", `Event legacy externalRef "${externalRef.system}/${externalRef.resourceType}/` +
|
|
1843
|
+
`${externalRef.resourceId}${externalRef.facet ? `#${externalRef.facet}` : ""}" ` +
|
|
1844
|
+
"matched multiple incoming device events; ambiguous legacy cleanup must be repaired explicitly.");
|
|
1845
|
+
}
|
|
1846
|
+
reservations.set(refKey, { entry, indexedMatch });
|
|
1847
|
+
};
|
|
1291
1848
|
for (const entry of entries) {
|
|
1292
1849
|
const externalRef = entry.record.externalRef;
|
|
1293
1850
|
if (!externalRef) {
|
|
1294
1851
|
continue;
|
|
1295
1852
|
}
|
|
1296
1853
|
const primaryRefKey = eventExternalRefKey(externalRef);
|
|
1854
|
+
const dailyAliasOwnerSplit = resolveJunctionDailyAggregateAliasOwnerSplit(entry, index);
|
|
1297
1855
|
for (const legacyExternalRef of entry.legacyExternalRefs) {
|
|
1298
1856
|
const legacyRefKey = eventExternalRefKey(legacyExternalRef);
|
|
1299
1857
|
if (legacyRefKey === primaryRefKey) {
|
|
1300
1858
|
continue;
|
|
1301
1859
|
}
|
|
1302
|
-
const
|
|
1303
|
-
|
|
1860
|
+
const persistedAliasMatch = dailyAliasOwnerSplit?.legacyRefKey === legacyRefKey
|
|
1861
|
+
? dailyAliasOwnerSplit.legacyOwner
|
|
1862
|
+
: undefined;
|
|
1863
|
+
const indexedMatch = persistedAliasMatch ?? index.latestByRefKey.get(legacyRefKey);
|
|
1864
|
+
if (!indexedMatch
|
|
1865
|
+
|| (!persistedAliasMatch
|
|
1866
|
+
&& !isCompatibleLegacyExternalRefMatch(indexedMatch, entry.record, legacyExternalRef))) {
|
|
1304
1867
|
continue;
|
|
1305
1868
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1869
|
+
reserve(entry, legacyRefKey, indexedMatch);
|
|
1870
|
+
}
|
|
1871
|
+
if (!isIncomingJunctionNoIdProfile(entry.record)) {
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
const scopeKey = junctionNoIdProfileScopeKey(externalRef, entry.record.dataOrigin);
|
|
1875
|
+
const incomingRevision = externalRef.version;
|
|
1876
|
+
if (!scopeKey || !incomingRevision || !isWritableIsoDateTime(incomingRevision)) {
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
const scopedMatches = (index.junctionNoIdProfilePredecessorsByScope.get(scopeKey) ?? [])
|
|
1880
|
+
.filter((match) => eventExternalRefKey(match.indexedExternalRef) !== primaryRefKey);
|
|
1881
|
+
const equalRevisionMatches = scopedMatches.filter((match) => {
|
|
1882
|
+
const predecessorRevision = junctionNoIdProfileProviderBaselineRevision(match);
|
|
1883
|
+
return predecessorRevision !== null
|
|
1884
|
+
&& compareIsoTimestampsAscending(incomingRevision, predecessorRevision) === 0;
|
|
1885
|
+
});
|
|
1886
|
+
if (equalRevisionMatches.length > 1) {
|
|
1887
|
+
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", `Junction profile externalRef "${externalRef.system}/${externalRef.resourceType}/` +
|
|
1888
|
+
`${externalRef.resourceId}${externalRef.facet ? `#${externalRef.facet}` : ""}" ` +
|
|
1889
|
+
"matched multiple persisted identities at the same provider revision; nothing was imported.");
|
|
1890
|
+
}
|
|
1891
|
+
const equalRevisionMatch = equalRevisionMatches[0];
|
|
1892
|
+
if (equalRevisionMatch) {
|
|
1893
|
+
if (!isJunctionNoIdProfileEqualRevisionTimestampReplay(equalRevisionMatch.indexedRecord, entry.record)) {
|
|
1894
|
+
throw new VaultError("EVENT_SOURCE_REVISION_CONFLICT", `Junction profile externalRef "${externalRef.system}/${externalRef.resourceType}/` +
|
|
1895
|
+
`${externalRef.resourceId}${externalRef.facet ? `#${externalRef.facet}` : ""}" ` +
|
|
1896
|
+
`has conflicting content for source revision "${incomingRevision}"; nothing was imported.`);
|
|
1311
1897
|
}
|
|
1312
|
-
|
|
1898
|
+
reserve(entry, eventExternalRefKey(equalRevisionMatch.indexedExternalRef), equalRevisionMatch);
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
const predecessorMatches = scopedMatches.filter((match) => {
|
|
1902
|
+
const predecessorRevision = junctionNoIdProfilePredecessorRevision(match);
|
|
1903
|
+
return predecessorRevision !== null
|
|
1904
|
+
&& compareIsoTimestampsAscending(incomingRevision, predecessorRevision) > 0;
|
|
1905
|
+
});
|
|
1906
|
+
if (predecessorMatches.length > 1) {
|
|
1907
|
+
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", `Junction profile externalRef "${externalRef.system}/${externalRef.resourceType}/` +
|
|
1908
|
+
`${externalRef.resourceId}${externalRef.facet ? `#${externalRef.facet}` : ""}" ` +
|
|
1909
|
+
"matched multiple persisted predecessor identities; nothing was imported.");
|
|
1910
|
+
}
|
|
1911
|
+
const predecessor = predecessorMatches[0];
|
|
1912
|
+
if (predecessor) {
|
|
1913
|
+
reserve(entry, eventExternalRefKey(predecessor.indexedExternalRef), predecessor);
|
|
1313
1914
|
}
|
|
1314
1915
|
}
|
|
1315
1916
|
return reservations;
|
|
1316
1917
|
}
|
|
1317
|
-
async function buildDeviceEventIdentityContext(vaultRoot, entries) {
|
|
1318
|
-
if (entries.length === 0) {
|
|
1918
|
+
async function buildDeviceEventIdentityContext(vaultRoot, entries, authoritativeEventSets = []) {
|
|
1919
|
+
if (entries.length === 0 && authoritativeEventSets.length === 0) {
|
|
1319
1920
|
return {
|
|
1320
1921
|
index: {
|
|
1922
|
+
aliasRepairContaminatedEventIds: new Set(),
|
|
1923
|
+
aliasRepairContaminatedRefKeys: new Set(),
|
|
1924
|
+
aliasRepairHistoryById: new Map(),
|
|
1321
1925
|
deviceOwnerRevisionsByRefKeyAndFingerprint: new Map(),
|
|
1926
|
+
junctionNoIdProfilePredecessorsByScope: new Map(),
|
|
1322
1927
|
junctionSparseDayHistoryById: new Map(),
|
|
1323
1928
|
latestByRefKey: new Map(),
|
|
1324
1929
|
latestById: new Map(),
|
|
1930
|
+
liveOwnerIdsByRefKey: new Map(),
|
|
1325
1931
|
maxRevisionById: new Map(),
|
|
1326
1932
|
revisionsById: new Map(),
|
|
1327
1933
|
},
|
|
@@ -1332,30 +1938,55 @@ async function buildDeviceEventIdentityContext(vaultRoot, entries) {
|
|
|
1332
1938
|
extension: ".jsonl",
|
|
1333
1939
|
});
|
|
1334
1940
|
const index = await indexLatestEventsByExternalRef(vaultRoot, shardPaths);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1941
|
+
const context = { index, legacyReservations: new Map() };
|
|
1942
|
+
// A legitimate primary spine may advance before the duplicate is repaired.
|
|
1943
|
+
// Load only structural candidates first so reservations can use the bounded
|
|
1944
|
+
// common-initial-history proof instead of latest-record equality.
|
|
1945
|
+
const aliasRepairOwnerIds = new Set();
|
|
1946
|
+
for (const entry of entries) {
|
|
1947
|
+
const ownerSplit = resolveStructuralJunctionDailyAggregateAliasOwnerSplit(entry, index);
|
|
1948
|
+
if (!ownerSplit) {
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
assertJunctionDailyAggregateAliasHistoryIsUncontaminated(entry, context);
|
|
1952
|
+
aliasRepairOwnerIds.add(ownerSplit.primaryOwner.record.id);
|
|
1953
|
+
aliasRepairOwnerIds.add(ownerSplit.legacyOwner.record.id);
|
|
1954
|
+
}
|
|
1955
|
+
if (aliasRepairOwnerIds.size > 0) {
|
|
1956
|
+
index.aliasRepairHistoryById = await loadBoundedAliasRepairHistories(vaultRoot, shardPaths, aliasRepairOwnerIds);
|
|
1957
|
+
}
|
|
1958
|
+
context.legacyReservations = buildLegacyExternalRefReservations(entries, index);
|
|
1959
|
+
return context;
|
|
1339
1960
|
}
|
|
1340
1961
|
function cloneDeviceEventIdentityContext(context) {
|
|
1341
1962
|
return {
|
|
1342
1963
|
index: {
|
|
1964
|
+
aliasRepairContaminatedEventIds: new Set(context.index.aliasRepairContaminatedEventIds),
|
|
1965
|
+
aliasRepairContaminatedRefKeys: new Set(context.index.aliasRepairContaminatedRefKeys),
|
|
1966
|
+
aliasRepairHistoryById: new Map([...context.index.aliasRepairHistoryById].map(([id, history]) => [id, [...history]])),
|
|
1343
1967
|
deviceOwnerRevisionsByRefKeyAndFingerprint: context.index.deviceOwnerRevisionsByRefKeyAndFingerprint,
|
|
1968
|
+
junctionNoIdProfilePredecessorsByScope: context.index.junctionNoIdProfilePredecessorsByScope,
|
|
1344
1969
|
junctionSparseDayHistoryById: context.index.junctionSparseDayHistoryById,
|
|
1345
1970
|
latestByRefKey: new Map(context.index.latestByRefKey),
|
|
1346
1971
|
latestById: new Map(context.index.latestById),
|
|
1972
|
+
liveOwnerIdsByRefKey: new Map([...context.index.liveOwnerIdsByRefKey].map(([key, ids]) => [key, new Set(ids)])),
|
|
1347
1973
|
maxRevisionById: new Map(context.index.maxRevisionById),
|
|
1348
|
-
revisionsById: new Map(context.index.revisionsById),
|
|
1974
|
+
revisionsById: new Map([...context.index.revisionsById].map(([id, revisions]) => [id, new Set(revisions)])),
|
|
1349
1975
|
},
|
|
1350
1976
|
legacyReservations: new Map(context.legacyReservations),
|
|
1351
1977
|
};
|
|
1352
1978
|
}
|
|
1353
1979
|
function buildEmptyDeviceEventIdentityContext(entries) {
|
|
1354
1980
|
const index = {
|
|
1981
|
+
aliasRepairContaminatedEventIds: new Set(),
|
|
1982
|
+
aliasRepairContaminatedRefKeys: new Set(),
|
|
1983
|
+
aliasRepairHistoryById: new Map(),
|
|
1355
1984
|
deviceOwnerRevisionsByRefKeyAndFingerprint: new Map(),
|
|
1985
|
+
junctionNoIdProfilePredecessorsByScope: new Map(),
|
|
1356
1986
|
junctionSparseDayHistoryById: new Map(),
|
|
1357
1987
|
latestByRefKey: new Map(),
|
|
1358
1988
|
latestById: new Map(),
|
|
1989
|
+
liveOwnerIdsByRefKey: new Map(),
|
|
1359
1990
|
maxRevisionById: new Map(),
|
|
1360
1991
|
revisionsById: new Map(),
|
|
1361
1992
|
};
|
|
@@ -1372,6 +2003,470 @@ function eventSpineRevisionsAreComplete(index, eventId) {
|
|
|
1372
2003
|
&& revisions !== undefined
|
|
1373
2004
|
&& revisions.size === maxRevision;
|
|
1374
2005
|
}
|
|
2006
|
+
function buildDeviceEventAliasRepairContext(plan) {
|
|
2007
|
+
return {
|
|
2008
|
+
evidenceByRole: new Map(plan.preparedEvidenceParts.map((part) => [part.role, part])),
|
|
2009
|
+
evidenceRolesByPreparedRecordId: plan.evidenceRolesByPreparedRecordId,
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function isLooseRecordValue(value) {
|
|
2013
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2014
|
+
}
|
|
2015
|
+
function readRejectedEventExternalRefKey(value) {
|
|
2016
|
+
if (!isLooseRecordValue(value)) {
|
|
2017
|
+
return undefined;
|
|
2018
|
+
}
|
|
2019
|
+
const parsed = safeParseContract(externalRefSchema, {
|
|
2020
|
+
system: value.system,
|
|
2021
|
+
resourceType: value.resourceType,
|
|
2022
|
+
resourceId: value.resourceId,
|
|
2023
|
+
...(value.facet === undefined ? {} : { facet: value.facet }),
|
|
2024
|
+
...(value.version === undefined ? {} : { version: value.version }),
|
|
2025
|
+
});
|
|
2026
|
+
return parsed.success ? eventExternalRefKey(parsed.data) : undefined;
|
|
2027
|
+
}
|
|
2028
|
+
function junctionDailyAggregateAliasRefShape(externalRef) {
|
|
2029
|
+
return stableStringify({
|
|
2030
|
+
facet: externalRef.facet ?? null,
|
|
2031
|
+
resourceType: externalRef.resourceType,
|
|
2032
|
+
system: externalRef.system,
|
|
2033
|
+
version: externalRef.version ?? null,
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
function junctionDailyAggregateAliasOriginKey(origin) {
|
|
2037
|
+
if (!origin) {
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
const { observedAtRaw: _observedAtRaw, ...stableOrigin } = origin;
|
|
2041
|
+
return stableStringify(stableOrigin);
|
|
2042
|
+
}
|
|
2043
|
+
function junctionDailyAggregateAliasHistoricalOriginMatches(historical, current) {
|
|
2044
|
+
if (!historical) {
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
if (junctionDailyAggregateAliasOriginKey(historical)
|
|
2048
|
+
=== junctionDailyAggregateAliasOriginKey(current)) {
|
|
2049
|
+
return true;
|
|
2050
|
+
}
|
|
2051
|
+
if (historical.timeZoneOffsetMinutes !== undefined) {
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
const { observedAtRaw: _historicalObservedAtRaw, timeZoneOffsetMinutes: _historicalOffset, ...stableHistorical } = historical;
|
|
2055
|
+
const { observedAtRaw: _currentObservedAtRaw, timeZoneOffsetMinutes: _currentOffset, ...stableCurrent } = current;
|
|
2056
|
+
return stableStringify(stableHistorical) === stableStringify(stableCurrent);
|
|
2057
|
+
}
|
|
2058
|
+
function junctionDailyAggregateProviderStateKey(record) {
|
|
2059
|
+
const { dataOrigin: _dataOrigin, dayKey: _dayKey, externalRef: _externalRef, id: _id, lifecycle: _lifecycle, links: _links, note: _note, recordedAt: _recordedAt, source: _source, tags: _tags, ...providerState } = record;
|
|
2060
|
+
return stableStringify(providerState);
|
|
2061
|
+
}
|
|
2062
|
+
function isJunctionDailyAggregateAliasCandidate(entry) {
|
|
2063
|
+
const externalRef = entry.record.externalRef;
|
|
2064
|
+
const legacyExternalRef = entry.legacyExternalRefs[0];
|
|
2065
|
+
return externalRef !== undefined
|
|
2066
|
+
&& legacyExternalRef !== undefined
|
|
2067
|
+
&& entry.legacyExternalRefs.length === 1
|
|
2068
|
+
&& entry.record.kind === "observation"
|
|
2069
|
+
&& entry.record.source === "device"
|
|
2070
|
+
&& entry.record.dataOrigin?.aggregatorProvider === "junction"
|
|
2071
|
+
&& entry.record.dataOrigin.normalizerVersion === "junction-normalizer.v1"
|
|
2072
|
+
&& externalRef.system === "junction"
|
|
2073
|
+
&& externalRef.version === undefined
|
|
2074
|
+
&& legacyExternalRef.version === undefined
|
|
2075
|
+
&& eventExternalRefKey(externalRef) !== eventExternalRefKey(legacyExternalRef)
|
|
2076
|
+
&& junctionDailyAggregateAliasRefShape(externalRef)
|
|
2077
|
+
=== junctionDailyAggregateAliasRefShape(legacyExternalRef)
|
|
2078
|
+
&& entry.record.note === undefined
|
|
2079
|
+
&& entry.record.tags === undefined
|
|
2080
|
+
&& entry.record.links === undefined
|
|
2081
|
+
&& entry.record.rawRefs === undefined
|
|
2082
|
+
&& entry.record.evidence === undefined
|
|
2083
|
+
&& entry.record.attachments === undefined;
|
|
2084
|
+
}
|
|
2085
|
+
function resolveStructuralJunctionDailyAggregateAliasOwnerSplit(entry, index) {
|
|
2086
|
+
const externalRef = entry.record.externalRef;
|
|
2087
|
+
const legacyExternalRef = entry.legacyExternalRefs[0];
|
|
2088
|
+
if (!externalRef
|
|
2089
|
+
|| !legacyExternalRef
|
|
2090
|
+
|| !isJunctionDailyAggregateAliasCandidate(entry)) {
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
const primaryRefKey = eventExternalRefKey(externalRef);
|
|
2094
|
+
const legacyRefKey = eventExternalRefKey(legacyExternalRef);
|
|
2095
|
+
const primaryOwnerIds = index.liveOwnerIdsByRefKey.get(primaryRefKey)
|
|
2096
|
+
?? new Set();
|
|
2097
|
+
const legacyOwnerIds = index.liveOwnerIdsByRefKey.get(legacyRefKey)
|
|
2098
|
+
?? new Set();
|
|
2099
|
+
if (primaryOwnerIds.size === 0 || legacyOwnerIds.size === 0) {
|
|
2100
|
+
return null;
|
|
2101
|
+
}
|
|
2102
|
+
const primaryOwner = index.latestByRefKey.get(primaryRefKey);
|
|
2103
|
+
const legacyOwner = index.latestByRefKey.get(legacyRefKey);
|
|
2104
|
+
if (!primaryOwner || !legacyOwner) {
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
const primaryProviderRecord = primaryOwner.indexedRecord;
|
|
2108
|
+
const legacyProviderRecord = legacyOwner.indexedRecord;
|
|
2109
|
+
const primaryOrigin = primaryProviderRecord.dataOrigin;
|
|
2110
|
+
const legacyOrigin = legacyProviderRecord.dataOrigin;
|
|
2111
|
+
const incomingOrigin = entry.record.dataOrigin;
|
|
2112
|
+
const observedAtRaw = incomingOrigin?.observedAtRaw;
|
|
2113
|
+
const observedAtRawPrefix = `${entry.record.dayKey}:`;
|
|
2114
|
+
const resource = observedAtRaw?.startsWith(observedAtRawPrefix)
|
|
2115
|
+
&& observedAtRaw.endsWith(":daily")
|
|
2116
|
+
? observedAtRaw.slice(observedAtRawPrefix.length, -":daily".length)
|
|
2117
|
+
: "";
|
|
2118
|
+
if (primaryProviderRecord.dayKey !== entry.record.dayKey
|
|
2119
|
+
|| !isSameObservationFacet(primaryProviderRecord, entry.record)
|
|
2120
|
+
|| !isSameObservationFacet(primaryProviderRecord, legacyProviderRecord)
|
|
2121
|
+
|| junctionDailyAggregateAliasRefShape(primaryOwner.indexedExternalRef)
|
|
2122
|
+
!== junctionDailyAggregateAliasRefShape(externalRef)
|
|
2123
|
+
|| junctionDailyAggregateAliasRefShape(legacyOwner.indexedExternalRef)
|
|
2124
|
+
!== junctionDailyAggregateAliasRefShape(legacyExternalRef)
|
|
2125
|
+
|| !primaryOrigin
|
|
2126
|
+
|| !legacyOrigin
|
|
2127
|
+
|| !incomingOrigin
|
|
2128
|
+
|| resource.length === 0
|
|
2129
|
+
|| !deviceDataOriginSourceMatches(primaryOrigin, incomingOrigin)
|
|
2130
|
+
|| !deviceDataOriginSourceMatches(legacyOrigin, incomingOrigin)) {
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
const distinctOwnerIds = new Set([...primaryOwnerIds, ...legacyOwnerIds]);
|
|
2134
|
+
if (distinctOwnerIds.size < 2) {
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
if (primaryOwnerIds.size !== 1
|
|
2138
|
+
|| legacyOwnerIds.size !== 1
|
|
2139
|
+
|| distinctOwnerIds.size !== 2) {
|
|
2140
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_OWNER_REFUSED", "Junction daily aggregate alias repair requires exactly one live owner per claimed reference.");
|
|
2141
|
+
}
|
|
2142
|
+
if (!primaryOwnerIds.has(primaryOwner.record.id)
|
|
2143
|
+
|| !legacyOwnerIds.has(legacyOwner.record.id)
|
|
2144
|
+
|| primaryOwner.record.id === legacyOwner.record.id) {
|
|
2145
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_OWNER_REFUSED", "Junction daily aggregate alias repair could not prove the two live reference owners.");
|
|
2146
|
+
}
|
|
2147
|
+
return { legacyOwner, legacyRefKey, primaryOwner, primaryRefKey, resource };
|
|
2148
|
+
}
|
|
2149
|
+
function assertJunctionDailyAggregateAliasHistoryIsUncontaminated(entry, context) {
|
|
2150
|
+
const externalRef = entry.record.externalRef;
|
|
2151
|
+
const legacyExternalRef = entry.legacyExternalRefs[0];
|
|
2152
|
+
if (!externalRef || !legacyExternalRef) {
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const primaryRefKey = eventExternalRefKey(externalRef);
|
|
2156
|
+
const legacyRefKey = eventExternalRefKey(legacyExternalRef);
|
|
2157
|
+
const ownerIds = new Set([
|
|
2158
|
+
...(context.index.liveOwnerIdsByRefKey.get(primaryRefKey) ?? []),
|
|
2159
|
+
...(context.index.liveOwnerIdsByRefKey.get(legacyRefKey) ?? []),
|
|
2160
|
+
]);
|
|
2161
|
+
if ([...ownerIds].some((id) => context.index.aliasRepairContaminatedEventIds.has(id))
|
|
2162
|
+
|| context.index.aliasRepairContaminatedRefKeys.has(primaryRefKey)
|
|
2163
|
+
|| context.index.aliasRepairContaminatedRefKeys.has(legacyRefKey)) {
|
|
2164
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_HISTORY_REFUSED", "Junction daily aggregate alias repair found a schema-rejected candidate revision.");
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
function parseJunctionDailyAggregateAliasEvidence(entry, context) {
|
|
2168
|
+
const roles = context.evidenceRolesByPreparedRecordId.get(entry.record.id) ?? [];
|
|
2169
|
+
const part = roles.length === 1 ? context.evidenceByRole.get(roles[0]) : undefined;
|
|
2170
|
+
const metadata = part?.metadata;
|
|
2171
|
+
if (!part
|
|
2172
|
+
|| !isLooseRecordValue(metadata)
|
|
2173
|
+
|| metadata.artifactClass !== "compact_provider_timeseries_aggregate"
|
|
2174
|
+
|| metadata.provider !== "junction"
|
|
2175
|
+
|| metadata.resourceCategory !== "timeseries_daily_aggregate"
|
|
2176
|
+
|| metadata.retentionClass !== "provider_evidence") {
|
|
2177
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_EVIDENCE_REFUSED", "Junction daily aggregate alias repair requires one exact compact provider evidence role.");
|
|
2178
|
+
}
|
|
2179
|
+
let parsed;
|
|
2180
|
+
try {
|
|
2181
|
+
parsed = JSON.parse(part.content);
|
|
2182
|
+
}
|
|
2183
|
+
catch {
|
|
2184
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_EVIDENCE_REFUSED", "Junction daily aggregate alias repair evidence was not valid JSON.");
|
|
2185
|
+
}
|
|
2186
|
+
if (!isLooseRecordValue(parsed)) {
|
|
2187
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_EVIDENCE_REFUSED", "Junction daily aggregate alias repair evidence did not contain an object.");
|
|
2188
|
+
}
|
|
2189
|
+
const legacyDayKeys = parsed.legacyDayKeys;
|
|
2190
|
+
const origin = entry.record.dataOrigin;
|
|
2191
|
+
if (parsed.schema !== "junction.timeseries_daily_aggregate.v1"
|
|
2192
|
+
|| parsed.provider !== "junction"
|
|
2193
|
+
|| typeof parsed.resource !== "string"
|
|
2194
|
+
|| parsed.resource.length === 0
|
|
2195
|
+
|| metadata.resource !== parsed.resource
|
|
2196
|
+
|| typeof parsed.dayKey !== "string"
|
|
2197
|
+
|| !/^\d{4}-\d{2}-\d{2}$/u.test(parsed.dayKey)
|
|
2198
|
+
|| parsed.dayKey !== entry.record.dayKey
|
|
2199
|
+
|| typeof parsed.sourceProviderSlug !== "string"
|
|
2200
|
+
|| parsed.sourceProviderSlug.length === 0
|
|
2201
|
+
|| typeof parsed.sampleCount !== "number"
|
|
2202
|
+
|| !Number.isSafeInteger(parsed.sampleCount)
|
|
2203
|
+
|| parsed.sampleCount <= 0
|
|
2204
|
+
|| !Array.isArray(legacyDayKeys)
|
|
2205
|
+
|| legacyDayKeys.length !== 1
|
|
2206
|
+
|| typeof legacyDayKeys[0] !== "string"
|
|
2207
|
+
|| !/^\d{4}-\d{2}-\d{2}$/u.test(legacyDayKeys[0])
|
|
2208
|
+
|| legacyDayKeys[0] === parsed.dayKey
|
|
2209
|
+
|| (parsed.sourceType !== undefined && typeof parsed.sourceType !== "string")
|
|
2210
|
+
|| (parsed.sourceInstanceId !== undefined
|
|
2211
|
+
&& parsed.sourceInstanceId !== null
|
|
2212
|
+
&& typeof parsed.sourceInstanceId !== "string")
|
|
2213
|
+
|| origin?.sourceProviderSlug !== parsed.sourceProviderSlug
|
|
2214
|
+
|| (origin.sourceType ?? null) !== (parsed.sourceType ?? null)
|
|
2215
|
+
|| (origin.sourceInstanceId ?? null) !== (parsed.sourceInstanceId ?? null)
|
|
2216
|
+
|| origin.observedAtRaw !== `${parsed.dayKey}:${parsed.resource}:daily`) {
|
|
2217
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_EVIDENCE_REFUSED", "Junction daily aggregate alias repair evidence did not prove one normalized primary and legacy day split.");
|
|
2218
|
+
}
|
|
2219
|
+
return {
|
|
2220
|
+
dayKey: parsed.dayKey,
|
|
2221
|
+
legacyDayKey: legacyDayKeys[0],
|
|
2222
|
+
resource: parsed.resource,
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
function readCompleteJunctionDailyAggregateAliasHistory(index, eventId) {
|
|
2226
|
+
const history = index.aliasRepairHistoryById.get(eventId) ?? [];
|
|
2227
|
+
const maxRevision = index.maxRevisionById.get(eventId) ?? 0;
|
|
2228
|
+
const revisions = index.revisionsById.get(eventId);
|
|
2229
|
+
if (maxRevision < 1
|
|
2230
|
+
|| maxRevision > MAX_JUNCTION_DAILY_ALIAS_REPAIR_REVISIONS
|
|
2231
|
+
|| !revisions
|
|
2232
|
+
|| revisions.size !== maxRevision
|
|
2233
|
+
|| history.length !== maxRevision
|
|
2234
|
+
|| history.some((entry, historyIndex) => eventSpineRevision(entry.record) !== historyIndex + 1)) {
|
|
2235
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_HISTORY_REFUSED", "Junction daily aggregate alias repair requires a complete bounded event history.");
|
|
2236
|
+
}
|
|
2237
|
+
return history;
|
|
2238
|
+
}
|
|
2239
|
+
function junctionDailyAggregateAliasPersistedOriginsMatch(left, right) {
|
|
2240
|
+
if (!left || !right) {
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
return junctionDailyAggregateAliasHistoricalOriginMatches(left, right)
|
|
2244
|
+
|| junctionDailyAggregateAliasHistoricalOriginMatches(right, left);
|
|
2245
|
+
}
|
|
2246
|
+
function junctionDailyAggregateAliasInitialRecordMatches(record, input) {
|
|
2247
|
+
return record !== undefined
|
|
2248
|
+
&& record.source === "device"
|
|
2249
|
+
&& !isDeletedEventSpineRecord(record)
|
|
2250
|
+
&& record.dayKey === input.expectedDayKey
|
|
2251
|
+
&& record.dataOrigin?.observedAtRaw === input.expectedObservedAtRaw
|
|
2252
|
+
&& record.externalRef !== undefined
|
|
2253
|
+
&& eventExternalRefKey(record.externalRef)
|
|
2254
|
+
=== eventExternalRefKey(input.expectedExternalRef)
|
|
2255
|
+
&& junctionDailyAggregateAliasRefShape(record.externalRef)
|
|
2256
|
+
=== junctionDailyAggregateAliasRefShape(input.expectedExternalRef);
|
|
2257
|
+
}
|
|
2258
|
+
function analyzeJunctionDailyAggregateAliasHistoryEvolution(input) {
|
|
2259
|
+
const expectedRefKey = eventExternalRefKey(input.expectedExternalRef);
|
|
2260
|
+
let providerStateKey = input.initialProviderStateKey;
|
|
2261
|
+
let memberCarrierStateKey;
|
|
2262
|
+
for (const { record } of input.history) {
|
|
2263
|
+
const recordProviderStateKey = junctionDailyAggregateProviderStateKey(record);
|
|
2264
|
+
const providerRecord = record.source === "device";
|
|
2265
|
+
const manualMatchesProvider = recordProviderStateKey === providerStateKey;
|
|
2266
|
+
const manualMatchesCarrier = memberCarrierStateKey !== undefined
|
|
2267
|
+
&& recordProviderStateKey === memberCarrierStateKey;
|
|
2268
|
+
if (isDeletedEventSpineRecord(record)
|
|
2269
|
+
|| (record.source !== "device" && record.source !== "manual")
|
|
2270
|
+
|| record.dayKey !== input.expectedDayKey
|
|
2271
|
+
|| !isSameObservationFacet(input.initialRecord, record)
|
|
2272
|
+
|| (providerRecord
|
|
2273
|
+
&& (!record.externalRef
|
|
2274
|
+
|| !record.dataOrigin))
|
|
2275
|
+
|| (record.externalRef !== undefined
|
|
2276
|
+
&& (eventExternalRefKey(record.externalRef) !== expectedRefKey
|
|
2277
|
+
|| junctionDailyAggregateAliasRefShape(record.externalRef)
|
|
2278
|
+
!== junctionDailyAggregateAliasRefShape(input.expectedExternalRef)))
|
|
2279
|
+
|| (record.dataOrigin !== undefined
|
|
2280
|
+
&& (record.dataOrigin.observedAtRaw !== input.expectedObservedAtRaw
|
|
2281
|
+
|| !junctionDailyAggregateAliasHistoricalOriginMatches(record.dataOrigin, input.expectedOrigin)))
|
|
2282
|
+
|| (providerRecord
|
|
2283
|
+
? !input.allowProviderEvolution
|
|
2284
|
+
&& recordProviderStateKey !== input.initialProviderStateKey
|
|
2285
|
+
: !manualMatchesProvider && !manualMatchesCarrier)) {
|
|
2286
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_HISTORY_REFUSED", "Junction daily aggregate alias repair found unsupported or divergent historical state.");
|
|
2287
|
+
}
|
|
2288
|
+
if (providerRecord) {
|
|
2289
|
+
providerStateKey = recordProviderStateKey;
|
|
2290
|
+
}
|
|
2291
|
+
else if (manualMatchesProvider) {
|
|
2292
|
+
memberCarrierStateKey = recordProviderStateKey;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
const latest = input.history.at(-1)?.record;
|
|
2296
|
+
if (!latest) {
|
|
2297
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_HISTORY_REFUSED", "Junction daily aggregate alias repair requires a complete bounded event history.");
|
|
2298
|
+
}
|
|
2299
|
+
const links = [...(latest.links ?? [])];
|
|
2300
|
+
const tags = [...(latest.tags ?? [])];
|
|
2301
|
+
return {
|
|
2302
|
+
links,
|
|
2303
|
+
memberAuthored: latest.source === "manual"
|
|
2304
|
+
|| latest.note !== undefined
|
|
2305
|
+
|| tags.length > 0
|
|
2306
|
+
|| links.length > 0,
|
|
2307
|
+
...(latest.note ? { note: latest.note } : {}),
|
|
2308
|
+
tags,
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
2311
|
+
function resolveJunctionDailyAggregateAliasOwnerSplit(entry, index) {
|
|
2312
|
+
const ownerSplit = resolveStructuralJunctionDailyAggregateAliasOwnerSplit(entry, index);
|
|
2313
|
+
const externalRef = entry.record.externalRef;
|
|
2314
|
+
const legacyExternalRef = entry.legacyExternalRefs[0];
|
|
2315
|
+
if (!ownerSplit || !externalRef || !legacyExternalRef || !entry.record.dataOrigin) {
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
const primaryInitial = index.aliasRepairHistoryById.get(ownerSplit.primaryOwner.record.id)?.[0]?.record;
|
|
2319
|
+
const legacyInitial = index.aliasRepairHistoryById.get(ownerSplit.legacyOwner.record.id)?.[0]?.record;
|
|
2320
|
+
const legacyDayKey = ownerSplit.legacyOwner.indexedRecord.dayKey;
|
|
2321
|
+
// Divergent initial baselines are ordinary adjacent-day owners. Once the
|
|
2322
|
+
// baselines match, every later revision must satisfy the bounded evolution
|
|
2323
|
+
// rules before this pair can reserve or mutate either identity.
|
|
2324
|
+
if (!junctionDailyAggregateAliasInitialRecordMatches(primaryInitial, {
|
|
2325
|
+
expectedDayKey: entry.record.dayKey,
|
|
2326
|
+
expectedExternalRef: externalRef,
|
|
2327
|
+
expectedObservedAtRaw: `${entry.record.dayKey}:${ownerSplit.resource}:daily`,
|
|
2328
|
+
})
|
|
2329
|
+
|| !junctionDailyAggregateAliasInitialRecordMatches(legacyInitial, {
|
|
2330
|
+
expectedDayKey: legacyDayKey,
|
|
2331
|
+
expectedExternalRef: legacyExternalRef,
|
|
2332
|
+
expectedObservedAtRaw: `${legacyDayKey}:${ownerSplit.resource}:daily`,
|
|
2333
|
+
})
|
|
2334
|
+
|| !isSameObservationFacet(primaryInitial, legacyInitial)
|
|
2335
|
+
|| junctionDailyAggregateProviderStateKey(primaryInitial)
|
|
2336
|
+
!== junctionDailyAggregateProviderStateKey(legacyInitial)
|
|
2337
|
+
|| !junctionDailyAggregateAliasPersistedOriginsMatch(primaryInitial.dataOrigin, legacyInitial.dataOrigin)) {
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
const primaryHistory = readCompleteJunctionDailyAggregateAliasHistory(index, ownerSplit.primaryOwner.record.id);
|
|
2341
|
+
const legacyHistory = readCompleteJunctionDailyAggregateAliasHistory(index, ownerSplit.legacyOwner.record.id);
|
|
2342
|
+
const primaryOverlay = analyzeJunctionDailyAggregateAliasHistoryEvolution({
|
|
2343
|
+
allowProviderEvolution: true,
|
|
2344
|
+
expectedDayKey: entry.record.dayKey,
|
|
2345
|
+
expectedExternalRef: externalRef,
|
|
2346
|
+
expectedOrigin: entry.record.dataOrigin,
|
|
2347
|
+
expectedObservedAtRaw: `${entry.record.dayKey}:${ownerSplit.resource}:daily`,
|
|
2348
|
+
history: primaryHistory,
|
|
2349
|
+
initialProviderStateKey: junctionDailyAggregateProviderStateKey(primaryInitial),
|
|
2350
|
+
initialRecord: primaryInitial,
|
|
2351
|
+
});
|
|
2352
|
+
const legacyOverlay = analyzeJunctionDailyAggregateAliasHistoryEvolution({
|
|
2353
|
+
allowProviderEvolution: false,
|
|
2354
|
+
expectedDayKey: legacyDayKey,
|
|
2355
|
+
expectedExternalRef: legacyExternalRef,
|
|
2356
|
+
expectedOrigin: entry.record.dataOrigin,
|
|
2357
|
+
expectedObservedAtRaw: `${legacyDayKey}:${ownerSplit.resource}:daily`,
|
|
2358
|
+
history: legacyHistory,
|
|
2359
|
+
initialProviderStateKey: junctionDailyAggregateProviderStateKey(legacyInitial),
|
|
2360
|
+
initialRecord: legacyInitial,
|
|
2361
|
+
});
|
|
2362
|
+
return { ...ownerSplit, legacyOverlay, primaryOverlay };
|
|
2363
|
+
}
|
|
2364
|
+
function hasJunctionDailyAggregateAliasSplit(entry, context) {
|
|
2365
|
+
return resolveJunctionDailyAggregateAliasOwnerSplit(entry, context.index) !== null;
|
|
2366
|
+
}
|
|
2367
|
+
function mergeJunctionDailyAggregateAliasOverlays(primary, legacy) {
|
|
2368
|
+
if (primary.note && legacy.note && primary.note !== legacy.note) {
|
|
2369
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_OVERLAY_REFUSED", "Junction daily aggregate alias repair found conflicting member notes.");
|
|
2370
|
+
}
|
|
2371
|
+
const linksByKey = new Map();
|
|
2372
|
+
for (const link of [...primary.links, ...legacy.links]) {
|
|
2373
|
+
linksByKey.set(stableStringify(link), link);
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
links: [...linksByKey.entries()]
|
|
2377
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
2378
|
+
.map(([, link]) => link),
|
|
2379
|
+
memberAuthored: primary.memberAuthored || legacy.memberAuthored,
|
|
2380
|
+
...(primary.note || legacy.note ? { note: primary.note ?? legacy.note } : {}),
|
|
2381
|
+
tags: [...new Set([...primary.tags, ...legacy.tags])].sort(),
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
function buildJunctionDailyAggregateAliasRepairPlan(input) {
|
|
2385
|
+
const { aliasRepairContext, context, entry } = input;
|
|
2386
|
+
const externalRef = entry.record.externalRef;
|
|
2387
|
+
const legacyExternalRef = entry.legacyExternalRefs[0];
|
|
2388
|
+
const ownerSplit = resolveJunctionDailyAggregateAliasOwnerSplit(entry, context.index);
|
|
2389
|
+
if (!aliasRepairContext
|
|
2390
|
+
|| !externalRef
|
|
2391
|
+
|| !legacyExternalRef
|
|
2392
|
+
|| !ownerSplit) {
|
|
2393
|
+
return null;
|
|
2394
|
+
}
|
|
2395
|
+
assertJunctionDailyAggregateAliasHistoryIsUncontaminated(entry, context);
|
|
2396
|
+
const { legacyOverlay, legacyOwner, legacyRefKey, primaryOverlay, primaryOwner, primaryRefKey, } = ownerSplit;
|
|
2397
|
+
const evidence = parseJunctionDailyAggregateAliasEvidence(entry, aliasRepairContext);
|
|
2398
|
+
if (primaryOwner.record.dayKey !== evidence.dayKey
|
|
2399
|
+
|| legacyOwner.record.dayKey !== evidence.legacyDayKey
|
|
2400
|
+
|| ownerSplit.resource !== evidence.resource) {
|
|
2401
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_HISTORY_REFUSED", "Junction daily aggregate alias repair owner days did not match the normalized evidence.");
|
|
2402
|
+
}
|
|
2403
|
+
const overlay = mergeJunctionDailyAggregateAliasOverlays(primaryOverlay, legacyOverlay);
|
|
2404
|
+
const primaryRevision = (context.index.maxRevisionById.get(primaryOwner.record.id) ?? 0) + 1;
|
|
2405
|
+
const legacyRevision = (context.index.maxRevisionById.get(legacyOwner.record.id) ?? 0) + 1;
|
|
2406
|
+
const providerSurvivor = {
|
|
2407
|
+
...entry.record,
|
|
2408
|
+
id: primaryOwner.record.id,
|
|
2409
|
+
lifecycle: buildEventSpineLifecycle(primaryRevision),
|
|
2410
|
+
};
|
|
2411
|
+
const loserTombstone = {
|
|
2412
|
+
...entry.record,
|
|
2413
|
+
id: legacyOwner.record.id,
|
|
2414
|
+
lifecycle: buildEventSpineLifecycle(legacyRevision, "deleted"),
|
|
2415
|
+
};
|
|
2416
|
+
const overlaySurvivor = overlay.memberAuthored
|
|
2417
|
+
? {
|
|
2418
|
+
...providerSurvivor,
|
|
2419
|
+
source: "manual",
|
|
2420
|
+
...(overlay.note ? { note: overlay.note } : {}),
|
|
2421
|
+
...(overlay.tags.length > 0 ? { tags: overlay.tags } : {}),
|
|
2422
|
+
...(overlay.links.length > 0 ? { links: overlay.links } : {}),
|
|
2423
|
+
lifecycle: buildEventSpineLifecycle(primaryRevision + 1),
|
|
2424
|
+
}
|
|
2425
|
+
: undefined;
|
|
2426
|
+
return {
|
|
2427
|
+
legacyRefKey,
|
|
2428
|
+
loserPath: legacyOwner.relativePath || toEventLedgerFile(legacyOwner.record.occurredAt),
|
|
2429
|
+
loserTombstone,
|
|
2430
|
+
...(overlaySurvivor ? { overlaySurvivor } : {}),
|
|
2431
|
+
primaryExternalRef: externalRef,
|
|
2432
|
+
primaryRefKey,
|
|
2433
|
+
providerSurvivor,
|
|
2434
|
+
survivorPath: entry.relativePath,
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
function applyJunctionDailyAggregateAliasRepairToIndex(plan, index) {
|
|
2438
|
+
const survivor = plan.overlaySurvivor ?? plan.providerSurvivor;
|
|
2439
|
+
index.latestByRefKey.set(plan.primaryRefKey, {
|
|
2440
|
+
indexedExternalRef: plan.primaryExternalRef,
|
|
2441
|
+
indexedRecord: plan.providerSurvivor,
|
|
2442
|
+
relativePath: plan.survivorPath,
|
|
2443
|
+
record: survivor,
|
|
2444
|
+
});
|
|
2445
|
+
index.latestByRefKey.delete(plan.legacyRefKey);
|
|
2446
|
+
index.latestById.set(survivor.id, survivor);
|
|
2447
|
+
index.latestById.set(plan.loserTombstone.id, plan.loserTombstone);
|
|
2448
|
+
index.liveOwnerIdsByRefKey.set(plan.primaryRefKey, new Set([survivor.id]));
|
|
2449
|
+
index.liveOwnerIdsByRefKey.delete(plan.legacyRefKey);
|
|
2450
|
+
for (const record of [
|
|
2451
|
+
plan.providerSurvivor,
|
|
2452
|
+
...(plan.overlaySurvivor ? [plan.overlaySurvivor] : []),
|
|
2453
|
+
plan.loserTombstone,
|
|
2454
|
+
]) {
|
|
2455
|
+
const revision = eventSpineRevision(record);
|
|
2456
|
+
index.maxRevisionById.set(record.id, revision);
|
|
2457
|
+
const revisions = index.revisionsById.get(record.id) ?? new Set();
|
|
2458
|
+
revisions.add(revision);
|
|
2459
|
+
index.revisionsById.set(record.id, revisions);
|
|
2460
|
+
const history = index.aliasRepairHistoryById.get(record.id) ?? [];
|
|
2461
|
+
history.push({
|
|
2462
|
+
relativePath: record.id === plan.loserTombstone.id
|
|
2463
|
+
? plan.loserPath
|
|
2464
|
+
: plan.survivorPath,
|
|
2465
|
+
record,
|
|
2466
|
+
});
|
|
2467
|
+
index.aliasRepairHistoryById.set(record.id, history);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
1375
2470
|
function whoopSleepTypeProviderBaselineRevision(index, refKey, eventId, incoming) {
|
|
1376
2471
|
if (incoming.kind !== "sleep_session"
|
|
1377
2472
|
|| incoming.externalRef?.system !== "whoop"
|
|
@@ -1414,6 +2509,7 @@ function resolveDeviceEventIdentity(entry, context, options = {}) {
|
|
|
1414
2509
|
const primaryMatch = reservedForLegacyClaim && reservedForLegacyClaim.entry !== entry
|
|
1415
2510
|
? undefined
|
|
1416
2511
|
: index.latestByRefKey.get(refKey);
|
|
2512
|
+
const explicitLegacyRefKeys = new Set(entry.legacyExternalRefs.map((legacyExternalRef) => eventExternalRefKey(legacyExternalRef)));
|
|
1417
2513
|
const legacyMatchedEntries = entry.legacyExternalRefs
|
|
1418
2514
|
.map((legacyExternalRef) => ({
|
|
1419
2515
|
legacyExternalRef,
|
|
@@ -1438,9 +2534,18 @@ function resolveDeviceEventIdentity(entry, context, options = {}) {
|
|
|
1438
2534
|
return (!reservation || reservation.entry === entry)
|
|
1439
2535
|
&& isCompatibleLegacyExternalRefMatch(match.indexedMatch, entry.record, match.legacyExternalRef);
|
|
1440
2536
|
});
|
|
2537
|
+
const implicitReservedEntries = [...legacyReservations.entries()]
|
|
2538
|
+
.filter(([reservedRefKey, reservation]) => reservation.entry === entry
|
|
2539
|
+
&& reservedRefKey !== refKey
|
|
2540
|
+
&& !explicitLegacyRefKeys.has(reservedRefKey))
|
|
2541
|
+
.map(([reservedRefKey, reservation]) => ({
|
|
2542
|
+
refKey: reservedRefKey,
|
|
2543
|
+
indexedMatch: reservation.indexedMatch,
|
|
2544
|
+
}));
|
|
1441
2545
|
const matchedEntries = [
|
|
1442
2546
|
...(primaryMatch ? [{ refKey, indexedMatch: primaryMatch }] : []),
|
|
1443
2547
|
...legacyMatchedEntries,
|
|
2548
|
+
...implicitReservedEntries,
|
|
1444
2549
|
];
|
|
1445
2550
|
const selectedPrimaryMatch = matchedEntries.find((match) => match.refKey === refKey);
|
|
1446
2551
|
const latest = selectedPrimaryMatch?.indexedMatch.record ?? matchedEntries[0]?.indexedMatch.record;
|
|
@@ -1547,6 +2652,8 @@ function mapCurrentDeviceEventOwners(entries, context) {
|
|
|
1547
2652
|
? []
|
|
1548
2653
|
: resolved.matchedEntries.map(({ indexedMatch }) => compareIncomingExternalRefVersion(indexedMatch.indexedExternalRef, incomingExternalRef)).filter((comparison) => comparison !== null);
|
|
1549
2654
|
const incomingHasEqualOrNewerComparableVersion = sourceVersionComparisons.some((comparison) => comparison >= 0);
|
|
2655
|
+
const incomingHasOnlyOlderComparableVersions = sourceVersionComparisons.length > 0
|
|
2656
|
+
&& !incomingHasEqualOrNewerComparableVersion;
|
|
1550
2657
|
if (sourceVersionComparisons.some((comparison) => comparison > 0)) {
|
|
1551
2658
|
incomingNewerPreparedIds.add(entry.record.id);
|
|
1552
2659
|
}
|
|
@@ -1556,6 +2663,7 @@ function mapCurrentDeviceEventOwners(entries, context) {
|
|
|
1556
2663
|
historicallyDeliveredPreparedIds.add(entry.record.id);
|
|
1557
2664
|
}
|
|
1558
2665
|
if (resolved.associationSafe
|
|
2666
|
+
&& !incomingHasOnlyOlderComparableVersions
|
|
1559
2667
|
&& !isDeletedEventSpineRecord(current)
|
|
1560
2668
|
&& deviceEventContentKey(current) === deviceEventContentKey(entry.record)) {
|
|
1561
2669
|
associationSafePreparedIds.add(entry.record.id);
|
|
@@ -1579,18 +2687,63 @@ function mapCurrentDeviceEventOwners(entries, context) {
|
|
|
1579
2687
|
// skipped; changed content normally appends an event-spine revision onto the
|
|
1580
2688
|
// existing event id instead of a new event. Callers may mark a capture's
|
|
1581
2689
|
// externalRef immutable when changed content must be rejected instead.
|
|
1582
|
-
async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, existingContext, preferredCanonicalIdByPreparedId = new Map()) {
|
|
2690
|
+
async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, existingContext, preferredCanonicalIdByPreparedId = new Map(), authoritativeEventSets = [], aliasRepairContext) {
|
|
1583
2691
|
assertCanonicalWriteLockScope(vaultRoot);
|
|
1584
2692
|
const context = existingContext ?? await buildDeviceEventIdentityContext(vaultRoot, entries);
|
|
1585
2693
|
const { index } = context;
|
|
1586
2694
|
const appendEntries = [];
|
|
1587
2695
|
const appendRecordIdByPreparedRecordId = new Map();
|
|
1588
|
-
const
|
|
2696
|
+
const recordsByEntryIndex = new Map();
|
|
1589
2697
|
const forceAppendIds = new Set();
|
|
1590
2698
|
const retainedPreparedIds = new Set();
|
|
1591
2699
|
let skippedDuplicateCount = 0;
|
|
1592
2700
|
let supersededCount = 0;
|
|
1593
|
-
|
|
2701
|
+
let retractedCount = 0;
|
|
2702
|
+
const aliasRepairByEntryIndex = new Map();
|
|
2703
|
+
for (const [entryIndex, entry] of entries.entries()) {
|
|
2704
|
+
const aliasRepair = buildJunctionDailyAggregateAliasRepairPlan({
|
|
2705
|
+
aliasRepairContext,
|
|
2706
|
+
context,
|
|
2707
|
+
entry,
|
|
2708
|
+
});
|
|
2709
|
+
if (aliasRepair) {
|
|
2710
|
+
aliasRepairByEntryIndex.set(entryIndex, aliasRepair);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
const aliasRepairOwnerIds = new Set();
|
|
2714
|
+
for (const aliasRepair of aliasRepairByEntryIndex.values()) {
|
|
2715
|
+
for (const ownerId of [
|
|
2716
|
+
aliasRepair.providerSurvivor.id,
|
|
2717
|
+
aliasRepair.loserTombstone.id,
|
|
2718
|
+
]) {
|
|
2719
|
+
if (aliasRepairOwnerIds.has(ownerId)) {
|
|
2720
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_OWNER_REFUSED", "Junction daily aggregate alias repair operations cannot share persisted owners.");
|
|
2721
|
+
}
|
|
2722
|
+
aliasRepairOwnerIds.add(ownerId);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
for (const [entryIndex, aliasRepair] of aliasRepairByEntryIndex) {
|
|
2726
|
+
const entry = entries[entryIndex];
|
|
2727
|
+
appendEntries.push({ relativePath: aliasRepair.survivorPath, record: aliasRepair.providerSurvivor }, { relativePath: aliasRepair.loserPath, record: aliasRepair.loserTombstone });
|
|
2728
|
+
if (aliasRepair.overlaySurvivor) {
|
|
2729
|
+
appendEntries.push({
|
|
2730
|
+
relativePath: aliasRepair.survivorPath,
|
|
2731
|
+
record: aliasRepair.overlaySurvivor,
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
appendRecordIdByPreparedRecordId.set(entry.record.id, aliasRepair.providerSurvivor.id);
|
|
2735
|
+
forceAppendIds.add(aliasRepair.providerSurvivor.id);
|
|
2736
|
+
forceAppendIds.add(aliasRepair.loserTombstone.id);
|
|
2737
|
+
recordsByEntryIndex.set(entryIndex, aliasRepair.overlaySurvivor ?? aliasRepair.providerSurvivor);
|
|
2738
|
+
applyJunctionDailyAggregateAliasRepairToIndex(aliasRepair, index);
|
|
2739
|
+
supersededCount += 1;
|
|
2740
|
+
retractedCount += 1;
|
|
2741
|
+
}
|
|
2742
|
+
for (const [entryIndex, originalEntry] of entries.entries()) {
|
|
2743
|
+
if (aliasRepairByEntryIndex.has(entryIndex)) {
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
let entry = originalEntry;
|
|
1594
2747
|
const externalRef = entry.record.externalRef;
|
|
1595
2748
|
if (!externalRef) {
|
|
1596
2749
|
const current = index.latestById.get(entry.record.id);
|
|
@@ -1599,20 +2752,25 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1599
2752
|
&& deviceEventContentKey(current) === deviceEventContentKey(entry.record)) {
|
|
1600
2753
|
skippedDuplicateCount += 1;
|
|
1601
2754
|
retainedPreparedIds.add(entry.record.id);
|
|
1602
|
-
|
|
2755
|
+
recordsByEntryIndex.set(entryIndex, current);
|
|
1603
2756
|
continue;
|
|
1604
2757
|
}
|
|
1605
2758
|
appendEntries.push(entry);
|
|
1606
2759
|
appendRecordIdByPreparedRecordId.set(entry.record.id, entry.record.id);
|
|
1607
|
-
|
|
2760
|
+
recordsByEntryIndex.set(entryIndex, entry.record);
|
|
1608
2761
|
continue;
|
|
1609
2762
|
}
|
|
1610
2763
|
const resolved = resolveDeviceEventIdentity(entry, context, { strict: true });
|
|
1611
2764
|
if (!resolved) {
|
|
1612
2765
|
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", "Device event identity unexpectedly lost its externalRef during reconciliation.");
|
|
1613
2766
|
}
|
|
2767
|
+
if (resolved.latest && aliasRepairOwnerIds.has(resolved.latest.id)) {
|
|
2768
|
+
throw new VaultError("EVENT_ALIAS_REPAIR_OWNER_REFUSED", "Junction daily aggregate alias repair operations cannot share persisted owners.");
|
|
2769
|
+
}
|
|
1614
2770
|
const { matchedEntries, refKey } = resolved;
|
|
1615
2771
|
let latest = resolved.latest;
|
|
2772
|
+
const migratesJunctionNoIdProfileIdentity = isIncomingJunctionNoIdProfile(entry.record)
|
|
2773
|
+
&& matchedEntries.some((match) => match.refKey !== refKey);
|
|
1616
2774
|
if (!latest) {
|
|
1617
2775
|
const canonicalRecordId = preferredCanonicalIdByPreparedId.get(entry.record.id)
|
|
1618
2776
|
?? entry.record.id;
|
|
@@ -1622,9 +2780,59 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1622
2780
|
index.latestByRefKey.set(refKey, toIndexedExternalRefMatch(canonicalRecord, externalRef));
|
|
1623
2781
|
appendEntries.push({ relativePath: entry.relativePath, record: canonicalRecord });
|
|
1624
2782
|
appendRecordIdByPreparedRecordId.set(entry.record.id, canonicalRecord.id);
|
|
1625
|
-
|
|
2783
|
+
recordsByEntryIndex.set(entryIndex, canonicalRecord);
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
const indexedProviderMatch = matchedEntries.find((match) => match.indexedMatch.record.id === latest.id)?.indexedMatch ?? matchedEntries[0]?.indexedMatch;
|
|
2787
|
+
const replaysJunctionNoIdProfileTimestamp = indexedProviderMatch !== undefined
|
|
2788
|
+
&& isJunctionNoIdProfileEqualRevisionTimestampReplay(indexedProviderMatch.indexedRecord, entry.record);
|
|
2789
|
+
if (replaysJunctionNoIdProfileTimestamp) {
|
|
2790
|
+
skippedDuplicateCount += 1;
|
|
2791
|
+
retainedPreparedIds.add(entry.record.id);
|
|
2792
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1626
2793
|
continue;
|
|
1627
2794
|
}
|
|
2795
|
+
const matchesIndexedProviderContent = indexedProviderMatch !== undefined
|
|
2796
|
+
&& deviceEventContentKey(indexedProviderMatch.indexedRecord)
|
|
2797
|
+
=== deviceEventContentKey(entry.record);
|
|
2798
|
+
const indexedSourceVersionComparison = indexedProviderMatch
|
|
2799
|
+
? compareIncomingExternalRefVersion(indexedProviderMatch.indexedExternalRef, externalRef)
|
|
2800
|
+
: null;
|
|
2801
|
+
const migratesJunctionStableProfileTimestamp = indexedProviderMatch !== undefined
|
|
2802
|
+
&& isJunctionStableProfileCreatedAtTimestampMigration(indexedProviderMatch.indexedRecord, entry.record);
|
|
2803
|
+
const retainsDeletedJunctionStableProfile = isMemberDeletedJunctionStableProfile(latest, entry.record);
|
|
2804
|
+
if (isDeletedEventSpineRecord(latest)
|
|
2805
|
+
&& (migratesJunctionStableProfileTimestamp || retainsDeletedJunctionStableProfile)) {
|
|
2806
|
+
skippedDuplicateCount += 1;
|
|
2807
|
+
retainedPreparedIds.add(entry.record.id);
|
|
2808
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
const authoritativeSet = authoritativeEventSets.find((set) => externalRef.system === set.system
|
|
2812
|
+
&& externalRef.resourceType === set.resourceType
|
|
2813
|
+
&& externalRef.resourceId === set.resourceId
|
|
2814
|
+
&& externalRef.facet !== undefined
|
|
2815
|
+
&& set.currentFacets.has(externalRef.facet));
|
|
2816
|
+
if (authoritativeSet) {
|
|
2817
|
+
const sourceVersionComparison = compareIncomingExternalRefVersion(matchedEntries.find((match) => match.refKey === refKey)?.indexedMatch.indexedExternalRef
|
|
2818
|
+
?? matchedEntries[0]?.indexedMatch.indexedExternalRef
|
|
2819
|
+
?? latest.externalRef
|
|
2820
|
+
?? externalRef, externalRef);
|
|
2821
|
+
if (sourceVersionComparison !== null && sourceVersionComparison < 0) {
|
|
2822
|
+
skippedDuplicateCount += 1;
|
|
2823
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2826
|
+
if (sourceVersionComparison === 0
|
|
2827
|
+
&& (isDeletedEventSpineRecord(latest)
|
|
2828
|
+
|| deviceEventContentKey(latest) !== deviceEventContentKey(entry.record))
|
|
2829
|
+
&& !matchesIndexedProviderContent
|
|
2830
|
+
&& !migratesJunctionStableProfileTimestamp) {
|
|
2831
|
+
throw new VaultError("EVENT_SOURCE_REVISION_CONFLICT", `Authoritative device event externalRef "${externalRef.system}/${externalRef.resourceType}/` +
|
|
2832
|
+
`${externalRef.resourceId}#${externalRef.facet}" has conflicting content for source revision ` +
|
|
2833
|
+
`"${authoritativeSet.version}"; nothing was imported.`);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
1628
2836
|
// externalRef identity does not include kind. Event spines are kind-stable,
|
|
1629
2837
|
// so device reconciliation must reject under-faceted provider refs instead
|
|
1630
2838
|
// of rewriting an existing event id as a different event kind.
|
|
@@ -1633,20 +2841,79 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1633
2841
|
`${externalRef.facet ? `#${externalRef.facet}` : ""}" already belongs to kind ` +
|
|
1634
2842
|
`"${latest.kind}" and cannot be rewritten as "${entry.record.kind}"; nothing was imported.`);
|
|
1635
2843
|
}
|
|
1636
|
-
|
|
2844
|
+
// An unversioned member declared current by this batch's authoritative set
|
|
2845
|
+
// reasserts a retracted facet in serialized arrival order rather than
|
|
2846
|
+
// treating the tombstone's identical content as an exact replay. Ordinary
|
|
2847
|
+
// versionless deliveries without complete-set authority never resurrect.
|
|
2848
|
+
// Only provider-owned authoritative-set retractions may be reasserted.
|
|
2849
|
+
// Their tombstones carry the set's explicit version marker on the external
|
|
2850
|
+
// reference, while a member deletion preserves the member's unversioned
|
|
2851
|
+
// reference and must stay deleted under authoritative replay.
|
|
2852
|
+
const reassertsUnversionedSetMember = Boolean(authoritativeSet
|
|
2853
|
+
&& indexedSourceVersionComparison === null
|
|
2854
|
+
&& isDeletedEventSpineRecord(latest)
|
|
2855
|
+
&& latest.source === "device"
|
|
2856
|
+
&& latest.externalRef?.version !== undefined);
|
|
2857
|
+
if (deviceEventContentKey(latest) === deviceEventContentKey(entry.record)
|
|
2858
|
+
&& (indexedSourceVersionComparison === null || indexedSourceVersionComparison === 0)
|
|
2859
|
+
&& !reassertsUnversionedSetMember) {
|
|
1637
2860
|
skippedDuplicateCount += 1;
|
|
1638
2861
|
retainedPreparedIds.add(entry.record.id);
|
|
1639
|
-
|
|
2862
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1640
2863
|
continue;
|
|
1641
2864
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
2865
|
+
if (matchesIndexedProviderContent && !reassertsUnversionedSetMember) {
|
|
2866
|
+
const historicalUserEditMatch = matchedEntries.find((match) => hasHistoricalExternalRefUserAuthoredChanges(match.indexedMatch));
|
|
2867
|
+
if (historicalUserEditMatch
|
|
2868
|
+
&& indexedSourceVersionComparison !== null
|
|
2869
|
+
&& indexedSourceVersionComparison > 0) {
|
|
2870
|
+
const providerRevision = Math.max(eventSpineRevision(latest), index.maxRevisionById.get(latest.id) ?? 0) + 1;
|
|
2871
|
+
const providerBaseline = {
|
|
2872
|
+
...entry.record,
|
|
2873
|
+
id: latest.id,
|
|
2874
|
+
lifecycle: buildEventSpineLifecycle(providerRevision),
|
|
2875
|
+
};
|
|
2876
|
+
const retainedMemberRevision = {
|
|
2877
|
+
...latest,
|
|
2878
|
+
...(migratesJunctionNoIdProfileIdentity
|
|
2879
|
+
? { externalRef, dataOrigin: entry.record.dataOrigin }
|
|
2880
|
+
: {}),
|
|
2881
|
+
lifecycle: buildEventSpineLifecycle(providerRevision + 1),
|
|
2882
|
+
};
|
|
2883
|
+
const retainedMemberPath = historicalUserEditMatch.indexedMatch.relativePath
|
|
2884
|
+
|| toEventLedgerFile(latest.occurredAt);
|
|
2885
|
+
forceAppendIds.add(latest.id);
|
|
2886
|
+
index.latestByRefKey.set(refKey, {
|
|
2887
|
+
indexedExternalRef: externalRef,
|
|
2888
|
+
indexedRecord: providerBaseline,
|
|
2889
|
+
relativePath: retainedMemberPath,
|
|
2890
|
+
record: retainedMemberRevision,
|
|
2891
|
+
});
|
|
2892
|
+
for (const { refKey: matchedRefKey, indexedMatch } of matchedEntries) {
|
|
2893
|
+
const currentMatch = index.latestByRefKey.get(matchedRefKey);
|
|
2894
|
+
if (matchedRefKey !== refKey
|
|
2895
|
+
&& indexedMatch.record.id === latest.id
|
|
2896
|
+
&& currentMatch?.record.id === latest.id) {
|
|
2897
|
+
index.latestByRefKey.delete(matchedRefKey);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
index.maxRevisionById.set(latest.id, providerRevision + 1);
|
|
2901
|
+
appendEntries.push({ relativePath: entry.relativePath, record: providerBaseline });
|
|
2902
|
+
appendEntries.push({
|
|
2903
|
+
relativePath: retainedMemberPath,
|
|
2904
|
+
record: retainedMemberRevision,
|
|
2905
|
+
});
|
|
2906
|
+
appendRecordIdByPreparedRecordId.set(entry.record.id, providerBaseline.id);
|
|
2907
|
+
recordsByEntryIndex.set(entryIndex, retainedMemberRevision);
|
|
2908
|
+
supersededCount += 1;
|
|
2909
|
+
continue;
|
|
2910
|
+
}
|
|
2911
|
+
if (indexedSourceVersionComparison === null || indexedSourceVersionComparison === 0) {
|
|
2912
|
+
skippedDuplicateCount += 1;
|
|
2913
|
+
retainedPreparedIds.add(entry.record.id);
|
|
2914
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
1650
2917
|
}
|
|
1651
2918
|
if (entry.externalRefUpdatePolicy === "immutable") {
|
|
1652
2919
|
throw new VaultError("EVENT_IMMUTABLE_EXTERNAL_REF_CONFLICT", "Immutable device event externalRef already exists with different content; nothing was imported.");
|
|
@@ -1672,7 +2939,7 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1672
2939
|
if (eventSpineRevisionsAreComplete(index, latest.id)) {
|
|
1673
2940
|
retainedPreparedIds.add(entry.record.id);
|
|
1674
2941
|
}
|
|
1675
|
-
|
|
2942
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1676
2943
|
continue;
|
|
1677
2944
|
}
|
|
1678
2945
|
if (sourceVersionComparison === 0) {
|
|
@@ -1688,7 +2955,7 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1688
2955
|
if (eventSpineRevisionsAreComplete(index, latest.id)) {
|
|
1689
2956
|
retainedPreparedIds.add(entry.record.id);
|
|
1690
2957
|
}
|
|
1691
|
-
|
|
2958
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1692
2959
|
continue;
|
|
1693
2960
|
}
|
|
1694
2961
|
const sleepTypeBaselineRevision = sourceVersionComparison === 0
|
|
@@ -1714,7 +2981,7 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1714
2981
|
if (eventSpineRevisionsAreComplete(index, latest.id)) {
|
|
1715
2982
|
retainedPreparedIds.add(entry.record.id);
|
|
1716
2983
|
}
|
|
1717
|
-
|
|
2984
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1718
2985
|
continue;
|
|
1719
2986
|
}
|
|
1720
2987
|
}
|
|
@@ -1725,37 +2992,87 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1725
2992
|
if (eventSpineRevisionsAreComplete(index, latest.id)) {
|
|
1726
2993
|
retainedPreparedIds.add(entry.record.id);
|
|
1727
2994
|
}
|
|
1728
|
-
|
|
2995
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1729
2996
|
continue;
|
|
1730
2997
|
}
|
|
1731
|
-
|
|
2998
|
+
const replaysProviderOwnedRetractionWithoutSetAuthority = Boolean(isDeletedEventSpineRecord(latest)
|
|
2999
|
+
&& latest.source === "device"
|
|
3000
|
+
&& latest.externalRef?.version !== undefined
|
|
3001
|
+
&& externalRef.version === undefined
|
|
3002
|
+
&& !authoritativeSet);
|
|
3003
|
+
if (replaysProviderOwnedRetractionWithoutSetAuthority) {
|
|
3004
|
+
skippedDuplicateCount += 1;
|
|
3005
|
+
retainedPreparedIds.add(entry.record.id);
|
|
3006
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
if (isDeletedEventSpineRecord(latest) && !reassertsUnversionedSetMember) {
|
|
3010
|
+
// A member-authored deletion tombstone (unversioned reference) stays
|
|
3011
|
+
// dead under authoritative replay: no append, no index change, so a
|
|
3012
|
+
// later empty-then-populated cadence cannot launder the deletion away.
|
|
3013
|
+
if (authoritativeSet && indexedSourceVersionComparison === null) {
|
|
3014
|
+
skippedDuplicateCount += 1;
|
|
3015
|
+
retainedPreparedIds.add(entry.record.id);
|
|
3016
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
1732
3019
|
index.latestByRefKey.set(refKey, toIndexedExternalRefMatch(entry.record, externalRef));
|
|
1733
3020
|
appendEntries.push(entry);
|
|
1734
3021
|
appendRecordIdByPreparedRecordId.set(entry.record.id, entry.record.id);
|
|
1735
|
-
|
|
3022
|
+
recordsByEntryIndex.set(entryIndex, entry.record);
|
|
1736
3023
|
continue;
|
|
1737
3024
|
}
|
|
3025
|
+
// A member declared current by this batch's authoritative set falls
|
|
3026
|
+
// through to the superseding append below, so the reassertion lands as
|
|
3027
|
+
// the next serialized event-spine revision over the retraction tombstone.
|
|
1738
3028
|
if (shouldKeepExistingJunctionSleepStageSummaryObservation(latest, entry.record)) {
|
|
1739
3029
|
if (eventSpineRevisionsAreComplete(index, latest.id)) {
|
|
1740
3030
|
retainedPreparedIds.add(entry.record.id);
|
|
1741
3031
|
}
|
|
1742
|
-
|
|
3032
|
+
recordsByEntryIndex.set(entryIndex, latest);
|
|
1743
3033
|
continue;
|
|
1744
3034
|
}
|
|
1745
3035
|
const historicalUserEditMatch = matchedEntries.find((match) => hasHistoricalExternalRefUserAuthoredChanges(match.indexedMatch));
|
|
1746
|
-
if (historicalUserEditMatch) {
|
|
1747
|
-
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", `Event externalRef "${externalRef.system}/${externalRef.resourceType}/${externalRef.resourceId}` +
|
|
1748
|
-
`${externalRef.facet ? `#${externalRef.facet}` : ""}" matched a historical provider identity ` +
|
|
1749
|
-
"whose latest event revision contains user-authored changes; merge must be repaired explicitly.");
|
|
1750
|
-
}
|
|
1751
3036
|
const revision = Math.max(eventSpineRevision(latest), index.maxRevisionById.get(latest.id) ?? 0) + 1;
|
|
1752
3037
|
const superseding = {
|
|
1753
3038
|
...entry.record,
|
|
1754
3039
|
id: latest.id,
|
|
1755
3040
|
lifecycle: buildEventSpineLifecycle(revision),
|
|
1756
3041
|
};
|
|
3042
|
+
const migratesRetainedMemberOccurrence = migratesJunctionStableProfileTimestamp
|
|
3043
|
+
&& indexedProviderMatch !== undefined
|
|
3044
|
+
&& latest.occurredAt === indexedProviderMatch.indexedRecord.occurredAt
|
|
3045
|
+
&& latest.dayKey === indexedProviderMatch.indexedRecord.dayKey;
|
|
3046
|
+
const retainedMemberRevision = historicalUserEditMatch
|
|
3047
|
+
? {
|
|
3048
|
+
...latest,
|
|
3049
|
+
...(migratesJunctionStableProfileTimestamp
|
|
3050
|
+
? {
|
|
3051
|
+
...(migratesRetainedMemberOccurrence
|
|
3052
|
+
? { occurredAt: entry.record.occurredAt, dayKey: entry.record.dayKey }
|
|
3053
|
+
: {}),
|
|
3054
|
+
externalRef,
|
|
3055
|
+
dataOrigin: entry.record.dataOrigin,
|
|
3056
|
+
}
|
|
3057
|
+
: migratesJunctionNoIdProfileIdentity
|
|
3058
|
+
? { externalRef, dataOrigin: entry.record.dataOrigin }
|
|
3059
|
+
: {}),
|
|
3060
|
+
lifecycle: buildEventSpineLifecycle(revision + 1),
|
|
3061
|
+
}
|
|
3062
|
+
: null;
|
|
3063
|
+
const retainedMemberPath = migratesRetainedMemberOccurrence
|
|
3064
|
+
? entry.relativePath
|
|
3065
|
+
: historicalUserEditMatch?.indexedMatch.relativePath
|
|
3066
|
+
|| toEventLedgerFile(latest.occurredAt);
|
|
1757
3067
|
forceAppendIds.add(latest.id);
|
|
1758
|
-
index.latestByRefKey.set(refKey,
|
|
3068
|
+
index.latestByRefKey.set(refKey, retainedMemberRevision
|
|
3069
|
+
? {
|
|
3070
|
+
indexedExternalRef: externalRef,
|
|
3071
|
+
indexedRecord: superseding,
|
|
3072
|
+
relativePath: retainedMemberPath,
|
|
3073
|
+
record: retainedMemberRevision,
|
|
3074
|
+
}
|
|
3075
|
+
: toIndexedExternalRefMatch(superseding, externalRef));
|
|
1759
3076
|
for (const { refKey: matchedRefKey, indexedMatch } of matchedEntries) {
|
|
1760
3077
|
const currentMatch = index.latestByRefKey.get(matchedRefKey);
|
|
1761
3078
|
if (matchedRefKey !== refKey &&
|
|
@@ -1764,12 +3081,87 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1764
3081
|
index.latestByRefKey.delete(matchedRefKey);
|
|
1765
3082
|
}
|
|
1766
3083
|
}
|
|
1767
|
-
index.maxRevisionById.set(latest.id, revision);
|
|
3084
|
+
index.maxRevisionById.set(latest.id, retainedMemberRevision ? revision + 1 : revision);
|
|
1768
3085
|
appendEntries.push({ relativePath: entry.relativePath, record: superseding });
|
|
3086
|
+
if (retainedMemberRevision) {
|
|
3087
|
+
appendEntries.push({ relativePath: retainedMemberPath, record: retainedMemberRevision });
|
|
3088
|
+
}
|
|
1769
3089
|
appendRecordIdByPreparedRecordId.set(entry.record.id, superseding.id);
|
|
1770
|
-
|
|
3090
|
+
recordsByEntryIndex.set(entryIndex, retainedMemberRevision ?? superseding);
|
|
1771
3091
|
supersededCount += 1;
|
|
1772
3092
|
}
|
|
3093
|
+
for (const set of authoritativeEventSets) {
|
|
3094
|
+
const ownsFacet = (facet) => set.facetPrefixes.some((prefix) => facet === prefix || facet.startsWith(`${prefix}-`));
|
|
3095
|
+
const candidates = [...index.latestByRefKey.entries()].filter(([, match]) => {
|
|
3096
|
+
const externalRef = match.record.externalRef;
|
|
3097
|
+
return externalRef?.system === set.system
|
|
3098
|
+
&& externalRef.resourceType === set.resourceType
|
|
3099
|
+
&& externalRef.resourceId === set.resourceId
|
|
3100
|
+
&& typeof externalRef.facet === "string"
|
|
3101
|
+
&& ownsFacet(externalRef.facet)
|
|
3102
|
+
&& !set.currentFacets.has(externalRef.facet)
|
|
3103
|
+
&& !isDeletedEventSpineRecord(match.record);
|
|
3104
|
+
});
|
|
3105
|
+
for (const [refKey, latestMatch] of candidates) {
|
|
3106
|
+
const latest = latestMatch.record;
|
|
3107
|
+
const latestRef = latest.externalRef;
|
|
3108
|
+
if (!latestRef?.facet) {
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
const incomingRef = { ...latestRef, version: set.version };
|
|
3112
|
+
const sourceVersionComparison = compareIncomingExternalRefVersion(latestMatch.indexedExternalRef, incomingRef);
|
|
3113
|
+
if (sourceVersionComparison !== null && sourceVersionComparison < 0) {
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
if (sourceVersionComparison === 0
|
|
3117
|
+
&& isDeletedEventSpineRecord(latestMatch.indexedRecord)) {
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
if (sourceVersionComparison === 0) {
|
|
3121
|
+
throw new VaultError("EVENT_SOURCE_REVISION_CONFLICT", `Authoritative device event externalRef "${set.system}/${set.resourceType}/` +
|
|
3122
|
+
`${set.resourceId}#${latestRef.facet}" has conflicting membership for source revision ` +
|
|
3123
|
+
`"${set.version}"; nothing was imported.`);
|
|
3124
|
+
}
|
|
3125
|
+
const hasMemberEdit = hasHistoricalExternalRefUserAuthoredChanges(latestMatch);
|
|
3126
|
+
const revision = Math.max(eventSpineRevision(latest), index.maxRevisionById.get(latest.id) ?? 0) + 1;
|
|
3127
|
+
const tombstone = {
|
|
3128
|
+
...latestMatch.indexedRecord,
|
|
3129
|
+
source: "device",
|
|
3130
|
+
recordedAt: set.version,
|
|
3131
|
+
externalRef: incomingRef,
|
|
3132
|
+
lifecycle: buildEventSpineLifecycle(revision, "deleted"),
|
|
3133
|
+
};
|
|
3134
|
+
const retainedMemberRevision = hasMemberEdit
|
|
3135
|
+
? {
|
|
3136
|
+
...latest,
|
|
3137
|
+
lifecycle: buildEventSpineLifecycle(revision + 1),
|
|
3138
|
+
}
|
|
3139
|
+
: null;
|
|
3140
|
+
const latestPath = latestMatch.relativePath || toEventLedgerFile(latest.occurredAt);
|
|
3141
|
+
forceAppendIds.add(latest.id);
|
|
3142
|
+
index.latestByRefKey.set(refKey, retainedMemberRevision
|
|
3143
|
+
? {
|
|
3144
|
+
indexedExternalRef: incomingRef,
|
|
3145
|
+
indexedRecord: tombstone,
|
|
3146
|
+
relativePath: latestPath,
|
|
3147
|
+
record: retainedMemberRevision,
|
|
3148
|
+
}
|
|
3149
|
+
: toIndexedExternalRefMatch(tombstone, incomingRef, latestPath));
|
|
3150
|
+
index.maxRevisionById.set(latest.id, retainedMemberRevision ? revision + 1 : revision);
|
|
3151
|
+
appendEntries.push({ relativePath: latestPath, record: tombstone });
|
|
3152
|
+
if (retainedMemberRevision) {
|
|
3153
|
+
appendEntries.push({ relativePath: latestPath, record: retainedMemberRevision });
|
|
3154
|
+
}
|
|
3155
|
+
retractedCount += 1;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
const records = entries.map((entry, entryIndex) => {
|
|
3159
|
+
const record = recordsByEntryIndex.get(entryIndex);
|
|
3160
|
+
if (!record) {
|
|
3161
|
+
throw new VaultError("EVENT_EXTERNAL_REF_ALIAS_CONFLICT", `Device event reconciliation did not produce a result for "${entry.record.id}".`);
|
|
3162
|
+
}
|
|
3163
|
+
return record;
|
|
3164
|
+
});
|
|
1773
3165
|
return {
|
|
1774
3166
|
appendEntries,
|
|
1775
3167
|
appendRecordIdByPreparedRecordId,
|
|
@@ -1778,6 +3170,7 @@ async function reconcileDeviceEventEntriesByExternalRef(vaultRoot, entries, exis
|
|
|
1778
3170
|
retainedPreparedIds,
|
|
1779
3171
|
skippedDuplicateCount,
|
|
1780
3172
|
supersededCount,
|
|
3173
|
+
retractedCount,
|
|
1781
3174
|
};
|
|
1782
3175
|
}
|
|
1783
3176
|
// Public bulk import reconciles externalRef identity vault-wide, not per
|
|
@@ -2147,6 +3540,9 @@ function buildDeviceBatchAuditSummary(input) {
|
|
|
2147
3540
|
if (input.supersededCount > 0) {
|
|
2148
3541
|
dedupeNotes.push(`${input.supersededCount} event(s) updated in place by externalRef`);
|
|
2149
3542
|
}
|
|
3543
|
+
if (input.retractedCount > 0) {
|
|
3544
|
+
dedupeNotes.push(`${input.retractedCount} omitted authoritative event(s) retracted`);
|
|
3545
|
+
}
|
|
2150
3546
|
const dedupeSuffix = dedupeNotes.length > 0 ? ` (${dedupeNotes.join(", ")})` : "";
|
|
2151
3547
|
return `Imported ${input.provider} device batch with ${input.eventCount} event(s) and ${input.sampleCount} sample(s)${dedupeSuffix}.`;
|
|
2152
3548
|
}
|
|
@@ -2178,7 +3574,7 @@ function buildLegacyReceiptFingerprint(receipt, provider) {
|
|
|
2178
3574
|
sha256: createHash("sha256").update(content).digest("hex"),
|
|
2179
3575
|
}];
|
|
2180
3576
|
}
|
|
2181
|
-
function prepareDeviceBatchPlan({ provider, accountId, importedAt = new Date(), defaultTimeZone: fallbackTimeZone, source = "device", events = [], samples = [], evidenceParts = [], ingestReceipt, provenance, }) {
|
|
3577
|
+
function prepareDeviceBatchPlan({ provider, accountId, importedAt = new Date(), defaultTimeZone: fallbackTimeZone, source = "device", events = [], samples = [], evidenceParts = [], authoritativeEventSets = [], ingestReceipt, provenance, }) {
|
|
2182
3578
|
const normalizedInputs = normalizeDeviceBatchInputs({
|
|
2183
3579
|
provider,
|
|
2184
3580
|
accountId,
|
|
@@ -2188,6 +3584,7 @@ function prepareDeviceBatchPlan({ provider, accountId, importedAt = new Date(),
|
|
|
2188
3584
|
events,
|
|
2189
3585
|
samples,
|
|
2190
3586
|
evidenceParts,
|
|
3587
|
+
authoritativeEventSets,
|
|
2191
3588
|
ingestReceipt,
|
|
2192
3589
|
provenance,
|
|
2193
3590
|
});
|
|
@@ -2224,6 +3621,18 @@ function prepareDeviceBatchPlan({ provider, accountId, importedAt = new Date(),
|
|
|
2224
3621
|
receipt: compactedIngestReceipt ?? null,
|
|
2225
3622
|
eventIds: normalizedInputs.events.map(({ recordId }) => recordId),
|
|
2226
3623
|
sampleIds: normalizedInputs.samples.map(({ recordId }) => recordId),
|
|
3624
|
+
...(normalizedInputs.authoritativeEventSets.length === 0
|
|
3625
|
+
? {}
|
|
3626
|
+
: {
|
|
3627
|
+
authoritativeEventSets: normalizedInputs.authoritativeEventSets.map((set) => ({
|
|
3628
|
+
system: set.system,
|
|
3629
|
+
resourceType: set.resourceType,
|
|
3630
|
+
resourceId: set.resourceId,
|
|
3631
|
+
version: set.version,
|
|
3632
|
+
facetPrefixes: set.facetPrefixes,
|
|
3633
|
+
currentFacets: [...set.currentFacets].sort(),
|
|
3634
|
+
})),
|
|
3635
|
+
}),
|
|
2227
3636
|
evidenceParts: fingerprint,
|
|
2228
3637
|
}));
|
|
2229
3638
|
const importId = buildImportId(rawArtifactFingerprint);
|
|
@@ -2245,6 +3654,7 @@ function prepareDeviceBatchPlan({ provider, accountId, importedAt = new Date(),
|
|
|
2245
3654
|
evidenceRolesByPreparedRecordId: eventPlan.evidenceRolesByPreparedRecordId,
|
|
2246
3655
|
preparedSamples,
|
|
2247
3656
|
preparedEvidenceParts,
|
|
3657
|
+
authoritativeEventSets: normalizedInputs.authoritativeEventSets,
|
|
2248
3658
|
};
|
|
2249
3659
|
}
|
|
2250
3660
|
async function hashSourceFile(sourcePath) {
|
|
@@ -3212,7 +4622,8 @@ function preparedDeviceEventRetainsCurrentOutput(input) {
|
|
|
3212
4622
|
}
|
|
3213
4623
|
const current = input.currentEventOwners.currentRecordByPreparedId.get(preparedId);
|
|
3214
4624
|
return current !== undefined && (isDeletedEventSpineRecord(current)
|
|
3215
|
-
|| (
|
|
4625
|
+
|| (current.source !== "device"
|
|
4626
|
+
&& eventSpineRevision(current) > eventSpineRevision(input.entry.record)
|
|
3216
4627
|
&& userAuthoredEventStateKey(current) !== userAuthoredEventStateKey(input.entry.record)));
|
|
3217
4628
|
}
|
|
3218
4629
|
function storedIntegrationIngestRetainsCurrentOutputLocators(input) {
|
|
@@ -3339,7 +4750,28 @@ function buildStoredDeviceDeliveryNoopResult(input) {
|
|
|
3339
4750
|
auditPath: null,
|
|
3340
4751
|
};
|
|
3341
4752
|
}
|
|
3342
|
-
export async function importDeviceBatch(
|
|
4753
|
+
export async function importDeviceBatch(input, options = {}) {
|
|
4754
|
+
assertCanonicalWriteLockScope(input.vaultRoot);
|
|
4755
|
+
const startedAt = performance.now();
|
|
4756
|
+
const timing = {
|
|
4757
|
+
canonicalWriteElapsedMs: 0,
|
|
4758
|
+
eventIdentityIndexCacheHit: false,
|
|
4759
|
+
eventIdentityIndexElapsedMs: 0,
|
|
4760
|
+
totalElapsedMs: 0,
|
|
4761
|
+
};
|
|
4762
|
+
try {
|
|
4763
|
+
return await importDeviceBatchWithExecutionOptions(input, options, timing);
|
|
4764
|
+
}
|
|
4765
|
+
catch (error) {
|
|
4766
|
+
clearDeviceBatchImportSessionVaultState(options.session, input.vaultRoot);
|
|
4767
|
+
throw error;
|
|
4768
|
+
}
|
|
4769
|
+
finally {
|
|
4770
|
+
timing.totalElapsedMs = Math.max(0, performance.now() - startedAt);
|
|
4771
|
+
emitDeviceBatchImportTiming(options.onTiming, timing);
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
async function importDeviceBatchWithExecutionOptions({ vaultRoot, provider, accountId, importedAt = new Date(), source = "device", events = [], samples = [], evidenceParts = [], authoritativeEventSets = [], ingestReceipt, provenance, }, options, timing) {
|
|
3343
4775
|
assertDeviceSampleRowLimit(Array.isArray(samples) ? samples.length : 0);
|
|
3344
4776
|
const vault = await loadVault({ vaultRoot });
|
|
3345
4777
|
const deviceBatchPlan = prepareDeviceBatchPlan({
|
|
@@ -3351,17 +4783,66 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3351
4783
|
events,
|
|
3352
4784
|
samples,
|
|
3353
4785
|
evidenceParts,
|
|
4786
|
+
authoritativeEventSets,
|
|
3354
4787
|
ingestReceipt,
|
|
3355
4788
|
provenance,
|
|
3356
4789
|
});
|
|
3357
|
-
const eventTargetShardPaths = [
|
|
3358
|
-
...new Set(deviceBatchPlan.preparedEvents.map((entry) => entry.relativePath)),
|
|
3359
|
-
].sort();
|
|
3360
4790
|
const sampleRecords = deviceBatchPlan.preparedSamples.map((entry) => entry.record);
|
|
3361
4791
|
const sampleAppendPlan = await buildJsonlAppendPlan(vaultRoot, deviceBatchPlan.preparedSamples, {
|
|
3362
4792
|
dedupeWithinPlan: true,
|
|
3363
4793
|
});
|
|
3364
|
-
const
|
|
4794
|
+
const requiresEventIdentityContext = deviceBatchPlan.preparedEvents.length > 0
|
|
4795
|
+
|| deviceBatchPlan.authoritativeEventSets.length > 0;
|
|
4796
|
+
const eventIdentityDependency = buildDeviceEventIdentityDependency(deviceBatchPlan);
|
|
4797
|
+
const indexStartedAt = performance.now();
|
|
4798
|
+
const sessionVaultState = requiresEventIdentityContext
|
|
4799
|
+
? readDeviceBatchImportSessionVaultState(options.session, vaultRoot)
|
|
4800
|
+
: undefined;
|
|
4801
|
+
const currentEventLedgerFingerprint = sessionVaultState
|
|
4802
|
+
? await tryBuildDeviceEventLedgerFingerprint(vaultRoot)
|
|
4803
|
+
: undefined;
|
|
4804
|
+
const sessionFingerprintMatches = sessionVaultState
|
|
4805
|
+
&& currentEventLedgerFingerprint !== undefined
|
|
4806
|
+
? sessionVaultState.eventLedgerFingerprint
|
|
4807
|
+
=== currentEventLedgerFingerprint
|
|
4808
|
+
: false;
|
|
4809
|
+
const cachedEventIdentityContext = sessionVaultState
|
|
4810
|
+
&& sessionFingerprintMatches
|
|
4811
|
+
&& sessionVaultState.dependencyChanges.every((dependencyChange) => !deviceEventIdentityDependenciesOverlap(eventIdentityDependency, dependencyChange))
|
|
4812
|
+
? cloneDeviceEventIdentityContext(sessionVaultState.eventIdentityContext)
|
|
4813
|
+
: undefined;
|
|
4814
|
+
let initialEventIdentityContext = cachedEventIdentityContext
|
|
4815
|
+
?? await buildDeviceEventIdentityContext(vaultRoot, deviceBatchPlan.preparedEvents, deviceBatchPlan.authoritativeEventSets);
|
|
4816
|
+
if (cachedEventIdentityContext
|
|
4817
|
+
&& deviceBatchPlan.preparedEvents.some((entry) => resolveStructuralJunctionDailyAggregateAliasOwnerSplit(entry, cachedEventIdentityContext.index) !== null)) {
|
|
4818
|
+
initialEventIdentityContext = await buildDeviceEventIdentityContext(vaultRoot, deviceBatchPlan.preparedEvents, deviceBatchPlan.authoritativeEventSets);
|
|
4819
|
+
}
|
|
4820
|
+
else if (cachedEventIdentityContext) {
|
|
4821
|
+
timing.eventIdentityIndexCacheHit = true;
|
|
4822
|
+
}
|
|
4823
|
+
timing.eventIdentityIndexElapsedMs = Math.max(0, performance.now() - indexStartedAt);
|
|
4824
|
+
if (requiresEventIdentityContext && !timing.eventIdentityIndexCacheHit) {
|
|
4825
|
+
const eventLedgerFingerprint = await tryBuildDeviceEventLedgerFingerprint(vaultRoot);
|
|
4826
|
+
if (eventLedgerFingerprint) {
|
|
4827
|
+
replaceDeviceBatchImportSessionVaultState(options.session, vaultRoot, {
|
|
4828
|
+
dependencyChanges: [],
|
|
4829
|
+
eventIdentityContext: cloneDeviceEventIdentityContext(initialEventIdentityContext),
|
|
4830
|
+
eventLedgerFingerprint,
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
else {
|
|
4834
|
+
clearDeviceBatchImportSessionVaultState(options.session, vaultRoot);
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
deviceBatchPlan.preparedEvents = resolveJunctionFloatingFallbackEntries(deviceBatchPlan.preparedEvents, initialEventIdentityContext);
|
|
4838
|
+
const eventIdentityContext = {
|
|
4839
|
+
index: initialEventIdentityContext.index,
|
|
4840
|
+
legacyReservations: buildLegacyExternalRefReservations(deviceBatchPlan.preparedEvents, initialEventIdentityContext.index),
|
|
4841
|
+
};
|
|
4842
|
+
const aliasRepairContext = buildDeviceEventAliasRepairContext(deviceBatchPlan);
|
|
4843
|
+
const eventTargetShardPaths = [
|
|
4844
|
+
...new Set(deviceBatchPlan.preparedEvents.map((entry) => entry.relativePath)),
|
|
4845
|
+
].sort();
|
|
3365
4846
|
const currentEventOwners = mapCurrentDeviceEventOwners(deviceBatchPlan.preparedEvents, eventIdentityContext);
|
|
3366
4847
|
const baselineEventReconciliation = await reconcileDeviceEventEntriesByExternalRef(vaultRoot, deviceBatchPlan.preparedEvents, buildEmptyDeviceEventIdentityContext(deviceBatchPlan.preparedEvents));
|
|
3367
4848
|
const baselineRetainedPreparedIds = new Set(baselineEventReconciliation.appendRecordIdByPreparedRecordId.keys());
|
|
@@ -3376,6 +4857,7 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3376
4857
|
});
|
|
3377
4858
|
const protectedPreparedEventIds = new Set(deviceBatchPlan.preparedEvents
|
|
3378
4859
|
.filter((entry) => baselineRetainedPreparedIds.has(entry.record.id)
|
|
4860
|
+
&& !hasJunctionDailyAggregateAliasSplit(entry, eventIdentityContext)
|
|
3379
4861
|
&& (currentEventOwners.historicallyDeliveredPreparedIds.has(entry.record.id)
|
|
3380
4862
|
|| (entry.record.externalRef === undefined
|
|
3381
4863
|
&& currentEventOwners.currentRecordByPreparedId.has(entry.record.id)
|
|
@@ -3384,7 +4866,7 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3384
4866
|
.map((entry) => entry.record.id));
|
|
3385
4867
|
const ordinaryReconciliationEntries = deviceBatchPlan.preparedEvents.filter((entry) => baselineRetainedPreparedIds.has(entry.record.id)
|
|
3386
4868
|
&& !protectedPreparedEventIds.has(entry.record.id));
|
|
3387
|
-
const currentEventReconciliation = await reconcileDeviceEventEntriesByExternalRef(vaultRoot, ordinaryReconciliationEntries, cloneDeviceEventIdentityContext(eventIdentityContext));
|
|
4869
|
+
const currentEventReconciliation = await reconcileDeviceEventEntriesByExternalRef(vaultRoot, ordinaryReconciliationEntries, cloneDeviceEventIdentityContext(eventIdentityContext), new Map(), deviceBatchPlan.authoritativeEventSets, aliasRepairContext);
|
|
3388
4870
|
const replayRetainedPreparedIds = new Set([
|
|
3389
4871
|
...protectedPreparedEventIds,
|
|
3390
4872
|
...currentEventReconciliation.retainedPreparedIds,
|
|
@@ -3693,7 +5175,7 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3693
5175
|
const persistenceReconciliationEntries = reconciliationEntries.filter((entry) => !storedEventRepairIds.has(entry.record.id));
|
|
3694
5176
|
const repairedProtectedPreparedEventCount = [...historicalRepairEntryByPreparedId.keys()].filter((preparedId) => persistenceProtectedPreparedEventIds.has(preparedId)).length;
|
|
3695
5177
|
const reconciled = evidenceRepairRequired
|
|
3696
|
-
? await reconcileDeviceEventEntriesByExternalRef(vaultRoot, persistenceReconciliationEntries, cloneDeviceEventIdentityContext(eventIdentityContext))
|
|
5178
|
+
? await reconcileDeviceEventEntriesByExternalRef(vaultRoot, persistenceReconciliationEntries, cloneDeviceEventIdentityContext(eventIdentityContext), new Map(), deviceBatchPlan.authoritativeEventSets, aliasRepairContext)
|
|
3697
5179
|
: currentEventReconciliation;
|
|
3698
5180
|
const reconciledRecordByPreparedId = new Map(persistenceReconciliationEntries.map((entry, index) => [
|
|
3699
5181
|
entry.record.id,
|
|
@@ -3767,7 +5249,7 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3767
5249
|
]));
|
|
3768
5250
|
const persistencePreparedEvents = deviceBatchPlan.preparedEvents.filter((entry) => associablePreparedEventIds.has(entry.record.id)
|
|
3769
5251
|
&& persistenceEvidenceRolesByPreparedRecordId.has(entry.record.id));
|
|
3770
|
-
const hasAppendedOutputs =
|
|
5252
|
+
const hasAppendedOutputs = eventAppendPlan.appendedRecordIds.length > 0
|
|
3771
5253
|
|| sampleAppendPlan.appendedRecordIds.length > 0;
|
|
3772
5254
|
const eventIdsByEvidenceRole = new Map();
|
|
3773
5255
|
for (const entry of deviceBatchPlan.preparedEvents) {
|
|
@@ -3936,7 +5418,8 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3936
5418
|
assertInspectionCanAuthorizeNoop();
|
|
3937
5419
|
return buildNoopResult();
|
|
3938
5420
|
}
|
|
3939
|
-
|
|
5421
|
+
const canonicalWriteStartedAt = performance.now();
|
|
5422
|
+
const result = await runCanonicalWrite({
|
|
3940
5423
|
vaultRoot,
|
|
3941
5424
|
operationType: "device_batch_import",
|
|
3942
5425
|
summary: `Import ${deviceBatchPlan.provider} device batch ${persistedImportId}`,
|
|
@@ -3961,6 +5444,7 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3961
5444
|
sampleCount: sampleRecords.length,
|
|
3962
5445
|
skippedDuplicateCount: eventReconciliation.skippedDuplicateCount,
|
|
3963
5446
|
supersededCount: eventReconciliation.supersededCount,
|
|
5447
|
+
retractedCount: eventReconciliation.retractedCount,
|
|
3964
5448
|
}),
|
|
3965
5449
|
occurredAt: deviceBatchPlan.importedAt,
|
|
3966
5450
|
files: touchedPaths,
|
|
@@ -3978,7 +5462,12 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3978
5462
|
importedAt: deviceBatchPlan.importedAt,
|
|
3979
5463
|
events: canonicalEventRecords,
|
|
3980
5464
|
samples: sampleRecords,
|
|
3981
|
-
eventShardPaths:
|
|
5465
|
+
eventShardPaths: [
|
|
5466
|
+
...new Set([
|
|
5467
|
+
...eventTargetShardPaths,
|
|
5468
|
+
...eventAppendPlan.targetShardPaths,
|
|
5469
|
+
]),
|
|
5470
|
+
].sort(),
|
|
3982
5471
|
sampleShardPaths: sampleAppendPlan.targetShardPaths,
|
|
3983
5472
|
evidencePartCount: deviceBatchPlan.preparedEvidenceParts.length,
|
|
3984
5473
|
persistedEvidencePartCount: retainedEvidenceParts.length,
|
|
@@ -3986,6 +5475,27 @@ export async function importDeviceBatch({ vaultRoot, provider, accountId, import
|
|
|
3986
5475
|
};
|
|
3987
5476
|
},
|
|
3988
5477
|
});
|
|
5478
|
+
timing.canonicalWriteElapsedMs = Math.max(0, performance.now() - canonicalWriteStartedAt);
|
|
5479
|
+
if (eventAppendPlan.appendedRecordIds.length > 0) {
|
|
5480
|
+
const currentSessionState = readDeviceBatchImportSessionVaultState(options.session, vaultRoot);
|
|
5481
|
+
if (currentSessionState) {
|
|
5482
|
+
const eventLedgerFingerprint = await tryBuildDeviceEventLedgerFingerprint(vaultRoot);
|
|
5483
|
+
if (eventLedgerFingerprint) {
|
|
5484
|
+
replaceDeviceBatchImportSessionVaultState(options.session, vaultRoot, {
|
|
5485
|
+
...currentSessionState,
|
|
5486
|
+
dependencyChanges: [
|
|
5487
|
+
...currentSessionState.dependencyChanges,
|
|
5488
|
+
buildDeviceEventIdentityDependency(deviceBatchPlan, eventReconciliation.records.map((record) => record.id)),
|
|
5489
|
+
],
|
|
5490
|
+
eventLedgerFingerprint,
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
else {
|
|
5494
|
+
clearDeviceBatchImportSessionVaultState(options.session, vaultRoot);
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
return result;
|
|
3989
5499
|
}
|
|
3990
5500
|
const EVENT_BATCH_FAILURE_REPORT_LIMIT = 20;
|
|
3991
5501
|
// importEventBatch is public API surface, so the batch-shape invariants live
|