@ornexus/neocortex 4.60.73 → 4.60.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/release-source.json +1 -1
- package/dist/sbom.cdx.json +1216 -1210
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -2
- package/packages/client/dist/adapters/adapter-registry.js +1 -108
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
- package/packages/client/dist/adapters/codex-adapter.js +2 -80
- package/packages/client/dist/adapters/cursor-adapter.js +4 -115
- package/packages/client/dist/adapters/gemini-adapter.js +2 -71
- package/packages/client/dist/adapters/grok-adapter.js +2 -42
- package/packages/client/dist/adapters/index.js +1 -22
- package/packages/client/dist/adapters/platform-detector.js +1 -212
- package/packages/client/dist/adapters/target-adapter.js +0 -12
- package/packages/client/dist/adapters/vscode-adapter.js +2 -72
- package/packages/client/dist/agent/refresh-stubs.js +2 -417
- package/packages/client/dist/agent/update-agent-yaml.js +1 -102
- package/packages/client/dist/agent/update-description.js +1 -251
- package/packages/client/dist/cache/crypto-utils.js +1 -78
- package/packages/client/dist/cache/encrypted-cache.d.ts +27 -0
- package/packages/client/dist/cache/encrypted-cache.js +1 -108
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
- package/packages/client/dist/cache/index.js +1 -13
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
- package/packages/client/dist/checkpoint/index.js +1 -11
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
- package/packages/client/dist/cli.js +5 -358
- package/packages/client/dist/commands/activate.js +8 -394
- package/packages/client/dist/commands/architecture-answers.js +7 -274
- package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
- package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
- package/packages/client/dist/commands/architecture-root-binding.js +1 -51
- package/packages/client/dist/commands/cache-status.js +2 -112
- package/packages/client/dist/commands/config.js +1 -88
- package/packages/client/dist/commands/doctor.js +2 -635
- package/packages/client/dist/commands/invoke.d.ts +38 -1
- package/packages/client/dist/commands/invoke.js +143 -7200
- package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
- package/packages/client/dist/commands/legacy-story-branch.js +1 -96
- package/packages/client/dist/commands/legacy-story-pr.js +1 -276
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
- package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
- package/packages/client/dist/commands/mutation-qa-git.js +1 -41
- package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
- package/packages/client/dist/commands/mutation-qa-run.js +3 -219
- package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
- package/packages/client/dist/commands/mutation-qa.js +1 -220
- package/packages/client/dist/commands/native-agent.js +2 -136
- package/packages/client/dist/commands/planning-diagnosis.js +1 -43
- package/packages/client/dist/commands/planning-generation.js +1 -53
- package/packages/client/dist/commands/planning-memory.js +1 -40
- package/packages/client/dist/commands/planning-research.js +1 -42
- package/packages/client/dist/commands/private-session.js +1 -75
- package/packages/client/dist/commands/property-qa.js +1 -139
- package/packages/client/dist/commands/qa-control.js +1 -16
- package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
- package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
- package/packages/client/dist/commands/qa-scan-capture.js +5 -663
- package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
- package/packages/client/dist/commands/qa-scan-review.js +5 -826
- package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
- package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
- package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
- package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
- package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
- package/packages/client/dist/commands/refactor-characterization.js +21 -671
- package/packages/client/dist/commands/refactor-commit.js +13 -399
- package/packages/client/dist/commands/refactor-control.js +3 -142
- package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
- package/packages/client/dist/commands/refactor-review.js +5 -283
- package/packages/client/dist/commands/refactor-root.js +7 -588
- package/packages/client/dist/commands/refresh-memory.js +3 -156
- package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
- package/packages/client/dist/commands/story-checkpoint.js +1 -171
- package/packages/client/dist/config/host-auth-broker.d.ts +31 -0
- package/packages/client/dist/config/host-auth-broker.js +1 -0
- package/packages/client/dist/config/host-auth-reference.d.ts +32 -0
- package/packages/client/dist/config/host-auth-reference.js +1 -0
- package/packages/client/dist/config/profile-directory.js +1 -13
- package/packages/client/dist/config/resolver-selection.js +1 -278
- package/packages/client/dist/config/secure-config.js +12 -281
- package/packages/client/dist/config.js +2 -223
- package/packages/client/dist/constants.js +1 -25
- package/packages/client/dist/context/context-collector.js +2 -222
- package/packages/client/dist/context/context-sanitizer.js +1 -145
- package/packages/client/dist/continuity/autonomous-governance.js +1 -285
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
- package/packages/client/dist/continuity/invoke-hooks.js +1 -129
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
- package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
- package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
- package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
- package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
- package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
- package/packages/client/dist/continuity/native-agent-store.js +1 -69
- package/packages/client/dist/continuity/native-agent-types.d.ts +11 -0
- package/packages/client/dist/continuity/native-agent-types.js +1 -90
- package/packages/client/dist/continuity/outbox.js +1 -117
- package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
- package/packages/client/dist/continuity/sqlite-store.js +81 -2612
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
- package/packages/client/dist/errors/error-messages.d.ts +3 -0
- package/packages/client/dist/errors/error-messages.js +2 -228
- package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
- package/packages/client/dist/evidence/collector.js +1 -347
- package/packages/client/dist/evidence/index.js +1 -1
- package/packages/client/dist/evidence/types.js +0 -2
- package/packages/client/dist/fs/atomic-state-replace.js +1 -152
- package/packages/client/dist/generated/command-registry.generated.js +1 -4076
- package/packages/client/dist/generated/public-projections.generated.js +1 -149
- package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
- package/packages/client/dist/git/direct-yolo-checkpoint.d.ts +11 -1
- package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
- package/packages/client/dist/git/epic-operational-state.js +2 -672
- package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
- package/packages/client/dist/git/git-lifecycle-store.d.ts +23 -0
- package/packages/client/dist/git/git-lifecycle-store.js +10 -1981
- package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
- package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
- package/packages/client/dist/git/legacy-story-pr.js +70 -3538
- package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
- package/packages/client/dist/i18n/first-run.js +2 -60
- package/packages/client/dist/index.d.ts +2 -0
- package/packages/client/dist/index.js +1 -47
- package/packages/client/dist/invocation/invoke-input.js +1 -24
- package/packages/client/dist/license/host-auth-recovery.d.ts +158 -0
- package/packages/client/dist/license/host-auth-recovery.js +1 -0
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.d.ts +10 -1
- package/packages/client/dist/license/license-client.js +1 -304
- package/packages/client/dist/machine/fingerprint.js +2 -163
- package/packages/client/dist/machine/index.js +1 -5
- package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
- package/packages/client/dist/memory/project-memory-writer.js +40 -1007
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
- package/packages/client/dist/migrations/epic-lifecycle-backfill.js +3 -1060
- package/packages/client/dist/migrations/project-identity-capability.js +1 -50
- package/packages/client/dist/migrations/project-identity.js +7 -493
- package/packages/client/dist/migrations/state-json.js +4 -393
- package/packages/client/dist/policy/architecture-policy.js +2 -184
- package/packages/client/dist/policy/index.js +1 -7
- package/packages/client/dist/policy/shared-policy-types.js +0 -38
- package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
- package/packages/client/dist/private-execution/clean-baseline.js +2 -279
- package/packages/client/dist/private-execution/codex-executor.js +4 -574
- package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
- package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
- package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
- package/packages/client/dist/private-execution/delivery.js +13 -410
- package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
- package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
- package/packages/client/dist/private-execution/implementation-validation.js +2 -106
- package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
- package/packages/client/dist/private-execution/isolated-session.js +5 -725
- package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
- package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
- package/packages/client/dist/private-execution/pact-test-source.js +1 -258
- package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
- package/packages/client/dist/private-execution/pact-write-set.js +1 -172
- package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
- package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
- package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
- package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
- package/packages/client/dist/private-execution/private-report-store.js +1 -42
- package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
- package/packages/client/dist/private-execution/property-test-choices.js +3 -147
- package/packages/client/dist/private-execution/property-test-plan.js +7 -271
- package/packages/client/dist/private-execution/property-test-review.js +4 -258
- package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
- package/packages/client/dist/private-execution/property-test-source.js +1 -139
- package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
- package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
- package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
- package/packages/client/dist/private-execution/root-memory-request.js +1 -29
- package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
- package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
- package/packages/client/dist/private-execution/session-lease.js +1 -63
- package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
- package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
- package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
- package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
- package/packages/client/dist/private-execution/ux-write-set.js +2 -191
- package/packages/client/dist/registry/registry-reconciliation.js +1 -234
- package/packages/client/dist/resilience/circuit-breaker.js +1 -170
- package/packages/client/dist/resilience/degradation-manager.js +1 -164
- package/packages/client/dist/resilience/freshness-indicator.js +1 -100
- package/packages/client/dist/resilience/index.js +1 -8
- package/packages/client/dist/resilience/recovery-detector.js +1 -74
- package/packages/client/dist/resolvers/asset-resolver.js +0 -13
- package/packages/client/dist/resolvers/local-resolver.js +8 -255
- package/packages/client/dist/resolvers/remote-resolver.js +1 -233
- package/packages/client/dist/runner/binary.js +1 -404
- package/packages/client/dist/runner/cli.js +17 -1571
- package/packages/client/dist/runner/epic-background-dispatcher.d.ts +61 -0
- package/packages/client/dist/runner/epic-background-dispatcher.js +1 -0
- package/packages/client/dist/runner/headless/adapters.js +1 -49
- package/packages/client/dist/runner/headless/background-root.d.ts +65 -0
- package/packages/client/dist/runner/headless/background-root.js +1 -0
- package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
- package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.d.ts +1 -1
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.d.ts +2 -0
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +10 -1236
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.d.ts +27 -2
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
- package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
- package/packages/client/dist/runner/headless/managed-environment-cleanup.d.ts +95 -0
- package/packages/client/dist/runner/headless/managed-environment-cleanup.js +1 -0
- package/packages/client/dist/runner/headless/managed-profile.js +1 -132
- package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
- package/packages/client/dist/runner/headless/resolver.js +1 -231
- package/packages/client/dist/runner/headless/run-yolo.js +1 -885
- package/packages/client/dist/runner/headless/run-yoloop.d.ts +1 -0
- package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
- package/packages/client/dist/runner/headless/types.js +2 -574
- package/packages/client/dist/runner/local-console.js +13 -396
- package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
- package/packages/client/dist/runner/project-identity.js +1 -106
- package/packages/client/dist/runner/scheduler.js +7 -995
- package/packages/client/dist/runner-cli.js +2 -18
- package/packages/client/dist/state/invocation-shape.js +1 -13
- package/packages/client/dist/state/project-state-snapshot.d.ts +7 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1731
- package/packages/client/dist/state/state-json-repair.js +3 -785
- package/packages/client/dist/state/structural-dag-projection.js +1 -118
- package/packages/client/dist/telemetry/index.js +1 -5
- package/packages/client/dist/telemetry/offline-queue.js +1 -131
- package/packages/client/dist/tier/index.js +1 -5
- package/packages/client/dist/tier/tier-aware-client.js +1 -405
- package/packages/client/dist/types/index.js +1 -38
- package/packages/client/dist/version.js +1 -27
- package/packages/client/dist/yolo/change-evidence-collector.d.ts +21 -1
- package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
- package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
- package/packages/client/dist/yolo/spec-envelope-collector.js +5 -184
- package/packages/client/dist/yoloop/discovery-hook.js +2 -300
- package/packages/client/dist/yoloop/index.js +1 -8
- package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
- package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
- package/packages/client/dist/yoloop/target-normalization.js +1 -180
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1920
- package/packages/shared/dist/invocation/explicit-intent-recovery.d.ts +83 -0
- package/packages/shared/dist/invocation/explicit-intent-recovery.js +187 -0
- package/packages/shared/dist/invocation/index.d.ts +7 -0
- package/packages/shared/dist/invocation/index.js +22 -0
- package/packages/shared/dist/yolo/change-evidence.js +1 -1
- package/packages/shared/dist/yoloop/epic-terminal-git.js +144 -2
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +24 -21
- package/targets-stubs/codex/README.md +6 -1
- package/targets-stubs/codex/neocortex.toml +7 -2
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +16 -1
- package/targets-stubs/opencode/neocortex.md +6 -0
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -1,1262 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license FSL-1.1
|
|
3
|
-
* Copyright (c) 2026 OrNexus AI
|
|
4
|
-
*
|
|
5
|
-
* P180.04 -- Local SQLite continuity store, migrations and integrity checks.
|
|
6
|
-
*/
|
|
7
|
-
import { Buffer } from 'node:buffer';
|
|
8
|
-
import { createHash } from 'node:crypto';
|
|
9
|
-
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readSync } from 'node:fs';
|
|
10
|
-
import { dirname, join } from 'node:path';
|
|
11
|
-
import { INITIAL_CONTINUITY_SQLITE_MIGRATION } from './migrations/001-initial-schema.js';
|
|
12
|
-
import { PROCESSING_OUTPUT_SUMMARY_SQLITE_MIGRATION } from './migrations/002-processing-output-summary-indexes.js';
|
|
13
|
-
import { HEADLESS_EXECUTION_ATTEMPTS_SQLITE_MIGRATION } from './migrations/003-headless-execution-attempts.js';
|
|
14
|
-
import { OUTBOX_DELIVERY_STATE_SQLITE_MIGRATION } from './migrations/004-outbox-delivery-state.js';
|
|
15
|
-
import { BOUNDED_HEADLESS_QUEUE_SQLITE_MIGRATION } from './migrations/005-bounded-headless-queue.js';
|
|
16
|
-
import { NATIVE_AGENT_DISPATCHES_SQLITE_MIGRATION } from './migrations/006-native-agent-dispatches.js';
|
|
17
|
-
import { NATIVE_AGENT_TERMINAL_STATES, isNativeAgentDispatchReceiptV1, isNativeAgentStateTransitionAllowed, isNativeDispatchBindingV1, isNativeHostAttestationV1, } from './native-agent-types.js';
|
|
18
|
-
import { CONTINUITY_QUEUE_REASON_CODES, CONTINUITY_RECEIPT_COMPACTION_EVENT_TYPE, CONTINUITY_RECEIPT_RETENTION_REASON_CODES, DEFAULT_CONTINUITY_QUEUE_POLICY, computeContinuityQueueRetryAt, computeContinuityOutboxFailureDecision, normalizeContinuityQueuePolicy, normalizeContinuityQueuePriority, normalizeContinuityQueueScope, normalizeContinuityRetentionPolicy, } from './outbox.js';
|
|
19
|
-
export const CONTINUITY_SQLITE_RELATIVE_DIR = '.neocortex/continuity';
|
|
20
|
-
export const CONTINUITY_SQLITE_FILENAME = 'continuity.sqlite';
|
|
21
|
-
export const CONTINUITY_SQLITE_SCHEMA_VERSION = 6;
|
|
22
|
-
export const DEFAULT_CONTINUITY_SQLITE_BUSY_TIMEOUT_MS = 5_000;
|
|
23
|
-
export const CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE = 'processing_output_summary';
|
|
24
|
-
export const CONTINUITY_PROCESSING_OUTPUT_SUMMARY_MAX_CHARS = 2_000;
|
|
25
|
-
export const DEFAULT_CONTINUITY_PROCESSING_SUMMARY_LIMIT = 20;
|
|
26
|
-
export const MAX_CONTINUITY_PROCESSING_SUMMARY_LIMIT = 100;
|
|
27
|
-
export const SQLITE_ADAPTER_INSTALL_GUIDANCE = 'Install the optional Enterprise continuity SQLite adapter with `npm install --workspace packages/client better-sqlite3 --include=optional` or reinstall Neocortex with optional dependencies enabled.';
|
|
28
|
-
/**
|
|
29
|
-
* P195.04: Client-local mirror of the shared continuity process-stage contract
|
|
30
|
-
* (SSoT: `packages/shared/src/continuity/runner-state.ts` ->
|
|
31
|
-
* `CONTINUITY_PUBLIC_STAGES` / `CONTINUITY_STAGE_HISTORY_MAX`). The client keeps a
|
|
32
|
-
* local copy so continuity SQLite persistence has no runtime `@neocortex/shared`
|
|
33
|
-
* import; a drift test in `continuity-sqlite.test.ts` asserts parity with the
|
|
34
|
-
* shared SSoT. Values are an allow-list only -- unknown/arbitrary strings never
|
|
35
|
-
* persist or project as a stage.
|
|
36
|
-
*/
|
|
37
|
-
export const CONTINUITY_PUBLIC_STAGES = Object.freeze([
|
|
38
|
-
// Goal tick decision kinds.
|
|
39
|
-
'discover_more',
|
|
40
|
-
'create_epic',
|
|
41
|
-
'run_yoloop',
|
|
42
|
-
'run_yolo',
|
|
43
|
-
'run_verifier',
|
|
44
|
-
'run_review_gate',
|
|
45
|
-
'compact_context',
|
|
46
|
-
'wait_external',
|
|
47
|
-
'backoff',
|
|
48
|
-
'waiting_human',
|
|
49
|
-
'complete',
|
|
50
|
-
'abort',
|
|
51
|
-
// Loop lifecycle states.
|
|
52
|
-
'scheduled',
|
|
53
|
-
'running',
|
|
54
|
-
'overlap_skipped',
|
|
55
|
-
'missed_run_catchup',
|
|
56
|
-
'completed',
|
|
57
|
-
'aborted',
|
|
58
|
-
'cancelled',
|
|
59
|
-
'blocked',
|
|
60
|
-
// Pre-tick states (goal draft/planned before the first tick decision).
|
|
61
|
-
'draft',
|
|
62
|
-
'planned',
|
|
63
|
-
]);
|
|
64
|
-
/** P195.04: Maximum retained `stageHistory` entries (newest kept). ADR-P195-03. */
|
|
65
|
-
export const CONTINUITY_STAGE_HISTORY_MAX = 20;
|
|
66
|
-
const CONTINUITY_PUBLIC_STAGE_SET = new Set(CONTINUITY_PUBLIC_STAGES);
|
|
67
|
-
/** P195.04: Bounded ISO-8601 date-time prefix accepted for `stageHistory.at`. */
|
|
68
|
-
const CONTINUITY_STAGE_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,6})?)?(?:Z|[+-]\d{2}:?\d{2})?$/;
|
|
69
|
-
export function isContinuityPublicStage(value) {
|
|
70
|
-
return typeof value === 'string' && CONTINUITY_PUBLIC_STAGE_SET.has(value);
|
|
71
|
-
}
|
|
72
|
-
export function sanitizeContinuityStageTimestamp(value) {
|
|
73
|
-
if (typeof value !== 'string')
|
|
74
|
-
return undefined;
|
|
75
|
-
const trimmed = value.trim();
|
|
76
|
-
if (trimmed.length === 0 || trimmed.length > 40)
|
|
77
|
-
return undefined;
|
|
78
|
-
if (!CONTINUITY_STAGE_TIMESTAMP_PATTERN.test(trimmed))
|
|
79
|
-
return undefined;
|
|
80
|
-
return trimmed;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* P195.04: Validate an unknown value into a bounded, allow-listed stage history.
|
|
84
|
-
* Unknown stages and unsafe timestamps are dropped, and the newest
|
|
85
|
-
* `CONTINUITY_STAGE_HISTORY_MAX` entries are kept.
|
|
86
|
-
*/
|
|
87
|
-
export function sanitizeContinuityStageHistory(value) {
|
|
88
|
-
if (!Array.isArray(value))
|
|
89
|
-
return [];
|
|
90
|
-
const entries = [];
|
|
91
|
-
for (const item of value) {
|
|
92
|
-
if (!isRecord(item) || !isContinuityPublicStage(item.stage))
|
|
93
|
-
continue;
|
|
94
|
-
const at = sanitizeContinuityStageTimestamp(item.at);
|
|
95
|
-
if (!at)
|
|
96
|
-
continue;
|
|
97
|
-
entries.push({ stage: item.stage, at });
|
|
98
|
-
}
|
|
99
|
-
return entries.slice(-CONTINUITY_STAGE_HISTORY_MAX);
|
|
100
|
-
}
|
|
101
|
-
export function normalizeContinuityStageIteration(value) {
|
|
102
|
-
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0)
|
|
103
|
-
return undefined;
|
|
104
|
-
return Math.floor(value);
|
|
105
|
-
}
|
|
106
|
-
export class ContinuitySqliteStoreError extends Error {
|
|
107
|
-
code;
|
|
108
|
-
reasonCode;
|
|
109
|
-
guidance;
|
|
110
|
-
mutationBlocked;
|
|
111
|
-
cause;
|
|
112
|
-
constructor(code, message, options = {}) {
|
|
113
|
-
super(message);
|
|
114
|
-
this.name = 'ContinuitySqliteStoreError';
|
|
115
|
-
this.code = code;
|
|
116
|
-
this.reasonCode = code;
|
|
117
|
-
this.guidance = options.guidance;
|
|
118
|
-
this.cause = options.cause;
|
|
119
|
-
this.mutationBlocked = options.mutationBlocked ?? code === 'sqlite-integrity-failed';
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function toNativeAgentReceipt(row) {
|
|
123
|
-
const receipt = Object.freeze({
|
|
124
|
-
schemaVersion: 1,
|
|
125
|
-
storyId: row.story_id,
|
|
126
|
-
graphRevision: row.graph_revision,
|
|
127
|
-
dispatchKey: row.dispatch_key,
|
|
128
|
-
profile: row.profile,
|
|
129
|
-
parentHandle: row.parent_handle,
|
|
130
|
-
handle: row.handle,
|
|
131
|
-
topology: row.topology,
|
|
132
|
-
state: row.state,
|
|
133
|
-
requestedAt: row.requested_at,
|
|
134
|
-
observedAt: row.observed_at,
|
|
135
|
-
terminalAt: row.terminal_at,
|
|
136
|
-
freshness: row.freshness,
|
|
137
|
-
observationSource: row.observation_source,
|
|
138
|
-
reasonCode: row.reason_code,
|
|
139
|
-
revision: row.revision,
|
|
140
|
-
publicSafe: true,
|
|
141
|
-
});
|
|
142
|
-
if (!isNativeAgentDispatchReceiptV1(receipt)) {
|
|
143
|
-
throw new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Native-agent receipt failed its closed public contract.', { mutationBlocked: true });
|
|
144
|
-
}
|
|
145
|
-
return receipt;
|
|
146
|
-
}
|
|
147
|
-
const CONTINUITY_MIGRATIONS = [
|
|
148
|
-
INITIAL_CONTINUITY_SQLITE_MIGRATION,
|
|
149
|
-
PROCESSING_OUTPUT_SUMMARY_SQLITE_MIGRATION,
|
|
150
|
-
HEADLESS_EXECUTION_ATTEMPTS_SQLITE_MIGRATION,
|
|
151
|
-
OUTBOX_DELIVERY_STATE_SQLITE_MIGRATION,
|
|
152
|
-
BOUNDED_HEADLESS_QUEUE_SQLITE_MIGRATION,
|
|
153
|
-
NATIVE_AGENT_DISPATCHES_SQLITE_MIGRATION,
|
|
154
|
-
];
|
|
155
|
-
const CONTINUITY_TABLES = Object.freeze([
|
|
156
|
-
'continuity_migrations',
|
|
157
|
-
'jobs',
|
|
158
|
-
'job_leases',
|
|
159
|
-
'job_events',
|
|
160
|
-
'job_receipts',
|
|
161
|
-
'goal_campaigns',
|
|
162
|
-
'goal_ledger_items',
|
|
163
|
-
'goal_verifiers',
|
|
164
|
-
'goal_review_gates',
|
|
165
|
-
'loop_jobs',
|
|
166
|
-
'artifacts',
|
|
167
|
-
'state_snapshots',
|
|
168
|
-
'headless_execution_attempts',
|
|
169
|
-
'runner_config',
|
|
170
|
-
'outbox',
|
|
171
|
-
'native_agent_dispatches',
|
|
172
|
-
'native_agent_observations',
|
|
173
|
-
]);
|
|
174
|
-
const UNSAFE_METADATA_KEY_PATTERN = /(raw[_-]?(?:logs?|payloads?)|std(?:out|err)|stack(?:[_-]?trace)?|transcript|objective(?:[_-]?(?:text|body|prompt))|prompt(?:[_-]?(?:body|text))?|private[_-]?(?:urls?|prompts?)|secret|token|password|protected(?:[_-]?(?:field|body|bodies|internals))?|workflow(?:[_-]?body)?|step(?:[_-]?body)?|customer(?:[_-]?(?:pii|data))?|\bpii\b|authorization|api[_-]?key|license[_-]?key|argv|args|executable(?:[_-]?path)?|binary(?:[_-]?path)?|shell|cwd|command[_-]?path|vendor(?:[_-]?(?:payload|corpus|response|output)))/i;
|
|
175
|
-
const UNSAFE_METADATA_VALUE_PATTERN = /(https?:\/\/\S+|(?:^|\s)(?:\/(?:home|Users|media|mnt|tmp|var|etc|private)\/\S+|[A-Za-z]:\\\S+)|ghp_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]+|-----BEGIN [A-Z ]*PRIVATE KEY-----|authorization\s*:|password\s*=|token\s*=|api[_-]?key\s*=|license[_-]?key\s*=|raw[_ -]?(?:logs?|outputs?)\s*[:=]|transcript|stack\s*trace|stderr:|stdout:|system prompt|prompt[_ -]?(?:body|text)|workflow body|step body|\bargv\b|(?:bash|sh|zsh|powershell|pwsh)\s+-c\b|(?:\.{0,2}\/|[A-Za-z]:\\|\/)[^\s]*(?:\.exe|\.bat|\.cmd|\.ps1|\.sh|\/bin\/[^\s]+)|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b|PROTECTED_|PRIVATE_INTERNALS|CUSTOMER_PII|\bPII\b|VENDOR_CORPUS|P180[_-]?SYNTHETIC[_-]?CONTINUITY|P185[_-]?SYNTHETIC[_-]?CONTINUITY|P187[_-]?SYNTHETIC[_-]?(?:CONTINUITY|RUNNER|WATCH)|P190[_-]?SYNTHETIC[_-]?(?:HEADLESS|CONTINUITY)|P192[_-]?SYNTHETIC[_-]?(?:PORTAL|RUNNER|SYNC))/i;
|
|
176
|
-
const PUBLIC_REDACTION_REASON_VALUES = new Set([
|
|
177
|
-
'unsafe_key',
|
|
178
|
-
'raw_log',
|
|
179
|
-
'prompt_body',
|
|
180
|
-
'private_url',
|
|
181
|
-
'local_absolute_path',
|
|
182
|
-
'secret',
|
|
183
|
-
'protected_field',
|
|
184
|
-
'unbounded_text',
|
|
185
|
-
'unbounded_number',
|
|
186
|
-
'unknown_required_operation',
|
|
187
|
-
'missing',
|
|
188
|
-
]);
|
|
189
|
-
const SQLITE_CORRUPT_PATTERN = /(corrupt|malformed|not a database|file is not a database|database disk image is malformed)/i;
|
|
190
|
-
const SQLITE_FOREIGN_KEY_PATTERN = /(foreign key constraint failed|SQLITE_CONSTRAINT_FOREIGNKEY)/i;
|
|
191
|
-
const PROCESSING_SUMMARY_CURSOR_PREFIX = 'p185:v1:';
|
|
192
|
-
const CONTINUITY_PUBLIC_METADATA_ALLOWED_KEYS = new Set([
|
|
193
|
-
'schemaVersion',
|
|
194
|
-
'contractVersion',
|
|
195
|
-
'eventType',
|
|
196
|
-
'executionId',
|
|
197
|
-
'jobId',
|
|
198
|
-
'operationId',
|
|
199
|
-
'idempotencyKey',
|
|
200
|
-
'goalId',
|
|
201
|
-
'loopId',
|
|
202
|
-
'campaignId',
|
|
203
|
-
'itemId',
|
|
204
|
-
'ledgerItemIds',
|
|
205
|
-
'verifierId',
|
|
206
|
-
'verifierIds',
|
|
207
|
-
'gateId',
|
|
208
|
-
'reviewGateIds',
|
|
209
|
-
'runnerId',
|
|
210
|
-
'leaseId',
|
|
211
|
-
'status',
|
|
212
|
-
'reasonCode',
|
|
213
|
-
'reasonCodes',
|
|
214
|
-
'runtimeId',
|
|
215
|
-
'modelId',
|
|
216
|
-
'runtimeSource',
|
|
217
|
-
'headlessRuntime',
|
|
218
|
-
'headlessModel',
|
|
219
|
-
'headlessRuntimeReasonCode',
|
|
220
|
-
'originRuntime',
|
|
221
|
-
'originModel',
|
|
222
|
-
'targetRef',
|
|
223
|
-
'attemptId',
|
|
224
|
-
'attemptStatus',
|
|
225
|
-
'terminalClass',
|
|
226
|
-
'releaseBlocked',
|
|
227
|
-
'directiveHash',
|
|
228
|
-
'timeoutMs',
|
|
229
|
-
'leaseExpiresAt',
|
|
230
|
-
'heartbeatAt',
|
|
231
|
-
'notBeforeAt',
|
|
232
|
-
'maxAttempts',
|
|
233
|
-
'counts',
|
|
234
|
-
'hashes',
|
|
235
|
-
'relativePaths',
|
|
236
|
-
'artifactRefs',
|
|
237
|
-
'evidenceRefs',
|
|
238
|
-
'validationRefs',
|
|
239
|
-
'registryRefs',
|
|
240
|
-
'storyIds',
|
|
241
|
-
'epicIds',
|
|
242
|
-
'debtIds',
|
|
243
|
-
'iteration',
|
|
244
|
-
'currentStage',
|
|
245
|
-
'stageHistory',
|
|
246
|
-
'durationMs',
|
|
247
|
-
'retryCount',
|
|
248
|
-
'attemptCount',
|
|
249
|
-
'queuePriority',
|
|
250
|
-
'queueScope',
|
|
251
|
-
'queueCap',
|
|
252
|
-
'queueDepth',
|
|
253
|
-
'queueAvailable',
|
|
254
|
-
'queueBackpressure',
|
|
255
|
-
'nextTickAt',
|
|
256
|
-
'nextAttemptAt',
|
|
257
|
-
'nextRetryAt',
|
|
258
|
-
'deadLetterReasonCode',
|
|
259
|
-
'deliveryCursor',
|
|
260
|
-
'lastAttemptAt',
|
|
261
|
-
'sentAt',
|
|
262
|
-
'ackedAt',
|
|
263
|
-
'lastErrorReason',
|
|
264
|
-
'lastErrorSummary',
|
|
265
|
-
'deadLetterSummary',
|
|
266
|
-
'deadLetteredAt',
|
|
267
|
-
'processingOutputSource',
|
|
268
|
-
'publicSummary',
|
|
269
|
-
'redactionEvidence',
|
|
270
|
-
'selectorKind',
|
|
271
|
-
'targetId',
|
|
272
|
-
'targetJobId',
|
|
273
|
-
'relatedJobIds',
|
|
274
|
-
'serverAccessToLocalhost',
|
|
275
|
-
'serverHeldStream',
|
|
276
|
-
'initialCursor',
|
|
277
|
-
'targetResolutionState',
|
|
278
|
-
'targetKind',
|
|
279
|
-
'relatedJobCount',
|
|
280
|
-
'snapshotStatus',
|
|
281
|
-
'snapshotGeneratedAt',
|
|
282
|
-
'renderedSummaryCount',
|
|
283
|
-
'cursorState',
|
|
284
|
-
'currentCursor',
|
|
285
|
-
'nextCursor',
|
|
286
|
-
'cursorAdvanced',
|
|
287
|
-
'eventId',
|
|
288
|
-
'latestEventId',
|
|
289
|
-
'watchStatus',
|
|
290
|
-
'stopReason',
|
|
291
|
-
'syncVersion',
|
|
292
|
-
'transportKind',
|
|
293
|
-
'envelopeId',
|
|
294
|
-
'workspaceId',
|
|
295
|
-
'workspaceLabel',
|
|
296
|
-
'projectId',
|
|
297
|
-
'projectLabel',
|
|
298
|
-
'snapshotId',
|
|
299
|
-
'syncCursor',
|
|
300
|
-
'previousSyncCursor',
|
|
301
|
-
'nextSyncCursor',
|
|
302
|
-
'commandId',
|
|
303
|
-
'receiptId',
|
|
304
|
-
'acknowledgedAt',
|
|
305
|
-
'observedAt',
|
|
306
|
-
'generatedAt',
|
|
307
|
-
'syncStatus',
|
|
308
|
-
'commandStatus',
|
|
309
|
-
'receiptStatus',
|
|
310
|
-
'actorType',
|
|
311
|
-
'actorId',
|
|
312
|
-
'tenantId',
|
|
313
|
-
'commandKind',
|
|
314
|
-
'auditStatus',
|
|
315
|
-
'redactionCount',
|
|
316
|
-
'autonomousGovernance',
|
|
317
|
-
'governanceVersion',
|
|
318
|
-
'governanceEvidence',
|
|
319
|
-
'governanceSource',
|
|
320
|
-
'publicSafe',
|
|
321
|
-
]);
|
|
322
|
-
const CONTINUITY_PROCESSING_OUTPUT_SUMMARY_SOURCES = new Set([
|
|
323
|
-
'foreground_invoke',
|
|
324
|
-
'scheduled_tick',
|
|
325
|
-
'runner_tick',
|
|
326
|
-
'manual_tick',
|
|
327
|
-
'headless_execution',
|
|
328
|
-
'jobs_command',
|
|
329
|
-
'invoke_watch',
|
|
330
|
-
'portal_render',
|
|
331
|
-
]);
|
|
332
|
-
function defaultDynamicImport(specifier) {
|
|
333
|
-
return import(specifier);
|
|
334
|
-
}
|
|
335
|
-
function checksumMigration(sql) {
|
|
336
|
-
return `sha256:${createHash('sha256').update(sql).digest('hex')}`;
|
|
337
|
-
}
|
|
338
|
-
function isRecord(value) {
|
|
339
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
340
|
-
}
|
|
341
|
-
function getErrorMessage(error) {
|
|
342
|
-
return error instanceof Error ? error.message : String(error);
|
|
343
|
-
}
|
|
344
|
-
function getErrorCode(error) {
|
|
345
|
-
return isRecord(error) && typeof error.code === 'string' ? error.code : undefined;
|
|
346
|
-
}
|
|
347
|
-
function mapSqliteError(error, fallback) {
|
|
348
|
-
if (error instanceof ContinuitySqliteStoreError)
|
|
349
|
-
return error;
|
|
350
|
-
const message = getErrorMessage(error);
|
|
351
|
-
const nativeCode = getErrorCode(error);
|
|
352
|
-
if (nativeCode === 'SQLITE_CORRUPT' || nativeCode === 'SQLITE_NOTADB' || SQLITE_CORRUPT_PATTERN.test(message)) {
|
|
353
|
-
return new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Continuity SQLite integrity check failed; durable mutation is blocked until the store is repaired or rebuilt.', { cause: error, mutationBlocked: true });
|
|
354
|
-
}
|
|
355
|
-
if (nativeCode === 'SQLITE_CONSTRAINT_FOREIGNKEY' || SQLITE_FOREIGN_KEY_PATTERN.test(message)) {
|
|
356
|
-
return new ContinuitySqliteStoreError('sqlite-foreign-key-failed', 'Continuity SQLite foreign-key validation failed; mutation was rejected.', { cause: error });
|
|
357
|
-
}
|
|
358
|
-
return new ContinuitySqliteStoreError(fallback, message, { cause: error });
|
|
359
|
-
}
|
|
360
|
-
function assertPublicSafeValue(value, path) {
|
|
361
|
-
if (typeof value === 'string') {
|
|
362
|
-
if (/\.(?:redactionEvidence|redactionSummary)\[\d+\]\.field$/.test(path) && /^[A-Za-z0-9_.:[\]-]{1,120}$/.test(value)) {
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (/\.(?:redactionEvidence|redactionSummary)\[\d+\]\.reason$/.test(path) && PUBLIC_REDACTION_REASON_VALUES.has(value)) {
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (UNSAFE_METADATA_VALUE_PATTERN.test(value)) {
|
|
369
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite metadata rejected unsafe value at ${path}.`);
|
|
370
|
-
}
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
if (value === null || typeof value === 'number' || typeof value === 'boolean')
|
|
374
|
-
return;
|
|
375
|
-
if (Array.isArray(value)) {
|
|
376
|
-
value.forEach((item, index) => assertPublicSafeValue(item, `${path}[${index}]`));
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (isRecord(value)) {
|
|
380
|
-
for (const [key, child] of Object.entries(value)) {
|
|
381
|
-
const publicReleaseBlockedSignal = key === 'terminationToken'
|
|
382
|
-
&& path.endsWith('.releaseBlocked')
|
|
383
|
-
&& child === '[YOLOOP_RELEASE_BLOCKED]';
|
|
384
|
-
if (UNSAFE_METADATA_KEY_PATTERN.test(key) && !publicReleaseBlockedSignal) {
|
|
385
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite metadata rejected unsafe key at ${path}.${key}.`);
|
|
386
|
-
}
|
|
387
|
-
assertPublicSafeValue(child, `${path}.${key}`);
|
|
388
|
-
}
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite metadata rejected unsupported value at ${path}.`);
|
|
392
|
-
}
|
|
393
|
-
function assertPublicSafeText(value, path) {
|
|
394
|
-
if (value === undefined)
|
|
395
|
-
return;
|
|
396
|
-
if (UNSAFE_METADATA_VALUE_PATTERN.test(value)) {
|
|
397
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite text rejected unsafe value at ${path}.`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
function assertProcessingSummaryText(value, path, maxChars = 240) {
|
|
401
|
-
if (value.trim().length === 0) {
|
|
402
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite processing summary text is required at ${path}.`);
|
|
403
|
-
}
|
|
404
|
-
assertPublicSafeText(value, path);
|
|
405
|
-
if (value.length > maxChars) {
|
|
406
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite processing summary text exceeded the public bound at ${path}.`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
function filterContinuityPublicMetadata(input) {
|
|
410
|
-
const metadata = {};
|
|
411
|
-
if (!input)
|
|
412
|
-
return metadata;
|
|
413
|
-
for (const [key, value] of Object.entries(input)) {
|
|
414
|
-
if (!CONTINUITY_PUBLIC_METADATA_ALLOWED_KEYS.has(key))
|
|
415
|
-
continue;
|
|
416
|
-
if (UNSAFE_METADATA_KEY_PATTERN.test(key))
|
|
417
|
-
continue;
|
|
418
|
-
try {
|
|
419
|
-
assertPublicSafeValue(value, `metadata.${key}`);
|
|
420
|
-
metadata[key] = value;
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
// Summary metadata is best-effort public context. Unsafe entries are
|
|
424
|
-
// filtered so a safe public summary can still be persisted without raw
|
|
425
|
-
// logs, prompts, secrets, private URLs or protected bodies.
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return metadata;
|
|
429
|
-
}
|
|
430
|
-
function buildProcessingOutputSummaryMetadata(input) {
|
|
431
|
-
const metadata = {
|
|
432
|
-
...filterContinuityPublicMetadata(input.metadata),
|
|
433
|
-
schemaVersion: 1,
|
|
434
|
-
contractVersion: 'continuity-v1',
|
|
435
|
-
eventType: CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE,
|
|
436
|
-
executionId: input.executionId,
|
|
437
|
-
jobId: input.jobId,
|
|
438
|
-
operationId: input.executionId,
|
|
439
|
-
publicSummary: input.publicSummary,
|
|
440
|
-
};
|
|
441
|
-
if (input.idempotencyKey)
|
|
442
|
-
metadata.idempotencyKey = input.idempotencyKey;
|
|
443
|
-
if (input.goalId)
|
|
444
|
-
metadata.goalId = input.goalId;
|
|
445
|
-
if (input.loopId)
|
|
446
|
-
metadata.loopId = input.loopId;
|
|
447
|
-
if (input.reasonCode)
|
|
448
|
-
metadata.reasonCode = input.reasonCode;
|
|
449
|
-
if (input.source && CONTINUITY_PROCESSING_OUTPUT_SUMMARY_SOURCES.has(input.source)) {
|
|
450
|
-
metadata.processingOutputSource = input.source;
|
|
451
|
-
}
|
|
452
|
-
return filterContinuityPublicMetadata(metadata);
|
|
453
|
-
}
|
|
454
|
-
function normalizeProcessingSummaryLimit(limit) {
|
|
455
|
-
const candidate = Math.floor(limit ?? DEFAULT_CONTINUITY_PROCESSING_SUMMARY_LIMIT);
|
|
456
|
-
if (!Number.isFinite(candidate))
|
|
457
|
-
return DEFAULT_CONTINUITY_PROCESSING_SUMMARY_LIMIT;
|
|
458
|
-
return Math.max(1, Math.min(MAX_CONTINUITY_PROCESSING_SUMMARY_LIMIT, candidate));
|
|
459
|
-
}
|
|
460
|
-
function processingSummaryEventId(input) {
|
|
461
|
-
if (input.eventId && input.eventId.trim().length > 0)
|
|
462
|
-
return input.eventId;
|
|
463
|
-
const safe = input.executionId.replace(/[^a-z0-9_.:-]+/gi, '-').replace(/^-+|-+$/g, '');
|
|
464
|
-
if (safe.length > 0)
|
|
465
|
-
return `event-processing-summary-${safe.slice(0, 160)}`;
|
|
466
|
-
return `event-processing-summary-${createHash('sha256').update(input.executionId).digest('hex').slice(0, 32)}`;
|
|
467
|
-
}
|
|
468
|
-
function encodeProcessingSummaryCursor(row) {
|
|
469
|
-
return `${PROCESSING_SUMMARY_CURSOR_PREFIX}${Buffer.from(JSON.stringify([row.created_at, row.event_id]), 'utf8').toString('base64url')}`;
|
|
470
|
-
}
|
|
471
|
-
function decodeProcessingSummaryCursor(cursor) {
|
|
472
|
-
if (!cursor?.startsWith(PROCESSING_SUMMARY_CURSOR_PREFIX))
|
|
473
|
-
return undefined;
|
|
474
|
-
try {
|
|
475
|
-
const payload = cursor.slice(PROCESSING_SUMMARY_CURSOR_PREFIX.length);
|
|
476
|
-
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
477
|
-
if (!Array.isArray(decoded) || decoded.length !== 2)
|
|
478
|
-
return undefined;
|
|
479
|
-
const [createdAt, eventId] = decoded;
|
|
480
|
-
if (typeof createdAt !== 'string' || typeof eventId !== 'string')
|
|
481
|
-
return undefined;
|
|
482
|
-
return { createdAt, eventId };
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
return undefined;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
function normalizeProcessingSummaryTargetId(kind, value) {
|
|
489
|
-
if (!value)
|
|
490
|
-
return undefined;
|
|
491
|
-
const normalized = value.trim().replace(/^@/, '');
|
|
492
|
-
if (kind === 'job' && /^job-J\d{3,}$/i.test(normalized))
|
|
493
|
-
return `job-J${normalized.match(/\d+$/)?.[0] ?? ''}`;
|
|
494
|
-
if (kind === 'goal' && /^goal-G\d{3,}$/i.test(normalized))
|
|
495
|
-
return `goal-G${normalized.match(/\d+$/)?.[0] ?? ''}`;
|
|
496
|
-
if (kind === 'loop' && /^loop-L\d{3,}$/i.test(normalized))
|
|
497
|
-
return `loop-L${normalized.match(/\d+$/)?.[0] ?? ''}`;
|
|
498
|
-
return undefined;
|
|
499
|
-
}
|
|
500
|
-
function normalizedRelatedJobIds(values) {
|
|
501
|
-
if (!values)
|
|
502
|
-
return [];
|
|
503
|
-
return uniqueValues(values
|
|
504
|
-
.map((value) => normalizeProcessingSummaryTargetId('job', value))
|
|
505
|
-
.filter((value) => Boolean(value)));
|
|
506
|
-
}
|
|
507
|
-
function processingSummaryTargetPredicate(options) {
|
|
508
|
-
const target = options.target;
|
|
509
|
-
if (!target) {
|
|
510
|
-
const jobId = normalizeProcessingSummaryTargetId('job', options.jobId);
|
|
511
|
-
return jobId ? { sql: ' AND job_id = ?', params: [jobId] } : { sql: '', params: [] };
|
|
512
|
-
}
|
|
513
|
-
const selectorKind = target.selectorKind;
|
|
514
|
-
if (selectorKind === 'job') {
|
|
515
|
-
const targetJobId = normalizeProcessingSummaryTargetId('job', target.jobId ?? target.targetId ?? options.jobId);
|
|
516
|
-
return targetJobId ? { sql: ' AND job_id = ?', params: [targetJobId] } : { sql: ' AND 1 = 0', params: [] };
|
|
517
|
-
}
|
|
518
|
-
if (selectorKind === 'goal') {
|
|
519
|
-
const goalId = normalizeProcessingSummaryTargetId('goal', target.goalId ?? target.targetId);
|
|
520
|
-
if (!goalId)
|
|
521
|
-
return { sql: ' AND 1 = 0', params: [] };
|
|
522
|
-
const relatedJobIds = normalizedRelatedJobIds(target.relatedJobIds);
|
|
523
|
-
const relatedPredicate = relatedJobIds.length > 0
|
|
524
|
-
? ` OR job_id IN (${relatedJobIds.map(() => '?').join(', ')})`
|
|
525
|
-
: '';
|
|
526
|
-
return {
|
|
527
|
-
sql: ` AND (job_id = ? OR json_extract(metadata_json, '$.goalId') = ?${relatedPredicate})`,
|
|
528
|
-
params: [goalId, goalId, ...relatedJobIds],
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
if (selectorKind === 'loop') {
|
|
532
|
-
const loopId = normalizeProcessingSummaryTargetId('loop', target.loopId ?? target.targetId);
|
|
533
|
-
if (!loopId)
|
|
534
|
-
return { sql: ' AND 1 = 0', params: [] };
|
|
535
|
-
const relatedJobIds = normalizedRelatedJobIds(target.relatedJobIds);
|
|
536
|
-
const relatedPredicate = relatedJobIds.length > 0
|
|
537
|
-
? ` OR job_id IN (${relatedJobIds.map(() => '?').join(', ')})`
|
|
538
|
-
: '';
|
|
539
|
-
return {
|
|
540
|
-
sql: ` AND (job_id = ? OR json_extract(metadata_json, '$.loopId') = ?${relatedPredicate})`,
|
|
541
|
-
params: [loopId, loopId, ...relatedJobIds],
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
return { sql: ' AND 1 = 0', params: [] };
|
|
545
|
-
}
|
|
546
|
-
function toProcessingOutputSummaryRow(row) {
|
|
547
|
-
const output = {
|
|
548
|
-
event_id: row.event_id,
|
|
549
|
-
job_id: row.job_id,
|
|
550
|
-
execution_id: row.operation_id,
|
|
551
|
-
public_summary: row.summary,
|
|
552
|
-
reason_code: row.reason_code,
|
|
553
|
-
created_at: row.created_at,
|
|
554
|
-
metadata_json: row.metadata_json,
|
|
555
|
-
};
|
|
556
|
-
return {
|
|
557
|
-
...output,
|
|
558
|
-
cursor: encodeProcessingSummaryCursor(output),
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function stringifyPublicMetadata(metadata) {
|
|
562
|
-
if (!metadata)
|
|
563
|
-
return null;
|
|
564
|
-
assertPublicSafeValue(metadata, 'metadata');
|
|
565
|
-
return JSON.stringify(metadata);
|
|
566
|
-
}
|
|
567
|
-
function retentionHashes(jobId, receipts, outputs) {
|
|
568
|
-
const digest = (values) => `sha256:${createHash('sha256')
|
|
569
|
-
.update(values
|
|
570
|
-
.map((value) => [value.id, value.status, value.reasonCode].join(':'))
|
|
571
|
-
.sort()
|
|
572
|
-
.join('\n'))
|
|
573
|
-
.digest('hex')}`;
|
|
574
|
-
const receiptHash = digest(receipts);
|
|
575
|
-
const outputHash = digest(outputs);
|
|
576
|
-
return {
|
|
577
|
-
receipts: receiptHash,
|
|
578
|
-
outputs: outputHash,
|
|
579
|
-
aggregate: `sha256:${createHash('sha256').update(`${jobId}:${receiptHash}:${outputHash}`).digest('hex')}`,
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* P195.04: Parse an existing `jobs.metadata_json` value into a mutable object for
|
|
584
|
-
* additive read-modify-write of stage progress. Corrupt/absent metadata returns a
|
|
585
|
-
* fresh object so forward-compatible reads never throw on older DBs.
|
|
586
|
-
*/
|
|
587
|
-
function parseJobMetadataObject(metadataJson) {
|
|
588
|
-
if (!metadataJson)
|
|
589
|
-
return {};
|
|
590
|
-
try {
|
|
591
|
-
const parsed = JSON.parse(metadataJson);
|
|
592
|
-
return isRecord(parsed) ? { ...parsed } : {};
|
|
593
|
-
}
|
|
594
|
-
catch {
|
|
595
|
-
return {};
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function assertBoundedPublicText(value, path, maxChars, required = false) {
|
|
599
|
-
if (value === undefined) {
|
|
600
|
-
if (required) {
|
|
601
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite text is required at ${path}.`);
|
|
602
|
-
}
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
assertPublicSafeText(value, path);
|
|
606
|
-
if (value.trim().length === 0 || value.length > maxChars) {
|
|
607
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite text exceeded the public bound at ${path}.`);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function assertNonNegativeInteger(value, path) {
|
|
611
|
-
if (value === undefined)
|
|
612
|
-
return;
|
|
613
|
-
if (!Number.isFinite(value) || value < 0 || Math.floor(value) !== value) {
|
|
614
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite numeric value rejected at ${path}.`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
function assertOutboxIdentity(input) {
|
|
618
|
-
assertBoundedPublicText(input.outboxId, 'outbox.outboxId', 160, true);
|
|
619
|
-
assertBoundedPublicText(input.jobId, 'outbox.jobId', 160);
|
|
620
|
-
assertBoundedPublicText(input.operationId, 'outbox.operationId', 160);
|
|
621
|
-
assertBoundedPublicText(input.idempotencyKey, 'outbox.idempotencyKey', 160);
|
|
622
|
-
assertBoundedPublicText(input.type, 'outbox.type', 80);
|
|
623
|
-
assertBoundedPublicText(input.status, 'outbox.status', 40);
|
|
624
|
-
assertBoundedPublicText(input.deliveryCursor, 'outbox.deliveryCursor', 200);
|
|
625
|
-
assertBoundedPublicText(input.timestamp, 'outbox.timestamp', 80);
|
|
626
|
-
assertBoundedPublicText(input.reasonCode, 'outbox.reasonCode', 100);
|
|
627
|
-
assertBoundedPublicText(input.summary, 'outbox.summary', 240);
|
|
628
|
-
}
|
|
629
|
-
function isOutboxStatus(value) {
|
|
630
|
-
return value === 'pending' || value === 'sent' || value === 'acked' || value === 'failed' || value === 'dead_letter';
|
|
631
|
-
}
|
|
632
|
-
function publicOutboxStatus(value) {
|
|
633
|
-
return typeof value === 'string' && isOutboxStatus(value) ? value : 'failed';
|
|
634
|
-
}
|
|
635
|
-
function optionalOutboxString(value) {
|
|
636
|
-
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
637
|
-
}
|
|
638
|
-
function parseOutboxPayload(payloadJson) {
|
|
639
|
-
if (typeof payloadJson !== 'string' || payloadJson.trim().length === 0)
|
|
640
|
-
return {};
|
|
641
|
-
try {
|
|
642
|
-
const parsed = JSON.parse(payloadJson);
|
|
643
|
-
if (!isRecord(parsed))
|
|
644
|
-
return {};
|
|
645
|
-
assertPublicSafeValue(parsed, 'outbox.payload');
|
|
646
|
-
return parsed;
|
|
647
|
-
}
|
|
648
|
-
catch {
|
|
649
|
-
return {};
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
function toOutboxDeliveryRow(row) {
|
|
653
|
-
return {
|
|
654
|
-
outboxId: String(row.outbox_id ?? ''),
|
|
655
|
-
...(optionalOutboxString(row.job_id) ? { jobId: optionalOutboxString(row.job_id) } : {}),
|
|
656
|
-
operationId: String(row.operation_id ?? ''),
|
|
657
|
-
...(optionalOutboxString(row.idempotency_key) ? { idempotencyKey: optionalOutboxString(row.idempotency_key) } : {}),
|
|
658
|
-
type: String(row.type ?? ''),
|
|
659
|
-
status: publicOutboxStatus(row.status),
|
|
660
|
-
payload: parseOutboxPayload(row.payload_json),
|
|
661
|
-
retryCount: typeof row.retry_count === 'number' ? row.retry_count : 0,
|
|
662
|
-
attemptCount: typeof row.attempt_count === 'number' ? row.attempt_count : 0,
|
|
663
|
-
...(optionalOutboxString(row.delivery_cursor) ? { deliveryCursor: optionalOutboxString(row.delivery_cursor) } : {}),
|
|
664
|
-
...(optionalOutboxString(row.next_attempt_at) ? { nextAttemptAt: optionalOutboxString(row.next_attempt_at) } : {}),
|
|
665
|
-
...(optionalOutboxString(row.last_attempt_at) ? { lastAttemptAt: optionalOutboxString(row.last_attempt_at) } : {}),
|
|
666
|
-
...(optionalOutboxString(row.sent_at) ? { sentAt: optionalOutboxString(row.sent_at) } : {}),
|
|
667
|
-
...(optionalOutboxString(row.acked_at) ? { ackedAt: optionalOutboxString(row.acked_at) } : {}),
|
|
668
|
-
...(optionalOutboxString(row.last_error_code) ? { lastErrorCode: optionalOutboxString(row.last_error_code) } : {}),
|
|
669
|
-
...(optionalOutboxString(row.last_error_reason) ? { lastErrorReason: optionalOutboxString(row.last_error_reason) } : {}),
|
|
670
|
-
...(optionalOutboxString(row.last_error_summary) ? { lastErrorSummary: optionalOutboxString(row.last_error_summary) } : {}),
|
|
671
|
-
...(optionalOutboxString(row.dead_letter_summary) ? { deadLetterSummary: optionalOutboxString(row.dead_letter_summary) } : {}),
|
|
672
|
-
...(optionalOutboxString(row.dead_lettered_at) ? { deadLetteredAt: optionalOutboxString(row.dead_lettered_at) } : {}),
|
|
673
|
-
createdAt: String(row.created_at ?? ''),
|
|
674
|
-
updatedAt: String(row.updated_at ?? ''),
|
|
675
|
-
publicSafe: true,
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
function outboxTransitionResult(input) {
|
|
679
|
-
return { ...input, publicSafe: true };
|
|
680
|
-
}
|
|
681
|
-
function stringifyPublicJsonValue(value, path) {
|
|
682
|
-
if (value === undefined)
|
|
683
|
-
return null;
|
|
684
|
-
assertPublicSafeValue(value, path);
|
|
685
|
-
return JSON.stringify(value);
|
|
686
|
-
}
|
|
687
|
-
function normalizePublicRelativePaths(paths) {
|
|
688
|
-
if (!paths)
|
|
689
|
-
return undefined;
|
|
690
|
-
const normalized = uniqueValues(paths.map((path) => path.replace(/\\/g, '/')))
|
|
691
|
-
.filter((path) => path.length > 0)
|
|
692
|
-
.slice(0, 50);
|
|
693
|
-
for (const [index, path] of normalized.entries()) {
|
|
694
|
-
assertBoundedPublicText(path, `relativePaths[${index}]`, 240, true);
|
|
695
|
-
const segments = path.split('/').filter(Boolean);
|
|
696
|
-
if (path.startsWith('/') || path.startsWith('~') || path.includes('://') || /^[A-Za-z]:\//.test(path) || segments.includes('..')) {
|
|
697
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', `Continuity SQLite rejected non-relative path at relativePaths[${index}].`);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return normalized;
|
|
701
|
-
}
|
|
702
|
-
function buildHeadlessExecutionPublicMetadata(input) {
|
|
703
|
-
const metadata = {
|
|
704
|
-
...filterContinuityPublicMetadata(input.metadata),
|
|
705
|
-
schemaVersion: 1,
|
|
706
|
-
contractVersion: 'continuity-v1',
|
|
707
|
-
attemptId: input.attemptId,
|
|
708
|
-
executionId: input.executionId,
|
|
709
|
-
jobId: input.jobId,
|
|
710
|
-
};
|
|
711
|
-
if (input.directiveHash)
|
|
712
|
-
metadata.directiveHash = input.directiveHash;
|
|
713
|
-
if (input.runtimeId)
|
|
714
|
-
metadata.runtimeId = input.runtimeId;
|
|
715
|
-
if (input.modelId)
|
|
716
|
-
metadata.modelId = input.modelId;
|
|
717
|
-
if (input.runtimeSource)
|
|
718
|
-
metadata.runtimeSource = input.runtimeSource;
|
|
719
|
-
if (input.status)
|
|
720
|
-
metadata.attemptStatus = input.status;
|
|
721
|
-
if (input.reasonCode)
|
|
722
|
-
metadata.reasonCode = input.reasonCode;
|
|
723
|
-
if (input.durationMs !== undefined)
|
|
724
|
-
metadata.durationMs = input.durationMs;
|
|
725
|
-
if (input.retryCount !== undefined)
|
|
726
|
-
metadata.retryCount = input.retryCount;
|
|
727
|
-
if (input.attemptCount !== undefined)
|
|
728
|
-
metadata.attemptCount = input.attemptCount;
|
|
729
|
-
if (input.maxAttempts !== undefined)
|
|
730
|
-
metadata.maxAttempts = input.maxAttempts;
|
|
731
|
-
if (input.queuePriority !== undefined)
|
|
732
|
-
metadata.queuePriority = input.queuePriority;
|
|
733
|
-
if (input.queueScope)
|
|
734
|
-
metadata.queueScope = input.queueScope;
|
|
735
|
-
if (input.nextRetryAt)
|
|
736
|
-
metadata.nextRetryAt = input.nextRetryAt;
|
|
737
|
-
if (input.deadLetterReasonCode)
|
|
738
|
-
metadata.deadLetterReasonCode = input.deadLetterReasonCode;
|
|
739
|
-
if (input.counts)
|
|
740
|
-
metadata.counts = input.counts;
|
|
741
|
-
if (input.hashes)
|
|
742
|
-
metadata.hashes = input.hashes;
|
|
743
|
-
if (input.redactionSummary)
|
|
744
|
-
metadata.redactionEvidence = input.redactionSummary;
|
|
745
|
-
if (input.relativePaths)
|
|
746
|
-
metadata.relativePaths = normalizePublicRelativePaths(input.relativePaths);
|
|
747
|
-
return filterContinuityPublicMetadata(metadata);
|
|
748
|
-
}
|
|
749
|
-
function validateHeadlessExecutionAttemptIdentity(input) {
|
|
750
|
-
assertBoundedPublicText(input.attemptId, 'headless_execution.attemptId', 160, true);
|
|
751
|
-
assertBoundedPublicText(input.executionId, 'headless_execution.executionId', 160, true);
|
|
752
|
-
assertBoundedPublicText(input.jobId, 'headless_execution.jobId', 160, true);
|
|
753
|
-
assertBoundedPublicText(input.directiveHash, 'headless_execution.directiveHash', 160);
|
|
754
|
-
assertBoundedPublicText(input.targetRef, 'headless_execution.targetRef', 240);
|
|
755
|
-
assertBoundedPublicText(input.storyId, 'headless_execution.storyId', 120);
|
|
756
|
-
assertBoundedPublicText(input.epicId, 'headless_execution.epicId', 120);
|
|
757
|
-
assertBoundedPublicText(input.runtimeId, 'headless_execution.runtimeId', 80);
|
|
758
|
-
assertBoundedPublicText(input.modelId, 'headless_execution.modelId', 120);
|
|
759
|
-
assertBoundedPublicText(input.runtimeSource, 'headless_execution.runtimeSource', 40);
|
|
760
|
-
assertBoundedPublicText(input.reasonCode, 'headless_execution.reasonCode', 100);
|
|
761
|
-
assertBoundedPublicText(input.publicSummary, 'headless_execution.publicSummary', 240);
|
|
762
|
-
assertBoundedPublicText(input.leaseId, 'headless_execution.leaseId', 160);
|
|
763
|
-
assertBoundedPublicText(input.runnerId, 'headless_execution.runnerId', 160);
|
|
764
|
-
assertBoundedPublicText(input.timestamp, 'headless_execution.timestamp', 80);
|
|
765
|
-
}
|
|
766
|
-
function asSinglePragmaValue(value) {
|
|
767
|
-
if (Array.isArray(value)) {
|
|
768
|
-
const first = value[0];
|
|
769
|
-
if (isRecord(first))
|
|
770
|
-
return Object.values(first)[0];
|
|
771
|
-
return first;
|
|
772
|
-
}
|
|
773
|
-
if (isRecord(value))
|
|
774
|
-
return Object.values(value)[0];
|
|
775
|
-
return value;
|
|
776
|
-
}
|
|
777
|
-
function isContinuityTable(table) {
|
|
778
|
-
return CONTINUITY_TABLES.includes(table);
|
|
779
|
-
}
|
|
780
|
-
function uniqueValues(values) {
|
|
781
|
-
return Array.from(new Set(values));
|
|
782
|
-
}
|
|
783
|
-
function createBetterSqlite3Adapter(moduleValue) {
|
|
784
|
-
const constructorValue = isRecord(moduleValue) && 'default' in moduleValue
|
|
785
|
-
? moduleValue.default
|
|
786
|
-
: moduleValue;
|
|
787
|
-
if (typeof constructorValue !== 'function') {
|
|
788
|
-
throw new ContinuitySqliteStoreError('sqlite-adapter-unavailable', 'Optional better-sqlite3 adapter did not export a database constructor.', { guidance: SQLITE_ADAPTER_INSTALL_GUIDANCE });
|
|
789
|
-
}
|
|
790
|
-
const Database = constructorValue;
|
|
791
|
-
return {
|
|
792
|
-
adapterName: 'better-sqlite3',
|
|
793
|
-
open(filePath, options) {
|
|
794
|
-
return new Database(filePath, options);
|
|
795
|
-
},
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
export async function loadBetterSqlite3Adapter(importer = defaultDynamicImport) {
|
|
799
|
-
try {
|
|
800
|
-
const moduleValue = await importer('better-sqlite3');
|
|
801
|
-
return { ok: true, adapter: createBetterSqlite3Adapter(moduleValue) };
|
|
802
|
-
}
|
|
803
|
-
catch (error) {
|
|
804
|
-
if (error instanceof ContinuitySqliteStoreError)
|
|
805
|
-
return { ok: false, error };
|
|
806
|
-
return {
|
|
807
|
-
ok: false,
|
|
808
|
-
error: new ContinuitySqliteStoreError('sqlite-adapter-unavailable', 'Optional Enterprise continuity SQLite adapter better-sqlite3 is unavailable.', { cause: error, guidance: SQLITE_ADAPTER_INSTALL_GUIDANCE }),
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
export function continuitySqliteStorePath(projectRoot) {
|
|
813
|
-
return join(projectRoot, CONTINUITY_SQLITE_RELATIVE_DIR, CONTINUITY_SQLITE_FILENAME);
|
|
814
|
-
}
|
|
815
|
-
function ensureContinuityDirectory(projectRoot) {
|
|
816
|
-
const directory = join(projectRoot, CONTINUITY_SQLITE_RELATIVE_DIR);
|
|
817
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
818
|
-
try {
|
|
819
|
-
chmodSync(directory, 0o700);
|
|
820
|
-
}
|
|
821
|
-
catch {
|
|
822
|
-
// Permission tightening is best-effort on non-POSIX filesystems.
|
|
823
|
-
}
|
|
824
|
-
return directory;
|
|
825
|
-
}
|
|
826
|
-
export async function openContinuitySqliteStore(options) {
|
|
827
|
-
const adapter = options.adapter ?? await resolveAdapter(options.adapterLoader);
|
|
828
|
-
const filePath = options.filePath ?? continuitySqliteStorePath(options.projectRoot);
|
|
829
|
-
if (options.strictReadOnly) {
|
|
830
|
-
assertContinuitySqliteReadOnlyHeader(filePath);
|
|
831
|
-
}
|
|
832
|
-
else {
|
|
833
|
-
if (options.filePath) {
|
|
834
|
-
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
835
|
-
try {
|
|
836
|
-
chmodSync(dirname(filePath), 0o700);
|
|
837
|
-
}
|
|
838
|
-
catch { /* best-effort */ }
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
ensureContinuityDirectory(options.projectRoot);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
let db;
|
|
845
|
-
try {
|
|
846
|
-
db = adapter.open(filePath, options.strictReadOnly
|
|
847
|
-
? { fileMustExist: true, readonly: true }
|
|
848
|
-
: undefined);
|
|
849
|
-
}
|
|
850
|
-
catch (error) {
|
|
851
|
-
throw mapSqliteError(error, 'sqlite-integrity-failed');
|
|
852
|
-
}
|
|
853
|
-
const store = new ContinuitySqliteStore({
|
|
854
|
-
db,
|
|
855
|
-
filePath,
|
|
856
|
-
adapterName: adapter.adapterName,
|
|
857
|
-
busyTimeoutMs: options.busyTimeoutMs ?? DEFAULT_CONTINUITY_SQLITE_BUSY_TIMEOUT_MS,
|
|
858
|
-
integrityCheck: options.integrityCheck ?? true,
|
|
859
|
-
now: options.now ?? (() => new Date().toISOString()),
|
|
860
|
-
});
|
|
861
|
-
try {
|
|
862
|
-
if (options.strictReadOnly) {
|
|
863
|
-
store.runIntegrityCheck();
|
|
864
|
-
// Prove the expected schema is readable without attempting repair.
|
|
865
|
-
store.getSchemaVersion();
|
|
866
|
-
return store;
|
|
867
|
-
}
|
|
868
|
-
store.initialize();
|
|
869
|
-
if (existsSync(filePath)) {
|
|
870
|
-
try {
|
|
871
|
-
chmodSync(filePath, 0o600);
|
|
872
|
-
}
|
|
873
|
-
catch {
|
|
874
|
-
// Best-effort on non-POSIX filesystems.
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
return store;
|
|
878
|
-
}
|
|
879
|
-
catch (error) {
|
|
880
|
-
try {
|
|
881
|
-
db.close();
|
|
882
|
-
}
|
|
883
|
-
catch {
|
|
884
|
-
// Ignore close errors while surfacing the original hard stop.
|
|
885
|
-
}
|
|
886
|
-
throw error;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
function assertContinuitySqliteReadOnlyHeader(filePath) {
|
|
890
|
-
if (!existsSync(filePath)) {
|
|
891
|
-
throw new ContinuitySqliteStoreError('sqlite-projection-missing', 'Continuity database is unavailable for physical read-only inspection.', { mutationBlocked: true });
|
|
892
|
-
}
|
|
893
|
-
let header;
|
|
894
|
-
try {
|
|
895
|
-
const fd = openSync(filePath, 'r');
|
|
896
|
-
try {
|
|
897
|
-
header = Buffer.alloc(20);
|
|
898
|
-
if (readSync(fd, header, 0, header.length, 0) < header.length)
|
|
899
|
-
throw new Error('short-header');
|
|
900
|
-
}
|
|
901
|
-
finally {
|
|
902
|
-
closeSync(fd);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
catch (error) {
|
|
906
|
-
throw new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Continuity database header could not be inspected read-only.', { cause: error, mutationBlocked: true });
|
|
907
|
-
}
|
|
908
|
-
if (header.subarray(0, 16).toString('ascii') !== 'SQLite format 3\0') {
|
|
909
|
-
throw new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Continuity database header is not a recognized SQLite database.', { mutationBlocked: true });
|
|
910
|
-
}
|
|
911
|
-
if (header[18] === 2 || header[19] === 2) {
|
|
912
|
-
throw new ContinuitySqliteStoreError('sqlite-projection-wal-unsafe', 'Continuity database requires an unsafe WAL read profile.', { mutationBlocked: true });
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* P205.05 physical inspection boundary. The database must already exist and is
|
|
917
|
-
* opened with the adapter's OS-backed read-only flag. No directory creation,
|
|
918
|
-
* chmod, initialization, migrations or mutative pragma is attempted.
|
|
919
|
-
*/
|
|
920
|
-
export async function openContinuitySqliteReadOnly(options) {
|
|
921
|
-
const filePath = continuitySqliteStorePath(options.projectRoot);
|
|
922
|
-
// SQLite read-only opens can still create `-wal`/`-shm` companions when the
|
|
923
|
-
// database header is in WAL mode. Deny that profile before adapter open.
|
|
924
|
-
assertContinuitySqliteReadOnlyHeader(filePath);
|
|
925
|
-
const adapter = options.adapter ?? await resolveAdapter(options.adapterLoader);
|
|
926
|
-
let db;
|
|
927
|
-
let opened = false;
|
|
928
|
-
try {
|
|
929
|
-
db = adapter.open(filePath, { fileMustExist: true, readonly: true });
|
|
930
|
-
opened = true;
|
|
931
|
-
const integrity = asSinglePragmaValue(db.pragma('integrity_check', { simple: true }));
|
|
932
|
-
if (String(integrity).toLowerCase() !== 'ok')
|
|
933
|
-
throw new Error('integrity');
|
|
934
|
-
}
|
|
935
|
-
catch (error) {
|
|
936
|
-
throw new ContinuitySqliteStoreError(!opened ? 'sqlite-projection-adapter-incompatible' : 'sqlite-integrity-failed', 'Continuity database failed physical read-only inspection.', { cause: error, mutationBlocked: true });
|
|
937
|
-
}
|
|
938
|
-
const assertReadStatement = (source) => {
|
|
939
|
-
const normalized = source.trim().replace(/^\(+/u, '').toLowerCase();
|
|
940
|
-
if (!/^(select|with\s)/u.test(normalized) || /\b(insert|update|delete|replace|create|alter|drop|pragma|attach|detach|vacuum|reindex)\b/u.test(normalized)) {
|
|
941
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-blocked', 'Statement is not in the physical read-only query profile.', { mutationBlocked: true });
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
return Object.freeze({
|
|
945
|
-
filePath,
|
|
946
|
-
adapterName: adapter.adapterName,
|
|
947
|
-
all(source, ...params) { assertReadStatement(source); return db.prepare(source).all(...params); },
|
|
948
|
-
get(source, ...params) { assertReadStatement(source); return db.prepare(source).get(...params); },
|
|
949
|
-
close() { db.close(); },
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* P224.01 strict projection binding. The existing state_snapshots table is
|
|
954
|
-
* inspected without schema initialization, WAL, chmod, mkdir or repair. A
|
|
955
|
-
* missing/optional adapter is explicit and never promoted to equality.
|
|
956
|
-
*/
|
|
957
|
-
export async function inspectEpicOperationalSqliteBinding(options) {
|
|
958
|
-
if (!Number.isSafeInteger(options.authorityRevision) || options.authorityRevision < 0
|
|
959
|
-
|| !/^[a-f0-9]{64}$/.test(options.sourceDigest)) {
|
|
960
|
-
throw new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Epic operational SQLite binding input is invalid.', { mutationBlocked: true });
|
|
961
|
-
}
|
|
962
|
-
let inspector;
|
|
963
|
-
try {
|
|
964
|
-
inspector = await openContinuitySqliteReadOnly(options);
|
|
965
|
-
let rows;
|
|
966
|
-
try {
|
|
967
|
-
rows = inspector.all('SELECT sha256, metadata_json FROM state_snapshots WHERE kind = ? ORDER BY captured_at DESC LIMIT 2', 'epic-operational-state');
|
|
968
|
-
}
|
|
969
|
-
catch {
|
|
970
|
-
return Object.freeze({
|
|
971
|
-
schemaVersion: 'EpicOperationalSqliteBindingInspection/1.0',
|
|
972
|
-
status: 'unavailable',
|
|
973
|
-
authorityRevision: options.authorityRevision,
|
|
974
|
-
sourceDigest: null,
|
|
975
|
-
adapterName: inspector.adapterName,
|
|
976
|
-
reasonCodes: ['sqlite-projection-schema-unsupported'],
|
|
977
|
-
publicSafe: true,
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
const matching = rows.filter((row) => {
|
|
981
|
-
if (!isRecord(row) || row.sha256 !== options.sourceDigest)
|
|
982
|
-
return false;
|
|
983
|
-
if (typeof row.metadata_json !== 'string')
|
|
984
|
-
return false;
|
|
985
|
-
try {
|
|
986
|
-
const metadata = JSON.parse(row.metadata_json);
|
|
987
|
-
return isRecord(metadata) && metadata.authorityRevision === options.authorityRevision;
|
|
988
|
-
}
|
|
989
|
-
catch {
|
|
990
|
-
return false;
|
|
991
|
-
}
|
|
992
|
-
});
|
|
993
|
-
return Object.freeze({
|
|
994
|
-
schemaVersion: 'EpicOperationalSqliteBindingInspection/1.0',
|
|
995
|
-
status: matching.length === 1 ? 'bound' : 'unbound',
|
|
996
|
-
authorityRevision: options.authorityRevision,
|
|
997
|
-
sourceDigest: matching.length === 1 ? options.sourceDigest : null,
|
|
998
|
-
adapterName: inspector.adapterName,
|
|
999
|
-
reasonCodes: matching.length === 1
|
|
1000
|
-
? ['epic-operational-sqlite-binding-verified']
|
|
1001
|
-
: ['epic-operational-projection-unbound'],
|
|
1002
|
-
publicSafe: true,
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
catch (error) {
|
|
1006
|
-
const reasonCode = error instanceof ContinuitySqliteStoreError
|
|
1007
|
-
? error.reasonCode
|
|
1008
|
-
: 'sqlite-integrity-failed';
|
|
1009
|
-
return Object.freeze({
|
|
1010
|
-
schemaVersion: 'EpicOperationalSqliteBindingInspection/1.0',
|
|
1011
|
-
status: 'unavailable',
|
|
1012
|
-
authorityRevision: options.authorityRevision,
|
|
1013
|
-
sourceDigest: null,
|
|
1014
|
-
adapterName: null,
|
|
1015
|
-
reasonCodes: [reasonCode],
|
|
1016
|
-
publicSafe: true,
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
finally {
|
|
1020
|
-
inspector?.close();
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
async function resolveAdapter(loader) {
|
|
1024
|
-
const result = loader ? await loader() : await loadBetterSqlite3Adapter();
|
|
1025
|
-
if (!result.ok)
|
|
1026
|
-
throw result.error;
|
|
1027
|
-
return result.adapter;
|
|
1028
|
-
}
|
|
1029
|
-
export class ContinuitySqliteStore {
|
|
1030
|
-
filePath;
|
|
1031
|
-
adapterName;
|
|
1032
|
-
db;
|
|
1033
|
-
busyTimeoutMs;
|
|
1034
|
-
integrityCheck;
|
|
1035
|
-
now;
|
|
1036
|
-
mutationBlocked = false;
|
|
1037
|
-
constructor(options) {
|
|
1038
|
-
this.db = options.db;
|
|
1039
|
-
this.filePath = options.filePath;
|
|
1040
|
-
this.adapterName = options.adapterName;
|
|
1041
|
-
this.busyTimeoutMs = options.busyTimeoutMs;
|
|
1042
|
-
this.integrityCheck = options.integrityCheck;
|
|
1043
|
-
this.now = options.now;
|
|
1044
|
-
}
|
|
1045
|
-
initialize() {
|
|
1046
|
-
try {
|
|
1047
|
-
this.configureConnection();
|
|
1048
|
-
if (this.integrityCheck)
|
|
1049
|
-
this.runIntegrityCheck();
|
|
1050
|
-
this.ensureMigrationTable();
|
|
1051
|
-
this.applyMigrations();
|
|
1052
|
-
if (this.integrityCheck)
|
|
1053
|
-
this.runIntegrityCheck();
|
|
1054
|
-
}
|
|
1055
|
-
catch (error) {
|
|
1056
|
-
throw mapSqliteError(error, 'sqlite-migration-failed');
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
close() {
|
|
1060
|
-
this.db.close();
|
|
1061
|
-
}
|
|
1062
|
-
pragmaValue(source) {
|
|
1063
|
-
return asSinglePragmaValue(this.db.pragma(source, { simple: true }));
|
|
1064
|
-
}
|
|
1065
|
-
runIntegrityCheck() {
|
|
1066
|
-
try {
|
|
1067
|
-
const result = String(asSinglePragmaValue(this.db.pragma('integrity_check', { simple: true })) ?? '');
|
|
1068
|
-
if (result.toLowerCase() !== 'ok') {
|
|
1069
|
-
this.mutationBlocked = true;
|
|
1070
|
-
throw new ContinuitySqliteStoreError('sqlite-integrity-failed', 'Continuity SQLite integrity check failed; durable mutation is blocked until the store is repaired or rebuilt.', { mutationBlocked: true });
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
catch (error) {
|
|
1074
|
-
this.mutationBlocked = true;
|
|
1075
|
-
throw mapSqliteError(error, 'sqlite-integrity-failed');
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
getSchemaVersion() {
|
|
1079
|
-
const row = this.db.prepare('SELECT COALESCE(MAX(version), 0) AS version FROM continuity_migrations').get();
|
|
1080
|
-
return isRecord(row) && typeof row.version === 'number' ? row.version : 0;
|
|
1081
|
-
}
|
|
1082
|
-
countRows(table) {
|
|
1083
|
-
if (!isContinuityTable(table)) {
|
|
1084
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Unknown continuity table name.');
|
|
1085
|
-
}
|
|
1086
|
-
const row = this.db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get();
|
|
1087
|
-
return isRecord(row) && typeof row.count === 'number' ? row.count : 0;
|
|
1088
|
-
}
|
|
1089
|
-
reserveNativeAgentDispatch(input) {
|
|
1090
|
-
this.assertCanMutate();
|
|
1091
|
-
if (!isNativeDispatchBindingV1(input.binding) || !isNativeHostAttestationV1(input.hostAttestation)) {
|
|
1092
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Native-agent reservation rejected invalid binding or host evidence.');
|
|
1093
|
-
}
|
|
1094
|
-
const candidate = {
|
|
1095
|
-
schemaVersion: 1,
|
|
1096
|
-
storyId: input.binding.storyId,
|
|
1097
|
-
graphRevision: input.binding.graphRevision.replace(/^sha256:/, '').toLowerCase(),
|
|
1098
|
-
dispatchKey: input.binding.dispatchKey.toLowerCase(),
|
|
1099
|
-
profile: 'neocortex',
|
|
1100
|
-
parentHandle: null,
|
|
1101
|
-
handle: null,
|
|
1102
|
-
topology: input.topology ?? 'native-agent',
|
|
1103
|
-
state: 'requested',
|
|
1104
|
-
requestedAt: input.requestedAt,
|
|
1105
|
-
observedAt: null,
|
|
1106
|
-
terminalAt: null,
|
|
1107
|
-
freshness: 'unknown',
|
|
1108
|
-
observationSource: 'local-reservation',
|
|
1109
|
-
reasonCode: input.reasonCode ?? 'native-agent-dispatch-reserved',
|
|
1110
|
-
revision: 0,
|
|
1111
|
-
publicSafe: true,
|
|
1112
|
-
};
|
|
1113
|
-
if (!isNativeAgentDispatchReceiptV1(candidate)) {
|
|
1114
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Native-agent reservation rejected unsafe public fields.');
|
|
1115
|
-
}
|
|
1116
|
-
try {
|
|
1117
|
-
return this.runInTransaction(() => {
|
|
1118
|
-
const inserted = this.db.prepare(`
|
|
1
|
+
import{Buffer as J}from"node:buffer";import{createHash as M}from"node:crypto";import{chmodSync as z,closeSync as ge,existsSync as te,mkdirSync as ae,openSync as Ae,readSync as xe}from"node:fs";import{dirname as re,join as se}from"node:path";import{INITIAL_CONTINUITY_SQLITE_MIGRATION as Re}from"./migrations/001-initial-schema.js";import{PROCESSING_OUTPUT_SUMMARY_SQLITE_MIGRATION as Oe}from"./migrations/002-processing-output-summary-indexes.js";import{HEADLESS_EXECUTION_ATTEMPTS_SQLITE_MIGRATION as Ne}from"./migrations/003-headless-execution-attempts.js";import{OUTBOX_DELIVERY_STATE_SQLITE_MIGRATION as Te}from"./migrations/004-outbox-delivery-state.js";import{BOUNDED_HEADLESS_QUEUE_SQLITE_MIGRATION as Le}from"./migrations/005-bounded-headless-queue.js";import{NATIVE_AGENT_DISPATCHES_SQLITE_MIGRATION as ve}from"./migrations/006-native-agent-dispatches.js";import{NATIVE_AGENT_TERMINAL_STATES as oe,isNativeAgentDispatchReceiptV1 as X,isNativeAgentStateTransitionAllowed as je,isNativeDispatchBindingV1 as Me,isNativeHostAttestationV1 as ke}from"./native-agent-types.js";import{CONTINUITY_QUEUE_REASON_CODES as k,CONTINUITY_RECEIPT_COMPACTION_EVENT_TYPE as W,CONTINUITY_RECEIPT_RETENTION_REASON_CODES as F,DEFAULT_CONTINUITY_QUEUE_POLICY as De,computeContinuityQueueRetryAt as we,computeContinuityOutboxFailureDecision as Ue,normalizeContinuityQueuePolicy as B,normalizeContinuityQueuePriority as qe,normalizeContinuityQueueScope as Pe,normalizeContinuityRetentionPolicy as He}from"./outbox.js";const ne=".neocortex/continuity",We="continuity.sqlite",Dt=6,Fe=5e3,O="processing_output_summary",Be=2e3,de=20,$e=100,ie="Install the optional Enterprise continuity SQLite adapter with `npm install --workspace packages/client better-sqlite3 --include=optional` or reinstall Neocortex with optional dependencies enabled.",Ye=Object.freeze(["discover_more","create_epic","run_yoloop","run_yolo","run_verifier","run_review_gate","compact_context","wait_external","backoff","waiting_human","complete","abort","scheduled","running","overlap_skipped","missed_run_catchup","completed","aborted","cancelled","blocked","draft","planned"]),ce=20,Ke=new Set(Ye),Ve=/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,6})?)?(?:Z|[+-]\d{2}:?\d{2})?$/;function le(t){return typeof t=="string"&&Ke.has(t)}function ue(t){if(typeof t!="string")return;const e=t.trim();if(!(e.length===0||e.length>40)&&Ve.test(e))return e}function Qe(t){if(!Array.isArray(t))return[];const e=[];for(const a of t){if(!E(a)||!le(a.stage))continue;const r=ue(a.at);r&&e.push({stage:a.stage,at:r})}return e.slice(-ce)}function Ge(t){if(!(typeof t!="number"||!Number.isFinite(t)||t<0))return Math.floor(t)}class i extends Error{code;reasonCode;guidance;mutationBlocked;cause;constructor(e,a,r={}){super(a),this.name="ContinuitySqliteStoreError",this.code=e,this.reasonCode=e,this.guidance=r.guidance,this.cause=r.cause,this.mutationBlocked=r.mutationBlocked??e==="sqlite-integrity-failed"}}function $(t){const e=Object.freeze({schemaVersion:1,storyId:t.story_id,graphRevision:t.graph_revision,dispatchKey:t.dispatch_key,profile:t.profile,parentHandle:t.parent_handle,handle:t.handle,topology:t.topology,state:t.state,requestedAt:t.requested_at,observedAt:t.observed_at,terminalAt:t.terminal_at,freshness:t.freshness,observationSource:t.observation_source,reasonCode:t.reason_code,revision:t.revision,publicSafe:!0});if(!X(e))throw new i("sqlite-integrity-failed","Native-agent receipt failed its closed public contract.",{mutationBlocked:!0});return e}const Je=[Re,Oe,Ne,Te,Le,ve],ze=Object.freeze(["continuity_migrations","jobs","job_leases","job_events","job_receipts","goal_campaigns","goal_ledger_items","goal_verifiers","goal_review_gates","loop_jobs","artifacts","state_snapshots","headless_execution_attempts","runner_config","outbox","native_agent_dispatches","native_agent_observations"]),_e=/(raw[_-]?(?:logs?|payloads?)|std(?:out|err)|stack(?:[_-]?trace)?|transcript|objective(?:[_-]?(?:text|body|prompt))|prompt(?:[_-]?(?:body|text))?|private[_-]?(?:urls?|prompts?)|secret|token|password|protected(?:[_-]?(?:field|body|bodies|internals))?|workflow(?:[_-]?body)?|step(?:[_-]?body)?|customer(?:[_-]?(?:pii|data))?|\bpii\b|authorization|api[_-]?key|license[_-]?key|argv|args|executable(?:[_-]?path)?|binary(?:[_-]?path)?|shell|cwd|command[_-]?path|vendor(?:[_-]?(?:payload|corpus|response|output)))/i,me=/(https?:\/\/\S+|(?:^|\s)(?:\/(?:home|Users|media|mnt|tmp|var|etc|private)\/\S+|[A-Za-z]:\\\S+)|ghp_[A-Za-z0-9_]+|sk-[A-Za-z0-9_-]+|-----BEGIN [A-Z ]*PRIVATE KEY-----|authorization\s*:|password\s*=|token\s*=|api[_-]?key\s*=|license[_-]?key\s*=|raw[_ -]?(?:logs?|outputs?)\s*[:=]|transcript|stack\s*trace|stderr:|stdout:|system prompt|prompt[_ -]?(?:body|text)|workflow body|step body|\bargv\b|(?:bash|sh|zsh|powershell|pwsh)\s+-c\b|(?:\.{0,2}\/|[A-Za-z]:\\|\/)[^\s]*(?:\.exe|\.bat|\.cmd|\.ps1|\.sh|\/bin\/[^\s]+)|\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b|PROTECTED_|PRIVATE_INTERNALS|CUSTOMER_PII|\bPII\b|VENDOR_CORPUS|P180[_-]?SYNTHETIC[_-]?CONTINUITY|P185[_-]?SYNTHETIC[_-]?CONTINUITY|P187[_-]?SYNTHETIC[_-]?(?:CONTINUITY|RUNNER|WATCH)|P190[_-]?SYNTHETIC[_-]?(?:HEADLESS|CONTINUITY)|P192[_-]?SYNTHETIC[_-]?(?:PORTAL|RUNNER|SYNC))/i,Xe=new Set(["unsafe_key","raw_log","prompt_body","private_url","local_absolute_path","secret","protected_field","unbounded_text","unbounded_number","unknown_required_operation","missing"]),Ze=/(corrupt|malformed|not a database|file is not a database|database disk image is malformed)/i,et=/(foreign key constraint failed|SQLITE_CONSTRAINT_FOREIGNKEY)/i,Z="p185:v1:",tt=new Set(["schemaVersion","contractVersion","eventType","executionId","jobId","operationId","idempotencyKey","goalId","loopId","campaignId","itemId","ledgerItemIds","verifierId","verifierIds","gateId","reviewGateIds","runnerId","leaseId","status","reasonCode","reasonCodes","runtimeId","modelId","runtimeSource","headlessRuntime","headlessModel","headlessRuntimeReasonCode","originRuntime","originModel","targetRef","attemptId","attemptStatus","terminalClass","releaseBlocked","directiveHash","timeoutMs","leaseExpiresAt","heartbeatAt","notBeforeAt","maxAttempts","counts","hashes","relativePaths","artifactRefs","evidenceRefs","validationRefs","registryRefs","storyIds","epicIds","debtIds","iteration","currentStage","stageHistory","durationMs","retryCount","attemptCount","queuePriority","queueScope","queueCap","queueDepth","queueAvailable","queueBackpressure","nextTickAt","nextAttemptAt","nextRetryAt","deadLetterReasonCode","deliveryCursor","lastAttemptAt","sentAt","ackedAt","lastErrorReason","lastErrorSummary","deadLetterSummary","deadLetteredAt","processingOutputSource","publicSummary","redactionEvidence","selectorKind","targetId","targetJobId","relatedJobIds","serverAccessToLocalhost","serverHeldStream","initialCursor","targetResolutionState","targetKind","relatedJobCount","snapshotStatus","snapshotGeneratedAt","renderedSummaryCount","cursorState","currentCursor","nextCursor","cursorAdvanced","eventId","latestEventId","watchStatus","stopReason","syncVersion","transportKind","envelopeId","workspaceId","workspaceLabel","projectId","projectLabel","snapshotId","syncCursor","previousSyncCursor","nextSyncCursor","commandId","receiptId","acknowledgedAt","observedAt","generatedAt","syncStatus","commandStatus","receiptStatus","actorType","actorId","tenantId","commandKind","auditStatus","redactionCount","autonomousGovernance","governanceVersion","governanceEvidence","governanceSource","publicSafe"]),at=new Set(["foreground_invoke","scheduled_tick","runner_tick","manual_tick","headless_execution","jobs_command","invoke_watch","portal_render"]);function rt(t){return import(t)}function st(t){return`sha256:${M("sha256").update(t).digest("hex")}`}function E(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function ot(t){return t instanceof Error?t.message:String(t)}function nt(t){return E(t)&&typeof t.code=="string"?t.code:void 0}function m(t,e){if(t instanceof i)return t;const a=ot(t),r=nt(t);return r==="SQLITE_CORRUPT"||r==="SQLITE_NOTADB"||Ze.test(a)?new i("sqlite-integrity-failed","Continuity SQLite integrity check failed; durable mutation is blocked until the store is repaired or rebuilt.",{cause:t,mutationBlocked:!0}):r==="SQLITE_CONSTRAINT_FOREIGNKEY"||et.test(a)?new i("sqlite-foreign-key-failed","Continuity SQLite foreign-key validation failed; mutation was rejected.",{cause:t}):new i(e,a,{cause:t})}function g(t,e){if(typeof t=="string"){if(/\.(?:redactionEvidence|redactionSummary)\[\d+\]\.field$/.test(e)&&/^[A-Za-z0-9_.:[\]-]{1,120}$/.test(t)||/\.(?:redactionEvidence|redactionSummary)\[\d+\]\.reason$/.test(e)&&Xe.has(t))return;if(me.test(t))throw new i("sqlite-public-safety-rejected",`Continuity SQLite metadata rejected unsafe value at ${e}.`);return}if(!(t===null||typeof t=="number"||typeof t=="boolean")){if(Array.isArray(t)){t.forEach((a,r)=>g(a,`${e}[${r}]`));return}if(E(t)){for(const[a,r]of Object.entries(t)){const s=a==="terminationToken"&&e.endsWith(".releaseBlocked")&&r==="[YOLOOP_RELEASE_BLOCKED]";if(_e.test(a)&&!s)throw new i("sqlite-public-safety-rejected",`Continuity SQLite metadata rejected unsafe key at ${e}.${a}.`);g(r,`${e}.${a}`)}return}throw new i("sqlite-public-safety-rejected",`Continuity SQLite metadata rejected unsupported value at ${e}.`)}}function b(t,e){if(t!==void 0&&me.test(t))throw new i("sqlite-public-safety-rejected",`Continuity SQLite text rejected unsafe value at ${e}.`)}function Y(t,e,a=240){if(t.trim().length===0)throw new i("sqlite-public-safety-rejected",`Continuity SQLite processing summary text is required at ${e}.`);if(b(t,e),t.length>a)throw new i("sqlite-public-safety-rejected",`Continuity SQLite processing summary text exceeded the public bound at ${e}.`)}function K(t){const e={};if(!t)return e;for(const[a,r]of Object.entries(t))if(tt.has(a)&&!_e.test(a))try{g(r,`metadata.${a}`),e[a]=r}catch{}return e}function dt(t){const e={...K(t.metadata),schemaVersion:1,contractVersion:"continuity-v1",eventType:O,executionId:t.executionId,jobId:t.jobId,operationId:t.executionId,publicSummary:t.publicSummary};return t.idempotencyKey&&(e.idempotencyKey=t.idempotencyKey),t.goalId&&(e.goalId=t.goalId),t.loopId&&(e.loopId=t.loopId),t.reasonCode&&(e.reasonCode=t.reasonCode),t.source&&at.has(t.source)&&(e.processingOutputSource=t.source),K(e)}function he(t){const e=Math.floor(t??de);return Number.isFinite(e)?Math.max(1,Math.min($e,e)):de}function it(t){if(t.eventId&&t.eventId.trim().length>0)return t.eventId;const e=t.executionId.replace(/[^a-z0-9_.:-]+/gi,"-").replace(/^-+|-+$/g,"");return e.length>0?`event-processing-summary-${e.slice(0,160)}`:`event-processing-summary-${M("sha256").update(t.executionId).digest("hex").slice(0,32)}`}function ct(t){return`${Z}${J.from(JSON.stringify([t.created_at,t.event_id]),"utf8").toString("base64url")}`}function lt(t){if(t?.startsWith(Z))try{const e=t.slice(Z.length),a=JSON.parse(J.from(e,"base64url").toString("utf8"));if(!Array.isArray(a)||a.length!==2)return;const[r,s]=a;return typeof r!="string"||typeof s!="string"?void 0:{createdAt:r,eventId:s}}catch{return}}function D(t,e){if(!e)return;const a=e.trim().replace(/^@/,"");if(t==="job"&&/^job-J\d{3,}$/i.test(a))return`job-J${a.match(/\d+$/)?.[0]??""}`;if(t==="goal"&&/^goal-G\d{3,}$/i.test(a))return`goal-G${a.match(/\d+$/)?.[0]??""}`;if(t==="loop"&&/^loop-L\d{3,}$/i.test(a))return`loop-L${a.match(/\d+$/)?.[0]??""}`}function ye(t){return t?q(t.map(e=>D("job",e)).filter(e=>!!e)):[]}function pe(t){const e=t.target;if(!e){const r=D("job",t.jobId);return r?{sql:" AND job_id = ?",params:[r]}:{sql:"",params:[]}}const a=e.selectorKind;if(a==="job"){const r=D("job",e.jobId??e.targetId??t.jobId);return r?{sql:" AND job_id = ?",params:[r]}:{sql:" AND 1 = 0",params:[]}}if(a==="goal"){const r=D("goal",e.goalId??e.targetId);if(!r)return{sql:" AND 1 = 0",params:[]};const s=ye(e.relatedJobIds);return{sql:` AND (job_id = ? OR json_extract(metadata_json, '$.goalId') = ?${s.length>0?` OR job_id IN (${s.map(()=>"?").join(", ")})`:""})`,params:[r,r,...s]}}if(a==="loop"){const r=D("loop",e.loopId??e.targetId);if(!r)return{sql:" AND 1 = 0",params:[]};const s=ye(e.relatedJobIds);return{sql:` AND (job_id = ? OR json_extract(metadata_json, '$.loopId') = ?${s.length>0?` OR job_id IN (${s.map(()=>"?").join(", ")})`:""})`,params:[r,r,...s]}}return{sql:" AND 1 = 0",params:[]}}function be(t){const e={event_id:t.event_id,job_id:t.job_id,execution_id:t.operation_id,public_summary:t.summary,reason_code:t.reason_code,created_at:t.created_at,metadata_json:t.metadata_json};return{...e,cursor:ct(e)}}function S(t){return t?(g(t,"metadata"),JSON.stringify(t)):null}function ut(t,e,a){const r=d=>`sha256:${M("sha256").update(d.map(o=>[o.id,o.status,o.reasonCode].join(":")).sort().join(`
|
|
2
|
+
`)).digest("hex")}`,s=r(e),n=r(a);return{receipts:s,outputs:n,aggregate:`sha256:${M("sha256").update(`${t}:${s}:${n}`).digest("hex")}`}}function _t(t){if(!t)return{};try{const e=JSON.parse(t);return E(e)?{...e}:{}}catch{return{}}}function c(t,e,a,r=!1){if(t===void 0){if(r)throw new i("sqlite-public-safety-rejected",`Continuity SQLite text is required at ${e}.`);return}if(b(t,e),t.trim().length===0||t.length>a)throw new i("sqlite-public-safety-rejected",`Continuity SQLite text exceeded the public bound at ${e}.`)}function N(t,e){if(t!==void 0&&(!Number.isFinite(t)||t<0||Math.floor(t)!==t))throw new i("sqlite-public-safety-rejected",`Continuity SQLite numeric value rejected at ${e}.`)}function v(t){c(t.outboxId,"outbox.outboxId",160,!0),c(t.jobId,"outbox.jobId",160),c(t.operationId,"outbox.operationId",160),c(t.idempotencyKey,"outbox.idempotencyKey",160),c(t.type,"outbox.type",80),c(t.status,"outbox.status",40),c(t.deliveryCursor,"outbox.deliveryCursor",200),c(t.timestamp,"outbox.timestamp",80),c(t.reasonCode,"outbox.reasonCode",100),c(t.summary,"outbox.summary",240)}function Ee(t){return t==="pending"||t==="sent"||t==="acked"||t==="failed"||t==="dead_letter"}function mt(t){return typeof t=="string"&&Ee(t)?t:"failed"}function y(t){return typeof t=="string"&&t.length>0?t:void 0}function ht(t){if(typeof t!="string"||t.trim().length===0)return{};try{const e=JSON.parse(t);return E(e)?(g(e,"outbox.payload"),e):{}}catch{return{}}}function yt(t){return{outboxId:String(t.outbox_id??""),...y(t.job_id)?{jobId:y(t.job_id)}:{},operationId:String(t.operation_id??""),...y(t.idempotency_key)?{idempotencyKey:y(t.idempotency_key)}:{},type:String(t.type??""),status:mt(t.status),payload:ht(t.payload_json),retryCount:typeof t.retry_count=="number"?t.retry_count:0,attemptCount:typeof t.attempt_count=="number"?t.attempt_count:0,...y(t.delivery_cursor)?{deliveryCursor:y(t.delivery_cursor)}:{},...y(t.next_attempt_at)?{nextAttemptAt:y(t.next_attempt_at)}:{},...y(t.last_attempt_at)?{lastAttemptAt:y(t.last_attempt_at)}:{},...y(t.sent_at)?{sentAt:y(t.sent_at)}:{},...y(t.acked_at)?{ackedAt:y(t.acked_at)}:{},...y(t.last_error_code)?{lastErrorCode:y(t.last_error_code)}:{},...y(t.last_error_reason)?{lastErrorReason:y(t.last_error_reason)}:{},...y(t.last_error_summary)?{lastErrorSummary:y(t.last_error_summary)}:{},...y(t.dead_letter_summary)?{deadLetterSummary:y(t.dead_letter_summary)}:{},...y(t.dead_lettered_at)?{deadLetteredAt:y(t.dead_lettered_at)}:{},createdAt:String(t.created_at??""),updatedAt:String(t.updated_at??""),publicSafe:!0}}function C(t){return{...t,publicSafe:!0}}function T(t,e){return t===void 0?null:(g(t,e),JSON.stringify(t))}function w(t){if(!t)return;const e=q(t.map(a=>a.replace(/\\/g,"/"))).filter(a=>a.length>0).slice(0,50);for(const[a,r]of e.entries()){c(r,`relativePaths[${a}]`,240,!0);const s=r.split("/").filter(Boolean);if(r.startsWith("/")||r.startsWith("~")||r.includes("://")||/^[A-Za-z]:\//.test(r)||s.includes(".."))throw new i("sqlite-public-safety-rejected",`Continuity SQLite rejected non-relative path at relativePaths[${a}].`)}return e}function V(t){const e={...K(t.metadata),schemaVersion:1,contractVersion:"continuity-v1",attemptId:t.attemptId,executionId:t.executionId,jobId:t.jobId};return t.directiveHash&&(e.directiveHash=t.directiveHash),t.runtimeId&&(e.runtimeId=t.runtimeId),t.modelId&&(e.modelId=t.modelId),t.runtimeSource&&(e.runtimeSource=t.runtimeSource),t.status&&(e.attemptStatus=t.status),t.reasonCode&&(e.reasonCode=t.reasonCode),t.durationMs!==void 0&&(e.durationMs=t.durationMs),t.retryCount!==void 0&&(e.retryCount=t.retryCount),t.attemptCount!==void 0&&(e.attemptCount=t.attemptCount),t.maxAttempts!==void 0&&(e.maxAttempts=t.maxAttempts),t.queuePriority!==void 0&&(e.queuePriority=t.queuePriority),t.queueScope&&(e.queueScope=t.queueScope),t.nextRetryAt&&(e.nextRetryAt=t.nextRetryAt),t.deadLetterReasonCode&&(e.deadLetterReasonCode=t.deadLetterReasonCode),t.counts&&(e.counts=t.counts),t.hashes&&(e.hashes=t.hashes),t.redactionSummary&&(e.redactionEvidence=t.redactionSummary),t.relativePaths&&(e.relativePaths=w(t.relativePaths)),K(e)}function L(t){c(t.attemptId,"headless_execution.attemptId",160,!0),c(t.executionId,"headless_execution.executionId",160,!0),c(t.jobId,"headless_execution.jobId",160,!0),c(t.directiveHash,"headless_execution.directiveHash",160),c(t.targetRef,"headless_execution.targetRef",240),c(t.storyId,"headless_execution.storyId",120),c(t.epicId,"headless_execution.epicId",120),c(t.runtimeId,"headless_execution.runtimeId",80),c(t.modelId,"headless_execution.modelId",120),c(t.runtimeSource,"headless_execution.runtimeSource",40),c(t.reasonCode,"headless_execution.reasonCode",100),c(t.publicSummary,"headless_execution.publicSummary",240),c(t.leaseId,"headless_execution.leaseId",160),c(t.runnerId,"headless_execution.runnerId",160),c(t.timestamp,"headless_execution.timestamp",80)}function U(t){if(Array.isArray(t)){const e=t[0];return E(e)?Object.values(e)[0]:e}return E(t)?Object.values(t)[0]:t}function pt(t){return ze.includes(t)}function q(t){return Array.from(new Set(t))}function bt(t){const e=E(t)&&"default"in t?t.default:t;if(typeof e!="function")throw new i("sqlite-adapter-unavailable","Optional better-sqlite3 adapter did not export a database constructor.",{guidance:ie});const a=e;return{adapterName:"better-sqlite3",open(r,s){return new a(r,s)}}}async function Et(t=rt){try{const e=await t("better-sqlite3");return{ok:!0,adapter:bt(e)}}catch(e){return e instanceof i?{ok:!1,error:e}:{ok:!1,error:new i("sqlite-adapter-unavailable","Optional Enterprise continuity SQLite adapter better-sqlite3 is unavailable.",{cause:e,guidance:ie})}}}function Ie(t){return se(t,ne,We)}function It(t){const e=se(t,ne);ae(e,{recursive:!0,mode:448});try{z(e,448)}catch{}return e}async function wt(t){const e=t.adapter??await Ce(t.adapterLoader),a=t.filePath??Ie(t.projectRoot);if(t.strictReadOnly)fe(a);else if(t.filePath){ae(re(a),{recursive:!0,mode:448});try{z(re(a),448)}catch{}}else It(t.projectRoot);let r;try{r=e.open(a,t.strictReadOnly?{fileMustExist:!0,readonly:!0}:void 0)}catch(n){throw m(n,"sqlite-integrity-failed")}const s=new Ct({db:r,filePath:a,adapterName:e.adapterName,busyTimeoutMs:t.busyTimeoutMs??Fe,integrityCheck:t.integrityCheck??!0,now:t.now??(()=>new Date().toISOString())});try{if(t.strictReadOnly)return s.runIntegrityCheck(),s.getSchemaVersion(),s;if(s.initialize(),te(a))try{z(a,384)}catch{}return s}catch(n){try{r.close()}catch{}throw n}}function fe(t){if(!te(t))throw new i("sqlite-projection-missing","Continuity database is unavailable for physical read-only inspection.",{mutationBlocked:!0});let e;try{const a=Ae(t,"r");try{if(e=J.alloc(20),xe(a,e,0,e.length,0)<e.length)throw new Error("short-header")}finally{ge(a)}}catch(a){throw new i("sqlite-integrity-failed","Continuity database header could not be inspected read-only.",{cause:a,mutationBlocked:!0})}if(e.subarray(0,16).toString("ascii")!=="SQLite format 3\0")throw new i("sqlite-integrity-failed","Continuity database header is not a recognized SQLite database.",{mutationBlocked:!0});if(e[18]===2||e[19]===2)throw new i("sqlite-projection-wal-unsafe","Continuity database requires an unsafe WAL read profile.",{mutationBlocked:!0})}async function ft(t){const e=Ie(t.projectRoot);fe(e);const a=t.adapter??await Ce(t.adapterLoader);let r,s=!1;try{r=a.open(e,{fileMustExist:!0,readonly:!0}),s=!0;const d=U(r.pragma("integrity_check",{simple:!0}));if(String(d).toLowerCase()!=="ok")throw new Error("integrity")}catch(d){throw new i(s?"sqlite-integrity-failed":"sqlite-projection-adapter-incompatible","Continuity database failed physical read-only inspection.",{cause:d,mutationBlocked:!0})}const n=d=>{const o=d.trim().replace(/^\(+/u,"").toLowerCase();if(!/^(select|with\s)/u.test(o)||/\b(insert|update|delete|replace|create|alter|drop|pragma|attach|detach|vacuum|reindex)\b/u.test(o))throw new i("sqlite-mutation-blocked","Statement is not in the physical read-only query profile.",{mutationBlocked:!0})};return Object.freeze({filePath:e,adapterName:a.adapterName,all(d,...o){return n(d),r.prepare(d).all(...o)},get(d,...o){return n(d),r.prepare(d).get(...o)},close(){r.close()}})}async function Ut(t){if(!Number.isSafeInteger(t.authorityRevision)||t.authorityRevision<0||!/^[a-f0-9]{64}$/.test(t.sourceDigest))throw new i("sqlite-integrity-failed","Epic operational SQLite binding input is invalid.",{mutationBlocked:!0});let e;try{e=await ft(t);let a;try{a=e.all("SELECT sha256, metadata_json FROM state_snapshots WHERE kind = ? ORDER BY captured_at DESC LIMIT 2","epic-operational-state")}catch{return Object.freeze({schemaVersion:"EpicOperationalSqliteBindingInspection/1.0",status:"unavailable",authorityRevision:t.authorityRevision,sourceDigest:null,adapterName:e.adapterName,reasonCodes:["sqlite-projection-schema-unsupported"],publicSafe:!0})}const r=a.filter(s=>{if(!E(s)||s.sha256!==t.sourceDigest||typeof s.metadata_json!="string")return!1;try{const n=JSON.parse(s.metadata_json);return E(n)&&n.authorityRevision===t.authorityRevision}catch{return!1}});return Object.freeze({schemaVersion:"EpicOperationalSqliteBindingInspection/1.0",status:r.length===1?"bound":"unbound",authorityRevision:t.authorityRevision,sourceDigest:r.length===1?t.sourceDigest:null,adapterName:e.adapterName,reasonCodes:r.length===1?["epic-operational-sqlite-binding-verified"]:["epic-operational-projection-unbound"],publicSafe:!0})}catch(a){const r=a instanceof i?a.reasonCode:"sqlite-integrity-failed";return Object.freeze({schemaVersion:"EpicOperationalSqliteBindingInspection/1.0",status:"unavailable",authorityRevision:t.authorityRevision,sourceDigest:null,adapterName:null,reasonCodes:[r],publicSafe:!0})}finally{e?.close()}}async function Ce(t){const e=t?await t():await Et();if(!e.ok)throw e.error;return e.adapter}class Ct{filePath;adapterName;db;busyTimeoutMs;integrityCheck;now;mutationBlocked=!1;constructor(e){this.db=e.db,this.filePath=e.filePath,this.adapterName=e.adapterName,this.busyTimeoutMs=e.busyTimeoutMs,this.integrityCheck=e.integrityCheck,this.now=e.now}initialize(){try{this.configureConnection(),this.integrityCheck&&this.runIntegrityCheck(),this.ensureMigrationTable(),this.applyMigrations(),this.integrityCheck&&this.runIntegrityCheck()}catch(e){throw m(e,"sqlite-migration-failed")}}close(){this.db.close()}pragmaValue(e){return U(this.db.pragma(e,{simple:!0}))}runIntegrityCheck(){try{if(String(U(this.db.pragma("integrity_check",{simple:!0}))??"").toLowerCase()!=="ok")throw this.mutationBlocked=!0,new i("sqlite-integrity-failed","Continuity SQLite integrity check failed; durable mutation is blocked until the store is repaired or rebuilt.",{mutationBlocked:!0})}catch(e){throw this.mutationBlocked=!0,m(e,"sqlite-integrity-failed")}}getSchemaVersion(){const e=this.db.prepare("SELECT COALESCE(MAX(version), 0) AS version FROM continuity_migrations").get();return E(e)&&typeof e.version=="number"?e.version:0}countRows(e){if(!pt(e))throw new i("sqlite-public-safety-rejected","Unknown continuity table name.");const a=this.db.prepare(`SELECT COUNT(*) AS count FROM ${e}`).get();return E(a)&&typeof a.count=="number"?a.count:0}reserveNativeAgentDispatch(e){if(this.assertCanMutate(),!Me(e.binding)||!ke(e.hostAttestation))throw new i("sqlite-public-safety-rejected","Native-agent reservation rejected invalid binding or host evidence.");const a={schemaVersion:1,storyId:e.binding.storyId,graphRevision:e.binding.graphRevision.replace(/^sha256:/,"").toLowerCase(),dispatchKey:e.binding.dispatchKey.toLowerCase(),profile:"neocortex",parentHandle:null,handle:null,topology:e.topology??"native-agent",state:"requested",requestedAt:e.requestedAt,observedAt:null,terminalAt:null,freshness:"unknown",observationSource:"local-reservation",reasonCode:e.reasonCode??"native-agent-dispatch-reserved",revision:0,publicSafe:!0};if(!X(a))throw new i("sqlite-public-safety-rejected","Native-agent reservation rejected unsafe public fields.");try{return this.runInTransaction(()=>{const r=this.db.prepare(`
|
|
1119
3
|
INSERT OR IGNORE INTO native_agent_dispatches (
|
|
1120
4
|
dispatch_key, story_id, graph_revision, profile,
|
|
1121
5
|
host_kind, host_version, host_origin, host_confidence,
|
|
1122
6
|
parent_handle, handle, topology, state, requested_at, observed_at,
|
|
1123
7
|
terminal_at, freshness, observation_source, reason_code, revision
|
|
1124
8
|
) VALUES (?, ?, ?, 'neocortex', ?, ?, ?, ?, NULL, NULL, ?, 'requested', ?, NULL, NULL, 'unknown', 'local-reservation', ?, 0)
|
|
1125
|
-
`).run(
|
|
1126
|
-
const row = this.findNativeAgentDispatchRow(candidate.dispatchKey, candidate.storyId, candidate.graphRevision);
|
|
1127
|
-
if (!row)
|
|
1128
|
-
throw new Error('native-agent-reservation-readback-missing');
|
|
1129
|
-
if (row.dispatch_key !== candidate.dispatchKey
|
|
1130
|
-
|| row.story_id !== candidate.storyId
|
|
1131
|
-
|| row.graph_revision !== candidate.graphRevision) {
|
|
1132
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent reservation conflicts with an existing story graph binding.');
|
|
1133
|
-
}
|
|
1134
|
-
return Object.freeze({
|
|
1135
|
-
receipt: toNativeAgentReceipt(row),
|
|
1136
|
-
reserved: inserted.changes === 1,
|
|
1137
|
-
replay: inserted.changes === 0,
|
|
1138
|
-
});
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
catch (error) {
|
|
1142
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
observeNativeAgentDispatch(input) {
|
|
1146
|
-
this.assertCanMutate();
|
|
1147
|
-
try {
|
|
1148
|
-
return this.runInTransaction(() => {
|
|
1149
|
-
const currentRow = this.db.prepare('SELECT * FROM native_agent_dispatches WHERE dispatch_key = ?').get(input.dispatchKey);
|
|
1150
|
-
if (!currentRow) {
|
|
1151
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent dispatch reservation is missing.');
|
|
1152
|
-
}
|
|
1153
|
-
const current = toNativeAgentReceipt(currentRow);
|
|
1154
|
-
const terminalAt = NATIVE_AGENT_TERMINAL_STATES.has(input.state)
|
|
1155
|
-
? input.terminalAt ?? input.observedAt
|
|
1156
|
-
: null;
|
|
1157
|
-
const handle = input.handle ?? current.handle;
|
|
1158
|
-
const parentHandle = input.parentHandle === undefined ? current.parentHandle : input.parentHandle;
|
|
1159
|
-
const candidate = {
|
|
1160
|
-
...current,
|
|
1161
|
-
handle,
|
|
1162
|
-
parentHandle,
|
|
1163
|
-
topology: input.topology,
|
|
1164
|
-
state: input.state,
|
|
1165
|
-
observedAt: input.observedAt,
|
|
1166
|
-
terminalAt,
|
|
1167
|
-
freshness: input.freshness,
|
|
1168
|
-
observationSource: input.observationSource,
|
|
1169
|
-
reasonCode: input.reasonCode,
|
|
1170
|
-
revision: current.revision + 1,
|
|
1171
|
-
};
|
|
1172
|
-
if (!isNativeAgentDispatchReceiptV1(candidate)) {
|
|
1173
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Native-agent observation rejected unsafe or inconsistent fields.');
|
|
1174
|
-
}
|
|
1175
|
-
if (current.handle && input.handle && current.handle !== input.handle) {
|
|
1176
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent observation handle conflicts with the reserved dispatch.');
|
|
1177
|
-
}
|
|
1178
|
-
if (!isNativeAgentStateTransitionAllowed(current.state, input.state)) {
|
|
1179
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent observation attempted a terminal or non-monotonic regression.');
|
|
1180
|
-
}
|
|
1181
|
-
const sameObservation = current.state === candidate.state
|
|
1182
|
-
&& current.handle === candidate.handle
|
|
1183
|
-
&& current.parentHandle === candidate.parentHandle
|
|
1184
|
-
&& current.topology === candidate.topology
|
|
1185
|
-
&& current.observedAt === candidate.observedAt
|
|
1186
|
-
&& current.terminalAt === candidate.terminalAt
|
|
1187
|
-
&& current.freshness === candidate.freshness
|
|
1188
|
-
&& current.observationSource === candidate.observationSource
|
|
1189
|
-
&& current.reasonCode === candidate.reasonCode;
|
|
1190
|
-
if (sameObservation)
|
|
1191
|
-
return Object.freeze({ receipt: current, applied: false, replay: true });
|
|
1192
|
-
if (input.expectedRevision !== current.revision) {
|
|
1193
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent observation CAS revision conflict.');
|
|
1194
|
-
}
|
|
1195
|
-
if (current.observedAt && Date.parse(input.observedAt) < Date.parse(current.observedAt)) {
|
|
1196
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent observation timestamp regressed.');
|
|
1197
|
-
}
|
|
1198
|
-
const updated = this.db.prepare(`
|
|
9
|
+
`).run(a.dispatchKey,a.storyId,a.graphRevision,e.hostAttestation.hostKind,e.hostAttestation.hostVersion,e.hostAttestation.origin,e.hostAttestation.confidence,a.topology,a.requestedAt,a.reasonCode),s=this.findNativeAgentDispatchRow(a.dispatchKey,a.storyId,a.graphRevision);if(!s)throw new Error("native-agent-reservation-readback-missing");if(s.dispatch_key!==a.dispatchKey||s.story_id!==a.storyId||s.graph_revision!==a.graphRevision)throw new i("sqlite-mutation-failed","Native-agent reservation conflicts with an existing story graph binding.");return Object.freeze({receipt:$(s),reserved:r.changes===1,replay:r.changes===0})})}catch(r){throw m(r,"sqlite-mutation-failed")}}observeNativeAgentDispatch(e){this.assertCanMutate();try{return this.runInTransaction(()=>{const a=this.db.prepare("SELECT * FROM native_agent_dispatches WHERE dispatch_key = ?").get(e.dispatchKey);if(!a)throw new i("sqlite-mutation-failed","Native-agent dispatch reservation is missing.");const r=$(a),s=oe.has(e.state)?e.terminalAt??e.observedAt:null,n=e.handle??r.handle,d=e.parentHandle===void 0?r.parentHandle:e.parentHandle,o={...r,handle:n,parentHandle:d,topology:e.topology,state:e.state,observedAt:e.observedAt,terminalAt:s,freshness:e.freshness,observationSource:e.observationSource,reasonCode:e.reasonCode,revision:r.revision+1};if(!X(o))throw new i("sqlite-public-safety-rejected","Native-agent observation rejected unsafe or inconsistent fields.");if(r.handle&&e.handle&&r.handle!==e.handle)throw new i("sqlite-mutation-failed","Native-agent observation handle conflicts with the reserved dispatch.");if(!je(r.state,e.state))throw new i("sqlite-mutation-failed","Native-agent observation attempted a terminal or non-monotonic regression.");if(r.state===o.state&&r.handle===o.handle&&r.parentHandle===o.parentHandle&&r.topology===o.topology&&r.observedAt===o.observedAt&&r.terminalAt===o.terminalAt&&r.freshness===o.freshness&&r.observationSource===o.observationSource&&r.reasonCode===o.reasonCode)return Object.freeze({receipt:r,applied:!1,replay:!0});if(e.expectedRevision!==r.revision)throw new i("sqlite-mutation-failed","Native-agent observation CAS revision conflict.");if(r.observedAt&&Date.parse(e.observedAt)<Date.parse(r.observedAt))throw new i("sqlite-mutation-failed","Native-agent observation timestamp regressed.");if(this.db.prepare(`
|
|
1199
10
|
UPDATE native_agent_dispatches
|
|
1200
11
|
SET parent_handle = ?, handle = ?, topology = ?, state = ?, observed_at = ?,
|
|
1201
12
|
terminal_at = ?, freshness = ?, observation_source = ?, reason_code = ?, revision = revision + 1
|
|
1202
13
|
WHERE dispatch_key = ? AND revision = ?
|
|
1203
|
-
`).run(
|
|
1204
|
-
if (updated.changes !== 1) {
|
|
1205
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-failed', 'Native-agent observation CAS update was not applied.');
|
|
1206
|
-
}
|
|
1207
|
-
this.db.prepare(`
|
|
14
|
+
`).run(o.parentHandle,o.handle,o.topology,o.state,o.observedAt,o.terminalAt,o.freshness,o.observationSource,o.reasonCode,o.dispatchKey,e.expectedRevision).changes!==1)throw new i("sqlite-mutation-failed","Native-agent observation CAS update was not applied.");return this.db.prepare(`
|
|
1208
15
|
INSERT INTO native_agent_observations (
|
|
1209
16
|
dispatch_key, sequence, state, handle, parent_handle, topology,
|
|
1210
17
|
observed_at, terminal_at, freshness, observation_source, reason_code
|
|
1211
18
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1212
|
-
`).run(
|
|
1213
|
-
return Object.freeze({ receipt: candidate, applied: true, replay: false });
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
catch (error) {
|
|
1217
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
getNativeAgentDispatchStatus(dispatchKey, options = {}) {
|
|
1221
|
-
const row = this.db.prepare('SELECT * FROM native_agent_dispatches WHERE dispatch_key = ?').get(dispatchKey);
|
|
1222
|
-
if (!row)
|
|
1223
|
-
return null;
|
|
1224
|
-
const receipt = toNativeAgentReceipt(row);
|
|
1225
|
-
const staleAfterMs = Math.max(1_000, options.staleAfterMs ?? 30_000);
|
|
1226
|
-
const nowMs = Date.parse(options.nowIso ?? this.now());
|
|
1227
|
-
const observedMs = receipt.observedAt ? Date.parse(receipt.observedAt) : NaN;
|
|
1228
|
-
if (!NATIVE_AGENT_TERMINAL_STATES.has(receipt.state)
|
|
1229
|
-
&& Number.isFinite(nowMs)
|
|
1230
|
-
&& (!Number.isFinite(observedMs) || nowMs - observedMs > staleAfterMs)) {
|
|
1231
|
-
return Object.freeze({
|
|
1232
|
-
...receipt,
|
|
1233
|
-
state: 'unknown',
|
|
1234
|
-
freshness: receipt.observedAt ? 'stale' : 'unknown',
|
|
1235
|
-
reasonCode: receipt.observedAt ? 'native-agent-observation-stale' : 'native-agent-awaiting-host-evidence',
|
|
1236
|
-
});
|
|
1237
|
-
}
|
|
1238
|
-
return receipt;
|
|
1239
|
-
}
|
|
1240
|
-
listRecentNativeAgentDispatches(limit = 20) {
|
|
1241
|
-
const bounded = Math.max(1, Math.min(50, Math.floor(limit)));
|
|
1242
|
-
const rows = this.db.prepare(`
|
|
19
|
+
`).run(o.dispatchKey,o.revision,o.state,o.handle,o.parentHandle,o.topology,o.observedAt,o.terminalAt,o.freshness,o.observationSource,o.reasonCode),Object.freeze({receipt:o,applied:!0,replay:!1})})}catch(a){throw m(a,"sqlite-mutation-failed")}}getNativeAgentDispatchStatus(e,a={}){const r=this.db.prepare("SELECT * FROM native_agent_dispatches WHERE dispatch_key = ?").get(e);if(!r)return null;const s=$(r),n=Math.max(1e3,a.staleAfterMs??3e4),d=Date.parse(a.nowIso??this.now()),o=s.observedAt?Date.parse(s.observedAt):NaN;return!oe.has(s.state)&&Number.isFinite(d)&&(!Number.isFinite(o)||d-o>n)?Object.freeze({...s,state:"unknown",freshness:s.observedAt?"stale":"unknown",reasonCode:s.observedAt?"native-agent-observation-stale":"native-agent-awaiting-host-evidence"}):s}listRecentNativeAgentDispatches(e=20){const a=Math.max(1,Math.min(50,Math.floor(e))),r=this.db.prepare(`
|
|
1243
20
|
SELECT * FROM native_agent_dispatches
|
|
1244
21
|
ORDER BY COALESCE(observed_at, requested_at) DESC, dispatch_key DESC
|
|
1245
22
|
LIMIT ?
|
|
1246
|
-
`).all(
|
|
1247
|
-
return Object.freeze(rows.map(toNativeAgentReceipt));
|
|
1248
|
-
}
|
|
1249
|
-
findNativeAgentDispatchRow(dispatchKey, storyId, graphRevision) {
|
|
1250
|
-
return this.db.prepare(`
|
|
23
|
+
`).all(a);return Object.freeze(r.map($))}findNativeAgentDispatchRow(e,a,r){return this.db.prepare(`
|
|
1251
24
|
SELECT * FROM native_agent_dispatches
|
|
1252
25
|
WHERE dispatch_key = ? OR (story_id = ? AND graph_revision = ?)
|
|
1253
26
|
ORDER BY CASE WHEN dispatch_key = ? THEN 0 ELSE 1 END
|
|
1254
27
|
LIMIT 1
|
|
1255
|
-
`).get(
|
|
1256
|
-
}
|
|
1257
|
-
getLocalConsoleAttachmentState(jobId) {
|
|
1258
|
-
assertBoundedPublicText(jobId, 'local_console.jobId', 160, true);
|
|
1259
|
-
const row = this.db.prepare(`
|
|
28
|
+
`).get(e,a,r,e)}getLocalConsoleAttachmentState(e){c(e,"local_console.jobId",160,!0);const a=this.db.prepare(`
|
|
1260
29
|
SELECT j.job_id AS job_id,
|
|
1261
30
|
j.kind AS kind,
|
|
1262
31
|
j.status AS status,
|
|
@@ -1265,210 +34,41 @@ export class ContinuitySqliteStore {
|
|
|
1265
34
|
FROM jobs j
|
|
1266
35
|
WHERE j.job_id = ?
|
|
1267
36
|
LIMIT 1
|
|
1268
|
-
`).get(jobId);
|
|
1269
|
-
if (!isRecord(row) || typeof row.job_id !== 'string')
|
|
1270
|
-
return null;
|
|
1271
|
-
return {
|
|
1272
|
-
jobId: row.job_id,
|
|
1273
|
-
kind: typeof row.kind === 'string' ? row.kind : 'unknown',
|
|
1274
|
-
status: typeof row.status === 'string' ? row.status : 'unknown',
|
|
1275
|
-
activeLeaseCount: typeof row.active_lease_count === 'number' ? row.active_lease_count : 0,
|
|
1276
|
-
runningHeadlessAttemptCount: typeof row.running_headless_attempt_count === 'number' ? row.running_headless_attempt_count : 0,
|
|
1277
|
-
publicSafe: true,
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
recordJob(input) {
|
|
1281
|
-
this.assertCanMutate();
|
|
1282
|
-
assertPublicSafeText(input.summary, 'jobs.summary');
|
|
1283
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
1284
|
-
try {
|
|
1285
|
-
const result = this.db.prepare(`
|
|
37
|
+
`).get(e);return!E(a)||typeof a.job_id!="string"?null:{jobId:a.job_id,kind:typeof a.kind=="string"?a.kind:"unknown",status:typeof a.status=="string"?a.status:"unknown",activeLeaseCount:typeof a.active_lease_count=="number"?a.active_lease_count:0,runningHeadlessAttemptCount:typeof a.running_headless_attempt_count=="number"?a.running_headless_attempt_count:0,publicSafe:!0}}recordJob(e){this.assertCanMutate(),b(e.summary,"jobs.summary");const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
1286
38
|
INSERT OR IGNORE INTO jobs (
|
|
1287
39
|
job_id, kind, status, summary, reason_code, created_at, updated_at,
|
|
1288
40
|
next_run_at, goal_id, loop_id, campaign_id, operation_id,
|
|
1289
41
|
idempotency_key, metadata_json
|
|
1290
42
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1291
|
-
`).run(
|
|
1292
|
-
return this.toWriteResult(result);
|
|
1293
|
-
}
|
|
1294
|
-
catch (error) {
|
|
1295
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
appendEvent(input) {
|
|
1299
|
-
this.assertCanMutate();
|
|
1300
|
-
assertPublicSafeText(input.summary, 'job_events.summary');
|
|
1301
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
1302
|
-
try {
|
|
1303
|
-
const result = this.db.prepare(`
|
|
43
|
+
`).run(e.jobId,e.kind,e.status,e.summary??null,e.reasonCode??null,e.createdAt,e.updatedAt,e.nextRunAt??null,e.goalId??null,e.loopId??null,e.campaignId??null,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}appendEvent(e){this.assertCanMutate(),b(e.summary,"job_events.summary");const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
1304
44
|
INSERT OR IGNORE INTO job_events (
|
|
1305
45
|
event_id, job_id, operation_id, idempotency_key, event_sequence,
|
|
1306
46
|
type, summary, reason_code, created_at, metadata_json
|
|
1307
47
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1308
|
-
`).run(
|
|
1309
|
-
return this.toWriteResult(result);
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
recordProcessingOutputSummary(input) {
|
|
1316
|
-
assertProcessingSummaryText(input.executionId, 'processing_output_summary.executionId', 160);
|
|
1317
|
-
assertProcessingSummaryText(input.jobId, 'processing_output_summary.jobId', 160);
|
|
1318
|
-
assertProcessingSummaryText(input.publicSummary, 'processing_output_summary.publicSummary', CONTINUITY_PROCESSING_OUTPUT_SUMMARY_MAX_CHARS);
|
|
1319
|
-
assertProcessingSummaryText(input.createdAt, 'processing_output_summary.createdAt', 80);
|
|
1320
|
-
assertPublicSafeText(input.eventId, 'processing_output_summary.eventId');
|
|
1321
|
-
assertPublicSafeText(input.idempotencyKey, 'processing_output_summary.idempotencyKey');
|
|
1322
|
-
assertPublicSafeText(input.goalId, 'processing_output_summary.goalId');
|
|
1323
|
-
assertPublicSafeText(input.loopId, 'processing_output_summary.loopId');
|
|
1324
|
-
assertPublicSafeText(input.reasonCode, 'processing_output_summary.reasonCode');
|
|
1325
|
-
assertPublicSafeText(input.source, 'processing_output_summary.source');
|
|
1326
|
-
const result = this.appendEvent({
|
|
1327
|
-
eventId: processingSummaryEventId(input),
|
|
1328
|
-
jobId: input.jobId,
|
|
1329
|
-
operationId: input.executionId,
|
|
1330
|
-
idempotencyKey: input.idempotencyKey ?? input.executionId,
|
|
1331
|
-
type: CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE,
|
|
1332
|
-
summary: input.publicSummary,
|
|
1333
|
-
reasonCode: input.reasonCode,
|
|
1334
|
-
createdAt: input.createdAt,
|
|
1335
|
-
metadata: buildProcessingOutputSummaryMetadata(input),
|
|
1336
|
-
});
|
|
1337
|
-
// P195.04: On a freshly applied tick summary, persist the producer-emitted
|
|
1338
|
-
// stage/iteration into the job row and append one bounded stageHistory entry.
|
|
1339
|
-
// Gating on `inserted` keeps this idempotent across replays and runner retries.
|
|
1340
|
-
if (result.inserted) {
|
|
1341
|
-
this.recordJobStageProgress({
|
|
1342
|
-
jobId: input.jobId,
|
|
1343
|
-
currentStage: input.metadata?.currentStage,
|
|
1344
|
-
iteration: input.metadata?.iteration,
|
|
1345
|
-
at: input.createdAt,
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
return result;
|
|
1349
|
-
}
|
|
1350
|
-
enqueueHeadlessExecutionAttempt(input) {
|
|
1351
|
-
this.assertCanMutate();
|
|
1352
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1353
|
-
attemptId: input.attemptId,
|
|
1354
|
-
executionId: input.executionId,
|
|
1355
|
-
jobId: input.jobId,
|
|
1356
|
-
directiveHash: input.directiveHash,
|
|
1357
|
-
targetRef: input.targetRef,
|
|
1358
|
-
storyId: input.storyId,
|
|
1359
|
-
epicId: input.epicId,
|
|
1360
|
-
runtimeId: input.runtimeId,
|
|
1361
|
-
modelId: input.modelId,
|
|
1362
|
-
runtimeSource: input.runtimeSource,
|
|
1363
|
-
reasonCode: input.reasonCode,
|
|
1364
|
-
publicSummary: input.publicSummary,
|
|
1365
|
-
timestamp: input.enqueuedAt,
|
|
1366
|
-
});
|
|
1367
|
-
assertBoundedPublicText(input.notBeforeAt, 'headless_execution.notBeforeAt', 80);
|
|
1368
|
-
assertBoundedPublicText(input.operationId, 'headless_execution.operationId', 160);
|
|
1369
|
-
assertBoundedPublicText(input.idempotencyKey, 'headless_execution.idempotencyKey', 160);
|
|
1370
|
-
assertNonNegativeInteger(input.retryCount, 'headless_execution.retryCount');
|
|
1371
|
-
assertNonNegativeInteger(input.maxAttempts, 'headless_execution.maxAttempts');
|
|
1372
|
-
if (input.maxAttempts !== undefined && input.maxAttempts < 1) {
|
|
1373
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Continuity SQLite maxAttempts must be greater than zero.');
|
|
1374
|
-
}
|
|
1375
|
-
if (input.metadata)
|
|
1376
|
-
assertPublicSafeValue(input.metadata, 'headless_execution.metadata');
|
|
1377
|
-
const queuePolicy = normalizeContinuityQueuePolicy({
|
|
1378
|
-
maxItems: input.queueCap,
|
|
1379
|
-
maxAttempts: input.maxAttempts,
|
|
1380
|
-
});
|
|
1381
|
-
const queuePriority = normalizeContinuityQueuePriority(input.queuePriority);
|
|
1382
|
-
const queueScope = normalizeContinuityQueueScope(input.queueScope);
|
|
1383
|
-
const relativePaths = normalizePublicRelativePaths(input.relativePaths);
|
|
1384
|
-
const metadata = buildHeadlessExecutionPublicMetadata({
|
|
1385
|
-
attemptId: input.attemptId,
|
|
1386
|
-
executionId: input.executionId,
|
|
1387
|
-
jobId: input.jobId,
|
|
1388
|
-
directiveHash: input.directiveHash,
|
|
1389
|
-
runtimeId: input.runtimeId,
|
|
1390
|
-
modelId: input.modelId,
|
|
1391
|
-
runtimeSource: input.runtimeSource,
|
|
1392
|
-
status: input.status ?? 'queued',
|
|
1393
|
-
reasonCode: input.reasonCode,
|
|
1394
|
-
retryCount: input.retryCount ?? 0,
|
|
1395
|
-
attemptCount: 0,
|
|
1396
|
-
maxAttempts: queuePolicy.maxAttempts,
|
|
1397
|
-
queuePriority,
|
|
1398
|
-
queueScope,
|
|
1399
|
-
counts: input.counts,
|
|
1400
|
-
hashes: input.hashes,
|
|
1401
|
-
redactionSummary: input.redactionSummary,
|
|
1402
|
-
relativePaths,
|
|
1403
|
-
metadata: input.metadata,
|
|
1404
|
-
});
|
|
1405
|
-
return this.runInTransaction(() => {
|
|
1406
|
-
const configuredPolicyRow = this.db.prepare(`
|
|
48
|
+
`).run(e.eventId,e.jobId,e.operationId,e.idempotencyKey??null,e.sequence??null,e.type,e.summary,e.reasonCode??null,e.createdAt,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}recordProcessingOutputSummary(e){Y(e.executionId,"processing_output_summary.executionId",160),Y(e.jobId,"processing_output_summary.jobId",160),Y(e.publicSummary,"processing_output_summary.publicSummary",Be),Y(e.createdAt,"processing_output_summary.createdAt",80),b(e.eventId,"processing_output_summary.eventId"),b(e.idempotencyKey,"processing_output_summary.idempotencyKey"),b(e.goalId,"processing_output_summary.goalId"),b(e.loopId,"processing_output_summary.loopId"),b(e.reasonCode,"processing_output_summary.reasonCode"),b(e.source,"processing_output_summary.source");const a=this.appendEvent({eventId:it(e),jobId:e.jobId,operationId:e.executionId,idempotencyKey:e.idempotencyKey??e.executionId,type:O,summary:e.publicSummary,reasonCode:e.reasonCode,createdAt:e.createdAt,metadata:dt(e)});return a.inserted&&this.recordJobStageProgress({jobId:e.jobId,currentStage:e.metadata?.currentStage,iteration:e.metadata?.iteration,at:e.createdAt}),a}enqueueHeadlessExecutionAttempt(e){if(this.assertCanMutate(),L({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,directiveHash:e.directiveHash,targetRef:e.targetRef,storyId:e.storyId,epicId:e.epicId,runtimeId:e.runtimeId,modelId:e.modelId,runtimeSource:e.runtimeSource,reasonCode:e.reasonCode,publicSummary:e.publicSummary,timestamp:e.enqueuedAt}),c(e.notBeforeAt,"headless_execution.notBeforeAt",80),c(e.operationId,"headless_execution.operationId",160),c(e.idempotencyKey,"headless_execution.idempotencyKey",160),N(e.retryCount,"headless_execution.retryCount"),N(e.maxAttempts,"headless_execution.maxAttempts"),e.maxAttempts!==void 0&&e.maxAttempts<1)throw new i("sqlite-public-safety-rejected","Continuity SQLite maxAttempts must be greater than zero.");e.metadata&&g(e.metadata,"headless_execution.metadata");const a=B({maxItems:e.queueCap,maxAttempts:e.maxAttempts}),r=qe(e.queuePriority),s=Pe(e.queueScope),n=w(e.relativePaths),d=V({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,directiveHash:e.directiveHash,runtimeId:e.runtimeId,modelId:e.modelId,runtimeSource:e.runtimeSource,status:e.status??"queued",reasonCode:e.reasonCode,retryCount:e.retryCount??0,attemptCount:0,maxAttempts:a.maxAttempts,queuePriority:r,queueScope:s,counts:e.counts,hashes:e.hashes,redactionSummary:e.redactionSummary,relativePaths:n,metadata:e.metadata});return this.runInTransaction(()=>{const o=this.db.prepare(`
|
|
1407
49
|
SELECT value_json
|
|
1408
50
|
FROM runner_config
|
|
1409
51
|
WHERE key = 'headless_queue_policy'
|
|
1410
52
|
LIMIT 1
|
|
1411
|
-
`).get();
|
|
1412
|
-
let effectiveQueueCap = queuePolicy.maxItems;
|
|
1413
|
-
if (input.queueCap === undefined && isRecord(configuredPolicyRow) && typeof configuredPolicyRow.value_json === 'string') {
|
|
1414
|
-
try {
|
|
1415
|
-
const configuredPolicy = JSON.parse(configuredPolicyRow.value_json);
|
|
1416
|
-
if (isRecord(configuredPolicy) && typeof configuredPolicy.maxItems === 'number') {
|
|
1417
|
-
effectiveQueueCap = normalizeContinuityQueuePolicy({ maxItems: configuredPolicy.maxItems }).maxItems;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
catch {
|
|
1421
|
-
// runner_config has a json_valid constraint; retain the bounded default defensively.
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
this.db.prepare(`
|
|
53
|
+
`).get();let l=a.maxItems;if(e.queueCap===void 0&&E(o)&&typeof o.value_json=="string")try{const u=JSON.parse(o.value_json);E(u)&&typeof u.maxItems=="number"&&(l=B({maxItems:u.maxItems}).maxItems)}catch{}this.db.prepare(`
|
|
1425
54
|
INSERT INTO runner_config (key, value_json, updated_at)
|
|
1426
55
|
VALUES ('headless_queue_policy', ?, ?)
|
|
1427
56
|
ON CONFLICT(key) DO UPDATE SET
|
|
1428
57
|
value_json = excluded.value_json,
|
|
1429
58
|
updated_at = excluded.updated_at
|
|
1430
|
-
`).run(JSON.stringify({
|
|
1431
|
-
const replay = this.db.prepare(`
|
|
59
|
+
`).run(JSON.stringify({maxItems:l}),e.enqueuedAt);const I=this.db.prepare(`
|
|
1432
60
|
SELECT attempt_id
|
|
1433
61
|
FROM headless_execution_attempts
|
|
1434
62
|
WHERE attempt_id = ?
|
|
1435
63
|
OR (? IS NOT NULL AND operation_id = ?)
|
|
1436
64
|
OR (? IS NOT NULL AND idempotency_key = ?)
|
|
1437
65
|
LIMIT 1
|
|
1438
|
-
`).get(
|
|
1439
|
-
const depthRow = this.db.prepare(`
|
|
66
|
+
`).get(e.attemptId,e.operationId??null,e.operationId??null,e.idempotencyKey??null,e.idempotencyKey??null),h=this.db.prepare(`
|
|
1440
67
|
SELECT COUNT(*) AS count
|
|
1441
68
|
FROM headless_execution_attempts
|
|
1442
69
|
WHERE status IN ('queued', 'backoff', 'running')
|
|
1443
70
|
AND dead_lettered_at IS NULL
|
|
1444
|
-
`).get();
|
|
1445
|
-
const queueDepth = isRecord(depthRow) && typeof depthRow.count === 'number' ? depthRow.count : 0;
|
|
1446
|
-
if (replay) {
|
|
1447
|
-
return {
|
|
1448
|
-
inserted: false,
|
|
1449
|
-
replay: true,
|
|
1450
|
-
changes: 0,
|
|
1451
|
-
accepted: true,
|
|
1452
|
-
backpressure: queueDepth >= effectiveQueueCap,
|
|
1453
|
-
queueDepth,
|
|
1454
|
-
queueCap: effectiveQueueCap,
|
|
1455
|
-
publicSafe: true,
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
if (queueDepth >= effectiveQueueCap) {
|
|
1459
|
-
return {
|
|
1460
|
-
inserted: false,
|
|
1461
|
-
replay: false,
|
|
1462
|
-
changes: 0,
|
|
1463
|
-
accepted: false,
|
|
1464
|
-
backpressure: true,
|
|
1465
|
-
queueDepth,
|
|
1466
|
-
queueCap: effectiveQueueCap,
|
|
1467
|
-
reasonCode: CONTINUITY_QUEUE_REASON_CODES.CAP_REACHED,
|
|
1468
|
-
publicSafe: true,
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
const result = this.db.prepare(`
|
|
71
|
+
`).get(),f=E(h)&&typeof h.count=="number"?h.count:0;if(I)return{inserted:!1,replay:!0,changes:0,accepted:!0,backpressure:f>=l,queueDepth:f,queueCap:l,publicSafe:!0};if(f>=l)return{inserted:!1,replay:!1,changes:0,accepted:!1,backpressure:!0,queueDepth:f,queueCap:l,reasonCode:k.CAP_REACHED,publicSafe:!0};const _=this.db.prepare(`
|
|
1472
72
|
INSERT INTO headless_execution_attempts (
|
|
1473
73
|
attempt_id, execution_id, job_id, directive_hash, directive_kind,
|
|
1474
74
|
command_class, target_kind, target_ref, story_id, epic_id, runtime_id,
|
|
@@ -1478,23 +78,7 @@ export class ContinuitySqliteStore {
|
|
|
1478
78
|
relative_paths_json, metadata_json, queue_priority, queue_scope,
|
|
1479
79
|
attempt_count, next_retry_at
|
|
1480
80
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)
|
|
1481
|
-
`).run(
|
|
1482
|
-
return {
|
|
1483
|
-
inserted: result.changes > 0,
|
|
1484
|
-
replay: false,
|
|
1485
|
-
changes: result.changes,
|
|
1486
|
-
accepted: result.changes > 0,
|
|
1487
|
-
backpressure: queueDepth + result.changes >= effectiveQueueCap,
|
|
1488
|
-
queueDepth: queueDepth + result.changes,
|
|
1489
|
-
queueCap: effectiveQueueCap,
|
|
1490
|
-
publicSafe: true,
|
|
1491
|
-
};
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
listDueHeadlessExecutionAttempts(options) {
|
|
1495
|
-
const limit = Math.max(1, Math.min(50, Math.floor(options.limit)));
|
|
1496
|
-
assertBoundedPublicText(options.nowIso, 'headless_execution.nowIso', 80, true);
|
|
1497
|
-
return this.db.prepare(`
|
|
81
|
+
`).run(e.attemptId,e.executionId,e.jobId,e.directiveHash,e.directiveKind,e.commandClass,e.targetKind,e.targetRef,e.storyId??null,e.epicId??null,e.runtimeId??null,e.modelId??null,e.runtimeSource??null,e.status??"queued",e.reasonCode??null,e.publicSummary??null,e.enqueuedAt,e.status==="backoff"?e.notBeforeAt??e.enqueuedAt:null,e.retryCount??0,a.maxAttempts,e.operationId??null,e.idempotencyKey??null,T(e.counts,"headless_execution.counts"),T(e.hashes,"headless_execution.hashes"),T(e.redactionSummary,"headless_execution.redactionSummary"),T(n,"headless_execution.relativePaths"),S(d),r,s,e.notBeforeAt??e.enqueuedAt);return{inserted:_.changes>0,replay:!1,changes:_.changes,accepted:_.changes>0,backpressure:f+_.changes>=l,queueDepth:f+_.changes,queueCap:l,publicSafe:!0}})}listDueHeadlessExecutionAttempts(e){const a=Math.max(1,Math.min(50,Math.floor(e.limit)));return c(e.nowIso,"headless_execution.nowIso",80,!0),this.db.prepare(`
|
|
1498
82
|
SELECT attempt_id, execution_id, job_id, directive_hash, directive_kind,
|
|
1499
83
|
command_class, target_kind, target_ref, story_id, epic_id,
|
|
1500
84
|
runtime_id, model_id, runtime_source, status, reason_code,
|
|
@@ -1513,36 +97,13 @@ export class ContinuitySqliteStore {
|
|
|
1513
97
|
ORDER BY queue_priority DESC, COALESCE(next_retry_at, not_before_at, enqueued_at) ASC,
|
|
1514
98
|
enqueued_at ASC, attempt_id ASC
|
|
1515
99
|
LIMIT ?
|
|
1516
|
-
`).all(
|
|
1517
|
-
}
|
|
1518
|
-
hasCompletedHeadlessExecutionDirectiveHash(directiveHash, excludeAttemptId) {
|
|
1519
|
-
assertBoundedPublicText(directiveHash, 'headless_execution.directiveHash', 160, true);
|
|
1520
|
-
assertBoundedPublicText(excludeAttemptId, 'headless_execution.attemptId', 160);
|
|
1521
|
-
const excludePredicate = excludeAttemptId ? ' AND attempt_id != ?' : '';
|
|
1522
|
-
const params = excludeAttemptId ? [directiveHash, excludeAttemptId] : [directiveHash];
|
|
1523
|
-
const row = this.db.prepare(`
|
|
100
|
+
`).all(e.nowIso,e.nowIso,a)}hasCompletedHeadlessExecutionDirectiveHash(e,a){c(e,"headless_execution.directiveHash",160,!0),c(a,"headless_execution.attemptId",160);const r=a?" AND attempt_id != ?":"",s=a?[e,a]:[e];return this.db.prepare(`
|
|
1524
101
|
SELECT 1 AS found
|
|
1525
102
|
FROM headless_execution_attempts
|
|
1526
103
|
WHERE directive_hash = ?
|
|
1527
|
-
AND status = 'completed'${
|
|
104
|
+
AND status = 'completed'${r}
|
|
1528
105
|
LIMIT 1
|
|
1529
|
-
`).get(...
|
|
1530
|
-
return row?.found === 1;
|
|
1531
|
-
}
|
|
1532
|
-
claimHeadlessExecutionAttempt(input) {
|
|
1533
|
-
this.assertCanMutate();
|
|
1534
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1535
|
-
attemptId: input.attemptId,
|
|
1536
|
-
executionId: input.attemptId,
|
|
1537
|
-
jobId: input.attemptId,
|
|
1538
|
-
leaseId: input.leaseId,
|
|
1539
|
-
runnerId: input.runnerId,
|
|
1540
|
-
reasonCode: input.reasonCode,
|
|
1541
|
-
timestamp: input.claimedAt,
|
|
1542
|
-
});
|
|
1543
|
-
assertBoundedPublicText(input.leaseExpiresAt, 'headless_execution.leaseExpiresAt', 80, true);
|
|
1544
|
-
try {
|
|
1545
|
-
const result = this.db.prepare(`
|
|
106
|
+
`).get(...s)?.found===1}claimHeadlessExecutionAttempt(e){this.assertCanMutate(),L({attemptId:e.attemptId,executionId:e.attemptId,jobId:e.attemptId,leaseId:e.leaseId,runnerId:e.runnerId,reasonCode:e.reasonCode,timestamp:e.claimedAt}),c(e.leaseExpiresAt,"headless_execution.leaseExpiresAt",80,!0);try{const a=this.db.prepare(`
|
|
1546
107
|
UPDATE headless_execution_attempts
|
|
1547
108
|
SET status = 'running',
|
|
1548
109
|
lease_id = ?,
|
|
@@ -1558,27 +119,7 @@ export class ContinuitySqliteStore {
|
|
|
1558
119
|
AND dead_lettered_at IS NULL
|
|
1559
120
|
AND (COALESCE(next_retry_at, not_before_at) IS NULL OR COALESCE(next_retry_at, not_before_at) <= ?)
|
|
1560
121
|
AND (lease_id IS NULL OR lease_released_at IS NOT NULL OR lease_expires_at <= ?)
|
|
1561
|
-
`).run(
|
|
1562
|
-
return this.toWriteResult(result);
|
|
1563
|
-
}
|
|
1564
|
-
catch (error) {
|
|
1565
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
acquireNextHeadlessExecutionAttempt(input) {
|
|
1569
|
-
this.assertCanMutate();
|
|
1570
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1571
|
-
attemptId: input.leaseId,
|
|
1572
|
-
executionId: input.leaseId,
|
|
1573
|
-
jobId: input.leaseId,
|
|
1574
|
-
leaseId: input.leaseId,
|
|
1575
|
-
runnerId: input.runnerId,
|
|
1576
|
-
reasonCode: input.reasonCode,
|
|
1577
|
-
timestamp: input.claimedAt,
|
|
1578
|
-
});
|
|
1579
|
-
assertBoundedPublicText(input.leaseExpiresAt, 'headless_execution.leaseExpiresAt', 80, true);
|
|
1580
|
-
return this.runInTransaction(() => {
|
|
1581
|
-
const row = this.db.prepare(`
|
|
122
|
+
`).run(e.leaseId,e.runnerId,e.claimedAt,e.claimedAt,e.leaseExpiresAt,e.reasonCode??null,e.attemptId,e.claimedAt,e.claimedAt);return this.toWriteResult(a)}catch(a){throw m(a,"sqlite-mutation-failed")}}acquireNextHeadlessExecutionAttempt(e){return this.assertCanMutate(),L({attemptId:e.leaseId,executionId:e.leaseId,jobId:e.leaseId,leaseId:e.leaseId,runnerId:e.runnerId,reasonCode:e.reasonCode,timestamp:e.claimedAt}),c(e.leaseExpiresAt,"headless_execution.leaseExpiresAt",80,!0),this.runInTransaction(()=>{const a=this.db.prepare(`
|
|
1582
123
|
SELECT attempt_id
|
|
1583
124
|
FROM headless_execution_attempts
|
|
1584
125
|
WHERE status IN ('queued', 'backoff')
|
|
@@ -1591,11 +132,7 @@ export class ContinuitySqliteStore {
|
|
|
1591
132
|
enqueued_at ASC,
|
|
1592
133
|
attempt_id ASC
|
|
1593
134
|
LIMIT 1
|
|
1594
|
-
`).get(
|
|
1595
|
-
if (typeof row?.attempt_id !== 'string') {
|
|
1596
|
-
return { acquired: false, reasonCode: 'continuity-ok', publicSafe: true };
|
|
1597
|
-
}
|
|
1598
|
-
const result = this.db.prepare(`
|
|
135
|
+
`).get(e.directiveKind??null,e.directiveKind??null,e.claimedAt,e.claimedAt);if(typeof a?.attempt_id!="string")return{acquired:!1,reasonCode:"continuity-ok",publicSafe:!0};if(this.db.prepare(`
|
|
1599
136
|
UPDATE headless_execution_attempts
|
|
1600
137
|
SET status = 'running',
|
|
1601
138
|
lease_id = ?,
|
|
@@ -1611,29 +148,7 @@ export class ContinuitySqliteStore {
|
|
|
1611
148
|
AND dead_lettered_at IS NULL
|
|
1612
149
|
AND (COALESCE(next_retry_at, not_before_at) IS NULL OR COALESCE(next_retry_at, not_before_at) <= ?)
|
|
1613
150
|
AND (lease_id IS NULL OR lease_released_at IS NOT NULL OR lease_expires_at <= ?)
|
|
1614
|
-
`).run(
|
|
1615
|
-
if (result.changes === 0) {
|
|
1616
|
-
return { acquired: false, reasonCode: 'lease-conflict', publicSafe: true };
|
|
1617
|
-
}
|
|
1618
|
-
const attempt = this.getHeadlessExecutionAttempt(row.attempt_id);
|
|
1619
|
-
return attempt
|
|
1620
|
-
? { acquired: true, attempt, reasonCode: input.reasonCode ?? 'continuity-ok', publicSafe: true }
|
|
1621
|
-
: { acquired: false, reasonCode: 'lease-conflict', publicSafe: true };
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
heartbeatHeadlessExecutionAttempt(input) {
|
|
1625
|
-
this.assertCanMutate();
|
|
1626
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1627
|
-
attemptId: input.attemptId,
|
|
1628
|
-
executionId: input.attemptId,
|
|
1629
|
-
jobId: input.attemptId,
|
|
1630
|
-
leaseId: input.leaseId,
|
|
1631
|
-
runnerId: input.runnerId,
|
|
1632
|
-
timestamp: input.heartbeatAt,
|
|
1633
|
-
});
|
|
1634
|
-
assertBoundedPublicText(input.leaseExpiresAt, 'headless_execution.leaseExpiresAt', 80);
|
|
1635
|
-
try {
|
|
1636
|
-
const result = this.db.prepare(`
|
|
151
|
+
`).run(e.leaseId,e.runnerId,e.claimedAt,e.claimedAt,e.leaseExpiresAt,e.reasonCode??null,a.attempt_id,e.claimedAt,e.claimedAt).changes===0)return{acquired:!1,reasonCode:"lease-conflict",publicSafe:!0};const s=this.getHeadlessExecutionAttempt(a.attempt_id);return s?{acquired:!0,attempt:s,reasonCode:e.reasonCode??"continuity-ok",publicSafe:!0}:{acquired:!1,reasonCode:"lease-conflict",publicSafe:!0}})}heartbeatHeadlessExecutionAttempt(e){this.assertCanMutate(),L({attemptId:e.attemptId,executionId:e.attemptId,jobId:e.attemptId,leaseId:e.leaseId,runnerId:e.runnerId,timestamp:e.heartbeatAt}),c(e.leaseExpiresAt,"headless_execution.leaseExpiresAt",80);try{const a=this.db.prepare(`
|
|
1637
152
|
UPDATE headless_execution_attempts
|
|
1638
153
|
SET heartbeat_at = ?,
|
|
1639
154
|
lease_expires_at = COALESCE(?, lease_expires_at)
|
|
@@ -1643,27 +158,7 @@ export class ContinuitySqliteStore {
|
|
|
1643
158
|
AND status = 'running'
|
|
1644
159
|
AND lease_released_at IS NULL
|
|
1645
160
|
AND lease_expires_at > ?
|
|
1646
|
-
`).run(
|
|
1647
|
-
return this.toWriteResult(result);
|
|
1648
|
-
}
|
|
1649
|
-
catch (error) {
|
|
1650
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
releaseHeadlessExecutionAttempt(input) {
|
|
1654
|
-
this.assertCanMutate();
|
|
1655
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1656
|
-
attemptId: input.attemptId,
|
|
1657
|
-
executionId: input.attemptId,
|
|
1658
|
-
jobId: input.attemptId,
|
|
1659
|
-
leaseId: input.leaseId,
|
|
1660
|
-
reasonCode: input.reasonCode,
|
|
1661
|
-
timestamp: input.releasedAt,
|
|
1662
|
-
});
|
|
1663
|
-
assertBoundedPublicText(input.notBeforeAt, 'headless_execution.notBeforeAt', 80);
|
|
1664
|
-
assertNonNegativeInteger(input.retryCount, 'headless_execution.retryCount');
|
|
1665
|
-
try {
|
|
1666
|
-
const result = this.db.prepare(`
|
|
161
|
+
`).run(e.heartbeatAt,e.leaseExpiresAt??null,e.attemptId,e.leaseId,e.runnerId,e.heartbeatAt);return this.toWriteResult(a)}catch(a){throw m(a,"sqlite-mutation-failed")}}releaseHeadlessExecutionAttempt(e){this.assertCanMutate(),L({attemptId:e.attemptId,executionId:e.attemptId,jobId:e.attemptId,leaseId:e.leaseId,reasonCode:e.reasonCode,timestamp:e.releasedAt}),c(e.notBeforeAt,"headless_execution.notBeforeAt",80),N(e.retryCount,"headless_execution.retryCount");try{const a=this.db.prepare(`
|
|
1667
162
|
UPDATE headless_execution_attempts
|
|
1668
163
|
SET status = COALESCE(?, status),
|
|
1669
164
|
reason_code = COALESCE(?, reason_code),
|
|
@@ -1679,76 +174,8 @@ export class ContinuitySqliteStore {
|
|
|
1679
174
|
AND lease_id = ?
|
|
1680
175
|
AND status = 'running'
|
|
1681
176
|
AND lease_released_at IS NULL
|
|
1682
|
-
`).run(
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1685
|
-
catch (error) {
|
|
1686
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
completeHeadlessExecutionAttempt(input) {
|
|
1690
|
-
this.assertCanMutate();
|
|
1691
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1692
|
-
attemptId: input.attemptId,
|
|
1693
|
-
executionId: input.attemptId,
|
|
1694
|
-
jobId: input.attemptId,
|
|
1695
|
-
leaseId: input.leaseId,
|
|
1696
|
-
runnerId: input.runnerId,
|
|
1697
|
-
reasonCode: input.reasonCode,
|
|
1698
|
-
publicSummary: input.publicSummary,
|
|
1699
|
-
timestamp: input.completedAt,
|
|
1700
|
-
});
|
|
1701
|
-
assertBoundedPublicText(input.notBeforeAt, 'headless_execution.notBeforeAt', 80);
|
|
1702
|
-
assertNonNegativeInteger(input.durationMs, 'headless_execution.durationMs');
|
|
1703
|
-
assertNonNegativeInteger(input.retryCount, 'headless_execution.retryCount');
|
|
1704
|
-
if (input.metadata)
|
|
1705
|
-
assertPublicSafeValue(input.metadata, 'headless_execution.metadata');
|
|
1706
|
-
const relativePaths = normalizePublicRelativePaths(input.relativePaths);
|
|
1707
|
-
const metadata = buildHeadlessExecutionPublicMetadata({
|
|
1708
|
-
attemptId: input.attemptId,
|
|
1709
|
-
executionId: input.executionId ?? input.attemptId,
|
|
1710
|
-
jobId: input.jobId ?? input.attemptId,
|
|
1711
|
-
status: input.status,
|
|
1712
|
-
reasonCode: input.reasonCode,
|
|
1713
|
-
durationMs: input.durationMs,
|
|
1714
|
-
retryCount: input.retryCount,
|
|
1715
|
-
counts: input.counts,
|
|
1716
|
-
hashes: input.hashes,
|
|
1717
|
-
redactionSummary: input.redactionSummary,
|
|
1718
|
-
relativePaths,
|
|
1719
|
-
metadata: input.metadata,
|
|
1720
|
-
});
|
|
1721
|
-
const leasePredicate = input.leaseId
|
|
1722
|
-
? ` AND lease_id = ?
|
|
1723
|
-
AND lease_released_at IS NULL${input.runnerId ? ' AND runner_id = ?' : ''}`
|
|
1724
|
-
: '';
|
|
1725
|
-
const statusPredicate = input.leaseId
|
|
1726
|
-
? "status = 'running'"
|
|
1727
|
-
: "status NOT IN ('completed', 'failed', 'timed_out', 'skipped')";
|
|
1728
|
-
const params = [
|
|
1729
|
-
input.status,
|
|
1730
|
-
input.completedAt,
|
|
1731
|
-
input.durationMs ?? null,
|
|
1732
|
-
input.reasonCode ?? null,
|
|
1733
|
-
input.publicSummary ?? null,
|
|
1734
|
-
input.notBeforeAt ?? null,
|
|
1735
|
-
input.retryCount ?? null,
|
|
1736
|
-
input.status,
|
|
1737
|
-
input.notBeforeAt ?? null,
|
|
1738
|
-
input.counts ? stringifyPublicJsonValue(input.counts, 'headless_execution.counts') : null,
|
|
1739
|
-
input.hashes ? stringifyPublicJsonValue(input.hashes, 'headless_execution.hashes') : null,
|
|
1740
|
-
input.redactionSummary ? stringifyPublicJsonValue(input.redactionSummary, 'headless_execution.redactionSummary') : null,
|
|
1741
|
-
relativePaths ? stringifyPublicJsonValue(relativePaths, 'headless_execution.relativePaths') : null,
|
|
1742
|
-
stringifyPublicMetadata(metadata),
|
|
1743
|
-
input.completedAt,
|
|
1744
|
-
input.attemptId,
|
|
1745
|
-
];
|
|
1746
|
-
if (input.leaseId)
|
|
1747
|
-
params.push(input.leaseId);
|
|
1748
|
-
if (input.leaseId && input.runnerId)
|
|
1749
|
-
params.push(input.runnerId);
|
|
1750
|
-
try {
|
|
1751
|
-
const result = this.db.prepare(`
|
|
177
|
+
`).run(e.status??null,e.reasonCode??null,e.notBeforeAt??null,e.retryCount??null,e.status??null,e.notBeforeAt??null,e.status??null,e.releasedAt,e.attemptId,e.leaseId);return this.toWriteResult(a)}catch(a){throw m(a,"sqlite-mutation-failed")}}completeHeadlessExecutionAttempt(e){this.assertCanMutate(),L({attemptId:e.attemptId,executionId:e.attemptId,jobId:e.attemptId,leaseId:e.leaseId,runnerId:e.runnerId,reasonCode:e.reasonCode,publicSummary:e.publicSummary,timestamp:e.completedAt}),c(e.notBeforeAt,"headless_execution.notBeforeAt",80),N(e.durationMs,"headless_execution.durationMs"),N(e.retryCount,"headless_execution.retryCount"),e.metadata&&g(e.metadata,"headless_execution.metadata");const a=w(e.relativePaths),r=V({attemptId:e.attemptId,executionId:e.executionId??e.attemptId,jobId:e.jobId??e.attemptId,status:e.status,reasonCode:e.reasonCode,durationMs:e.durationMs,retryCount:e.retryCount,counts:e.counts,hashes:e.hashes,redactionSummary:e.redactionSummary,relativePaths:a,metadata:e.metadata}),s=e.leaseId?` AND lease_id = ?
|
|
178
|
+
AND lease_released_at IS NULL${e.runnerId?" AND runner_id = ?":""}`:"",n=e.leaseId?"status = 'running'":"status NOT IN ('completed', 'failed', 'timed_out', 'skipped')",d=[e.status,e.completedAt,e.durationMs??null,e.reasonCode??null,e.publicSummary??null,e.notBeforeAt??null,e.retryCount??null,e.status,e.notBeforeAt??null,e.counts?T(e.counts,"headless_execution.counts"):null,e.hashes?T(e.hashes,"headless_execution.hashes"):null,e.redactionSummary?T(e.redactionSummary,"headless_execution.redactionSummary"):null,a?T(a,"headless_execution.relativePaths"):null,S(r),e.completedAt,e.attemptId];e.leaseId&&d.push(e.leaseId),e.leaseId&&e.runnerId&&d.push(e.runnerId);try{const o=this.db.prepare(`
|
|
1752
179
|
UPDATE headless_execution_attempts
|
|
1753
180
|
SET status = ?,
|
|
1754
181
|
completed_at = ?,
|
|
@@ -1767,426 +194,53 @@ export class ContinuitySqliteStore {
|
|
|
1767
194
|
relative_paths_json = COALESCE(?, relative_paths_json),
|
|
1768
195
|
metadata_json = COALESCE(?, metadata_json),
|
|
1769
196
|
lease_released_at = COALESCE(lease_released_at, ?)
|
|
1770
|
-
WHERE attempt_id = ?${
|
|
1771
|
-
AND ${
|
|
1772
|
-
`).run(...
|
|
1773
|
-
return this.toWriteResult(result);
|
|
1774
|
-
}
|
|
1775
|
-
catch (error) {
|
|
1776
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
recordHeadlessExecutionReceipt(input) {
|
|
1780
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1781
|
-
attemptId: input.attemptId,
|
|
1782
|
-
executionId: input.executionId,
|
|
1783
|
-
jobId: input.jobId,
|
|
1784
|
-
reasonCode: input.reasonCode,
|
|
1785
|
-
timestamp: input.createdAt,
|
|
1786
|
-
});
|
|
1787
|
-
if (input.metadata)
|
|
1788
|
-
assertPublicSafeValue(input.metadata, 'headless_execution.metadata');
|
|
1789
|
-
const relativePaths = normalizePublicRelativePaths(input.relativePaths);
|
|
1790
|
-
const metadata = buildHeadlessExecutionPublicMetadata({
|
|
1791
|
-
attemptId: input.attemptId,
|
|
1792
|
-
executionId: input.executionId,
|
|
1793
|
-
jobId: input.jobId,
|
|
1794
|
-
status: input.status,
|
|
1795
|
-
reasonCode: input.reasonCode,
|
|
1796
|
-
counts: input.counts,
|
|
1797
|
-
hashes: input.hashes,
|
|
1798
|
-
redactionSummary: input.redactionSummary,
|
|
1799
|
-
relativePaths,
|
|
1800
|
-
metadata: input.metadata,
|
|
1801
|
-
});
|
|
1802
|
-
return this.appendReceipt({
|
|
1803
|
-
receiptId: input.receiptId,
|
|
1804
|
-
jobId: input.jobId,
|
|
1805
|
-
operationId: input.operationId ?? input.executionId,
|
|
1806
|
-
idempotencyKey: input.idempotencyKey ?? input.executionId,
|
|
1807
|
-
status: input.status,
|
|
1808
|
-
reasonCode: input.reasonCode,
|
|
1809
|
-
appliedOperationIds: input.status === 'applied' ? [input.executionId] : undefined,
|
|
1810
|
-
ignoredOperationIds: input.status === 'ignored' ? [input.executionId] : undefined,
|
|
1811
|
-
createdAt: input.createdAt,
|
|
1812
|
-
metadata,
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1815
|
-
recordHeadlessExecutionSummary(input) {
|
|
1816
|
-
validateHeadlessExecutionAttemptIdentity({
|
|
1817
|
-
attemptId: input.attemptId,
|
|
1818
|
-
executionId: input.executionId,
|
|
1819
|
-
jobId: input.jobId,
|
|
1820
|
-
runtimeId: input.runtimeId,
|
|
1821
|
-
modelId: input.modelId,
|
|
1822
|
-
reasonCode: input.reasonCode,
|
|
1823
|
-
publicSummary: input.publicSummary,
|
|
1824
|
-
timestamp: input.createdAt,
|
|
1825
|
-
});
|
|
1826
|
-
assertNonNegativeInteger(input.durationMs, 'headless_execution.durationMs');
|
|
1827
|
-
if (input.metadata)
|
|
1828
|
-
assertPublicSafeValue(input.metadata, 'headless_execution.metadata');
|
|
1829
|
-
const relativePaths = normalizePublicRelativePaths(input.relativePaths);
|
|
1830
|
-
const metadata = buildHeadlessExecutionPublicMetadata({
|
|
1831
|
-
attemptId: input.attemptId,
|
|
1832
|
-
executionId: input.executionId,
|
|
1833
|
-
jobId: input.jobId,
|
|
1834
|
-
runtimeId: input.runtimeId,
|
|
1835
|
-
modelId: input.modelId,
|
|
1836
|
-
status: input.status ?? 'completed',
|
|
1837
|
-
reasonCode: input.reasonCode,
|
|
1838
|
-
durationMs: input.durationMs,
|
|
1839
|
-
counts: input.counts,
|
|
1840
|
-
hashes: input.hashes,
|
|
1841
|
-
redactionSummary: input.redactionSummary,
|
|
1842
|
-
relativePaths,
|
|
1843
|
-
metadata: input.metadata,
|
|
1844
|
-
});
|
|
1845
|
-
return this.recordProcessingOutputSummary({
|
|
1846
|
-
eventId: input.eventId,
|
|
1847
|
-
executionId: input.executionId,
|
|
1848
|
-
jobId: input.jobId,
|
|
1849
|
-
idempotencyKey: input.executionId,
|
|
1850
|
-
publicSummary: input.publicSummary,
|
|
1851
|
-
reasonCode: input.reasonCode,
|
|
1852
|
-
createdAt: input.createdAt,
|
|
1853
|
-
source: 'headless_execution',
|
|
1854
|
-
metadata,
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
|
-
appendReceipt(input) {
|
|
1858
|
-
this.assertCanMutate();
|
|
1859
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
1860
|
-
try {
|
|
1861
|
-
const result = this.db.prepare(`
|
|
197
|
+
WHERE attempt_id = ?${s}
|
|
198
|
+
AND ${n}
|
|
199
|
+
`).run(...d);return this.toWriteResult(o)}catch(o){throw m(o,"sqlite-mutation-failed")}}recordHeadlessExecutionReceipt(e){L({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,reasonCode:e.reasonCode,timestamp:e.createdAt}),e.metadata&&g(e.metadata,"headless_execution.metadata");const a=w(e.relativePaths),r=V({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,status:e.status,reasonCode:e.reasonCode,counts:e.counts,hashes:e.hashes,redactionSummary:e.redactionSummary,relativePaths:a,metadata:e.metadata});return this.appendReceipt({receiptId:e.receiptId,jobId:e.jobId,operationId:e.operationId??e.executionId,idempotencyKey:e.idempotencyKey??e.executionId,status:e.status,reasonCode:e.reasonCode,appliedOperationIds:e.status==="applied"?[e.executionId]:void 0,ignoredOperationIds:e.status==="ignored"?[e.executionId]:void 0,createdAt:e.createdAt,metadata:r})}recordHeadlessExecutionSummary(e){L({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,runtimeId:e.runtimeId,modelId:e.modelId,reasonCode:e.reasonCode,publicSummary:e.publicSummary,timestamp:e.createdAt}),N(e.durationMs,"headless_execution.durationMs"),e.metadata&&g(e.metadata,"headless_execution.metadata");const a=w(e.relativePaths),r=V({attemptId:e.attemptId,executionId:e.executionId,jobId:e.jobId,runtimeId:e.runtimeId,modelId:e.modelId,status:e.status??"completed",reasonCode:e.reasonCode,durationMs:e.durationMs,counts:e.counts,hashes:e.hashes,redactionSummary:e.redactionSummary,relativePaths:a,metadata:e.metadata});return this.recordProcessingOutputSummary({eventId:e.eventId,executionId:e.executionId,jobId:e.jobId,idempotencyKey:e.executionId,publicSummary:e.publicSummary,reasonCode:e.reasonCode,createdAt:e.createdAt,source:"headless_execution",metadata:r})}appendReceipt(e){this.assertCanMutate();const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
1862
200
|
INSERT OR IGNORE INTO job_receipts (
|
|
1863
201
|
receipt_id, job_id, operation_id, idempotency_key, status, reason_code,
|
|
1864
202
|
applied_operation_ids_json, ignored_operation_ids_json, created_at, metadata_json
|
|
1865
203
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1866
|
-
`).run(
|
|
1867
|
-
return this.toWriteResult(result);
|
|
1868
|
-
}
|
|
1869
|
-
catch (error) {
|
|
1870
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
/**
|
|
1874
|
-
* P199.06: Apply TTL/count/byte retention to public receipts and persisted
|
|
1875
|
-
* processing summaries. Deleted content is replaced with bounded audit evidence
|
|
1876
|
-
* that hashes only public identifiers/status/reason codes, never summaries.
|
|
1877
|
-
*/
|
|
1878
|
-
compactPublicReceiptsAndOutputs(input) {
|
|
1879
|
-
this.assertCanMutate();
|
|
1880
|
-
const nowMs = Date.parse(input.nowIso);
|
|
1881
|
-
if (!Number.isFinite(nowMs)) {
|
|
1882
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Continuity receipt compaction requires a valid public timestamp.');
|
|
1883
|
-
}
|
|
1884
|
-
const policy = normalizeContinuityRetentionPolicy(input.policy);
|
|
1885
|
-
const receiptCutoff = new Date(nowMs - policy.receipts.maxAgeMs).toISOString();
|
|
1886
|
-
const outputCutoff = new Date(nowMs - policy.processingOutputs.maxAgeMs).toISOString();
|
|
1887
|
-
try {
|
|
1888
|
-
return this.runInTransaction(() => {
|
|
1889
|
-
const receiptCandidates = this.selectReceiptRetentionCandidates({
|
|
1890
|
-
cutoff: receiptCutoff,
|
|
1891
|
-
maxItems: policy.receipts.maxItems,
|
|
1892
|
-
maxBytes: policy.receipts.maxBytes,
|
|
1893
|
-
limit: policy.maxDeleteRowsPerRun,
|
|
1894
|
-
});
|
|
1895
|
-
const outputCandidates = this.selectOutputRetentionCandidates({
|
|
1896
|
-
cutoff: outputCutoff,
|
|
1897
|
-
maxItems: policy.processingOutputs.maxItems,
|
|
1898
|
-
maxBytes: policy.processingOutputs.maxBytes,
|
|
1899
|
-
limit: policy.maxDeleteRowsPerRun,
|
|
1900
|
-
});
|
|
1901
|
-
const candidates = [...receiptCandidates, ...outputCandidates]
|
|
1902
|
-
.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
|
|
1903
|
-
.slice(0, policy.maxDeleteRowsPerRun);
|
|
1904
|
-
if (candidates.length === 0) {
|
|
1905
|
-
return {
|
|
1906
|
-
status: 'noop',
|
|
1907
|
-
summaries: [],
|
|
1908
|
-
counts: { receiptsRemoved: 0, outputsRemoved: 0, bytesRemoved: 0, summariesCreated: 0 },
|
|
1909
|
-
reasonCode: CONTINUITY_RECEIPT_RETENTION_REASON_CODES.NOOP,
|
|
1910
|
-
publicSafe: true,
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
const selectedReceipts = candidates.filter((candidate) => candidate.kind === 'receipt');
|
|
1914
|
-
const selectedOutputs = candidates.filter((candidate) => candidate.kind === 'output');
|
|
1915
|
-
for (const jobId of uniqueValues(selectedReceipts.map((candidate) => candidate.jobId))) {
|
|
1916
|
-
this.preserveReceiptDerivedIteration(jobId, input.nowIso);
|
|
1917
|
-
}
|
|
1918
|
-
this.deleteRetentionCandidates('job_receipts', 'receipt_id', selectedReceipts.map((candidate) => candidate.id));
|
|
1919
|
-
this.deleteRetentionCandidates('job_events', 'event_id', selectedOutputs.map((candidate) => candidate.id));
|
|
1920
|
-
const byJob = new Map();
|
|
1921
|
-
for (const candidate of candidates) {
|
|
1922
|
-
byJob.set(candidate.jobId, [...(byJob.get(candidate.jobId) ?? []), candidate]);
|
|
1923
|
-
}
|
|
1924
|
-
const summaries = [];
|
|
1925
|
-
for (const [jobId, removed] of [...byJob.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
1926
|
-
const removedReceipts = removed.filter((candidate) => candidate.kind === 'receipt');
|
|
1927
|
-
const removedOutputs = removed.filter((candidate) => candidate.kind === 'output');
|
|
1928
|
-
const retained = this.retainedReceiptOutputCounts(jobId);
|
|
1929
|
-
const hashes = retentionHashes(jobId, removedReceipts, removedOutputs);
|
|
1930
|
-
const counts = {
|
|
1931
|
-
receiptsRemoved: removedReceipts.length,
|
|
1932
|
-
receiptsRetained: retained.receipts,
|
|
1933
|
-
outputsRemoved: removedOutputs.length,
|
|
1934
|
-
outputsRetained: retained.outputs,
|
|
1935
|
-
bytesRemoved: removed.reduce((total, candidate) => total + candidate.approxBytes, 0),
|
|
1936
|
-
bytesRetained: retained.bytes,
|
|
1937
|
-
redactions: 0,
|
|
1938
|
-
};
|
|
1939
|
-
const publicSummary = `Compacted public receipt and processing evidence: receipts removed ${counts.receiptsRemoved}, retained ${counts.receiptsRetained}; summaries removed ${counts.outputsRemoved}, retained ${counts.outputsRetained}; bytes removed ${counts.bytesRemoved}, retained ${counts.bytesRetained}.`;
|
|
1940
|
-
assertBoundedPublicText(publicSummary, 'receipt_compaction.publicSummary', 500, true);
|
|
1941
|
-
const eventDigest = createHash('sha256')
|
|
1942
|
-
.update(`${jobId}:${input.nowIso}:${hashes.aggregate}`)
|
|
1943
|
-
.digest('hex');
|
|
1944
|
-
const summaryId = `receipt-compaction-${eventDigest.slice(0, 32)}`;
|
|
1945
|
-
const summary = {
|
|
1946
|
-
schemaVersion: 1,
|
|
1947
|
-
contractVersion: 'continuity-v1',
|
|
1948
|
-
policyVersion: 1,
|
|
1949
|
-
type: CONTINUITY_RECEIPT_COMPACTION_EVENT_TYPE,
|
|
1950
|
-
summaryId,
|
|
1951
|
-
jobId,
|
|
1952
|
-
scope: 'local_sqlite',
|
|
1953
|
-
status: 'compacted',
|
|
1954
|
-
createdAt: input.nowIso,
|
|
1955
|
-
reasonCodes: [CONTINUITY_RECEIPT_RETENTION_REASON_CODES.COMPACTED],
|
|
1956
|
-
counts,
|
|
1957
|
-
hashes,
|
|
1958
|
-
publicSummary,
|
|
1959
|
-
publicSafe: true,
|
|
1960
|
-
};
|
|
1961
|
-
this.appendEvent({
|
|
1962
|
-
eventId: summaryId,
|
|
1963
|
-
jobId,
|
|
1964
|
-
operationId: `op-${summaryId}`,
|
|
1965
|
-
idempotencyKey: `idem-${summaryId}`,
|
|
1966
|
-
type: CONTINUITY_RECEIPT_COMPACTION_EVENT_TYPE,
|
|
1967
|
-
summary: publicSummary,
|
|
1968
|
-
reasonCode: CONTINUITY_RECEIPT_RETENTION_REASON_CODES.COMPACTED,
|
|
1969
|
-
createdAt: input.nowIso,
|
|
1970
|
-
metadata: {
|
|
1971
|
-
schemaVersion: 1,
|
|
1972
|
-
contractVersion: 'continuity-v1',
|
|
1973
|
-
eventType: CONTINUITY_RECEIPT_COMPACTION_EVENT_TYPE,
|
|
1974
|
-
jobId,
|
|
1975
|
-
reasonCodes: summary.reasonCodes,
|
|
1976
|
-
counts,
|
|
1977
|
-
hashes,
|
|
1978
|
-
publicSummary,
|
|
1979
|
-
},
|
|
1980
|
-
});
|
|
1981
|
-
this.pruneReceiptCompactionSummaries(jobId, policy.maxCompactionSummariesPerJob);
|
|
1982
|
-
summaries.push(summary);
|
|
1983
|
-
}
|
|
1984
|
-
return {
|
|
1985
|
-
status: 'compacted',
|
|
1986
|
-
summaries,
|
|
1987
|
-
counts: {
|
|
1988
|
-
receiptsRemoved: selectedReceipts.length,
|
|
1989
|
-
outputsRemoved: selectedOutputs.length,
|
|
1990
|
-
bytesRemoved: candidates.reduce((total, candidate) => total + candidate.approxBytes, 0),
|
|
1991
|
-
summariesCreated: summaries.length,
|
|
1992
|
-
},
|
|
1993
|
-
reasonCode: CONTINUITY_RECEIPT_RETENTION_REASON_CODES.COMPACTED,
|
|
1994
|
-
publicSafe: true,
|
|
1995
|
-
};
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
catch (error) {
|
|
1999
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
updateJobStatus(input) {
|
|
2003
|
-
this.assertCanMutate();
|
|
2004
|
-
try {
|
|
2005
|
-
const result = this.db.prepare(`
|
|
204
|
+
`).run(e.receiptId,e.jobId,e.operationId??null,e.idempotencyKey??null,e.status,e.reasonCode??null,e.appliedOperationIds?JSON.stringify(e.appliedOperationIds):null,e.ignoredOperationIds?JSON.stringify(e.ignoredOperationIds):null,e.createdAt,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}compactPublicReceiptsAndOutputs(e){this.assertCanMutate();const a=Date.parse(e.nowIso);if(!Number.isFinite(a))throw new i("sqlite-public-safety-rejected","Continuity receipt compaction requires a valid public timestamp.");const r=He(e.policy),s=new Date(a-r.receipts.maxAgeMs).toISOString(),n=new Date(a-r.processingOutputs.maxAgeMs).toISOString();try{return this.runInTransaction(()=>{const d=this.selectReceiptRetentionCandidates({cutoff:s,maxItems:r.receipts.maxItems,maxBytes:r.receipts.maxBytes,limit:r.maxDeleteRowsPerRun}),o=this.selectOutputRetentionCandidates({cutoff:n,maxItems:r.processingOutputs.maxItems,maxBytes:r.processingOutputs.maxBytes,limit:r.maxDeleteRowsPerRun}),l=[...d,...o].sort((u,p)=>u.createdAt.localeCompare(p.createdAt)||u.id.localeCompare(p.id)).slice(0,r.maxDeleteRowsPerRun);if(l.length===0)return{status:"noop",summaries:[],counts:{receiptsRemoved:0,outputsRemoved:0,bytesRemoved:0,summariesCreated:0},reasonCode:F.NOOP,publicSafe:!0};const I=l.filter(u=>u.kind==="receipt"),h=l.filter(u=>u.kind==="output");for(const u of q(I.map(p=>p.jobId)))this.preserveReceiptDerivedIteration(u,e.nowIso);this.deleteRetentionCandidates("job_receipts","receipt_id",I.map(u=>u.id)),this.deleteRetentionCandidates("job_events","event_id",h.map(u=>u.id));const f=new Map;for(const u of l)f.set(u.jobId,[...f.get(u.jobId)??[],u]);const _=[];for(const[u,p]of[...f.entries()].sort(([x],[A])=>x.localeCompare(A))){const x=p.filter(j=>j.kind==="receipt"),A=p.filter(j=>j.kind==="output"),Q=this.retainedReceiptOutputCounts(u),G=ut(u,x,A),R={receiptsRemoved:x.length,receiptsRetained:Q.receipts,outputsRemoved:A.length,outputsRetained:Q.outputs,bytesRemoved:p.reduce((j,Se)=>j+Se.approxBytes,0),bytesRetained:Q.bytes,redactions:0},P=`Compacted public receipt and processing evidence: receipts removed ${R.receiptsRemoved}, retained ${R.receiptsRetained}; summaries removed ${R.outputsRemoved}, retained ${R.outputsRetained}; bytes removed ${R.bytesRemoved}, retained ${R.bytesRetained}.`;c(P,"receipt_compaction.publicSummary",500,!0);const H=`receipt-compaction-${M("sha256").update(`${u}:${e.nowIso}:${G.aggregate}`).digest("hex").slice(0,32)}`,ee={schemaVersion:1,contractVersion:"continuity-v1",policyVersion:1,type:W,summaryId:H,jobId:u,scope:"local_sqlite",status:"compacted",createdAt:e.nowIso,reasonCodes:[F.COMPACTED],counts:R,hashes:G,publicSummary:P,publicSafe:!0};this.appendEvent({eventId:H,jobId:u,operationId:`op-${H}`,idempotencyKey:`idem-${H}`,type:W,summary:P,reasonCode:F.COMPACTED,createdAt:e.nowIso,metadata:{schemaVersion:1,contractVersion:"continuity-v1",eventType:W,jobId:u,reasonCodes:ee.reasonCodes,counts:R,hashes:G,publicSummary:P}}),this.pruneReceiptCompactionSummaries(u,r.maxCompactionSummariesPerJob),_.push(ee)}return{status:"compacted",summaries:_,counts:{receiptsRemoved:I.length,outputsRemoved:h.length,bytesRemoved:l.reduce((u,p)=>u+p.approxBytes,0),summariesCreated:_.length},reasonCode:F.COMPACTED,publicSafe:!0}})}catch(d){throw m(d,"sqlite-mutation-failed")}}updateJobStatus(e){this.assertCanMutate();try{const a=this.db.prepare(`
|
|
2006
205
|
UPDATE jobs
|
|
2007
206
|
SET status = ?,
|
|
2008
207
|
reason_code = COALESCE(?, reason_code),
|
|
2009
208
|
updated_at = ?,
|
|
2010
209
|
next_run_at = COALESCE(?, next_run_at)
|
|
2011
210
|
WHERE job_id = ?
|
|
2012
|
-
`).run(
|
|
2013
|
-
if (result.changes === 0) {
|
|
2014
|
-
throw new ContinuitySqliteStoreError('sqlite-foreign-key-failed', `Continuity SQLite job ${input.jobId} was not found for status update.`);
|
|
2015
|
-
}
|
|
2016
|
-
return this.toWriteResult(result);
|
|
2017
|
-
}
|
|
2018
|
-
catch (error) {
|
|
2019
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
/**
|
|
2023
|
-
* P195.04: Additively persist producer-emitted process stage/iteration into the
|
|
2024
|
-
* existing `jobs.metadata_json` column and append a bounded `stageHistory`
|
|
2025
|
-
* (newest `CONTINUITY_STAGE_HISTORY_MAX` kept). No schema migration is required.
|
|
2026
|
-
* Unknown stages, unsafe timestamps and missing jobs are no-ops so older DBs and
|
|
2027
|
-
* non-tick summaries never throw.
|
|
2028
|
-
*/
|
|
2029
|
-
recordJobStageProgress(input) {
|
|
2030
|
-
this.assertCanMutate();
|
|
2031
|
-
assertBoundedPublicText(input.jobId, 'job_stage.jobId', 160, true);
|
|
2032
|
-
const stage = isContinuityPublicStage(input.currentStage) ? input.currentStage : undefined;
|
|
2033
|
-
const iteration = normalizeContinuityStageIteration(input.iteration);
|
|
2034
|
-
if (!stage && iteration === undefined) {
|
|
2035
|
-
return { inserted: false, replay: false, changes: 0 };
|
|
2036
|
-
}
|
|
2037
|
-
const at = sanitizeContinuityStageTimestamp(input.at);
|
|
2038
|
-
try {
|
|
2039
|
-
const existing = this.db
|
|
2040
|
-
.prepare('SELECT metadata_json FROM jobs WHERE job_id = ?')
|
|
2041
|
-
.get(input.jobId);
|
|
2042
|
-
if (!existing)
|
|
2043
|
-
return { inserted: false, replay: false, changes: 0 };
|
|
2044
|
-
const metadata = parseJobMetadataObject(typeof existing.metadata_json === 'string' ? existing.metadata_json : null);
|
|
2045
|
-
if (stage)
|
|
2046
|
-
metadata.currentStage = stage;
|
|
2047
|
-
if (iteration !== undefined)
|
|
2048
|
-
metadata.iteration = iteration;
|
|
2049
|
-
if (stage && at && input.appendHistory !== false) {
|
|
2050
|
-
const history = sanitizeContinuityStageHistory(metadata.stageHistory);
|
|
2051
|
-
history.push({ stage, at });
|
|
2052
|
-
metadata.stageHistory = history.slice(-CONTINUITY_STAGE_HISTORY_MAX);
|
|
2053
|
-
}
|
|
2054
|
-
const result = this.db
|
|
2055
|
-
.prepare('UPDATE jobs SET metadata_json = ? WHERE job_id = ?')
|
|
2056
|
-
.run(stringifyPublicMetadata(metadata), input.jobId);
|
|
2057
|
-
return this.toWriteResult(result);
|
|
2058
|
-
}
|
|
2059
|
-
catch (error) {
|
|
2060
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
updateJobAndExistingGoalCampaignStatus(input) {
|
|
2064
|
-
return this.runInTransaction(() => {
|
|
2065
|
-
const jobResult = this.db.prepare(`
|
|
211
|
+
`).run(e.status,e.reasonCode??null,e.updatedAt,e.nextRunAt??null,e.jobId);if(a.changes===0)throw new i("sqlite-foreign-key-failed",`Continuity SQLite job ${e.jobId} was not found for status update.`);return this.toWriteResult(a)}catch(a){throw m(a,"sqlite-mutation-failed")}}recordJobStageProgress(e){this.assertCanMutate(),c(e.jobId,"job_stage.jobId",160,!0);const a=le(e.currentStage)?e.currentStage:void 0,r=Ge(e.iteration);if(!a&&r===void 0)return{inserted:!1,replay:!1,changes:0};const s=ue(e.at);try{const n=this.db.prepare("SELECT metadata_json FROM jobs WHERE job_id = ?").get(e.jobId);if(!n)return{inserted:!1,replay:!1,changes:0};const d=_t(typeof n.metadata_json=="string"?n.metadata_json:null);if(a&&(d.currentStage=a),r!==void 0&&(d.iteration=r),a&&s&&e.appendHistory!==!1){const l=Qe(d.stageHistory);l.push({stage:a,at:s}),d.stageHistory=l.slice(-ce)}const o=this.db.prepare("UPDATE jobs SET metadata_json = ? WHERE job_id = ?").run(S(d),e.jobId);return this.toWriteResult(o)}catch(n){throw m(n,"sqlite-mutation-failed")}}updateJobAndExistingGoalCampaignStatus(e){return this.runInTransaction(()=>{const a=this.db.prepare(`
|
|
2066
212
|
UPDATE jobs
|
|
2067
213
|
SET status = ?,
|
|
2068
214
|
reason_code = COALESCE(?, reason_code),
|
|
2069
215
|
updated_at = ?,
|
|
2070
216
|
next_run_at = COALESCE(?, next_run_at)
|
|
2071
217
|
WHERE job_id = ?
|
|
2072
|
-
`).run(
|
|
2073
|
-
if (jobResult.changes === 0) {
|
|
2074
|
-
throw new ContinuitySqliteStoreError('sqlite-foreign-key-failed', `Continuity SQLite job ${input.jobId} was not found for status parity update.`);
|
|
2075
|
-
}
|
|
2076
|
-
const campaignResult = this.db.prepare(`
|
|
218
|
+
`).run(e.status,e.reasonCode??null,e.updatedAt,e.nextRunAt??null,e.jobId);if(a.changes===0)throw new i("sqlite-foreign-key-failed",`Continuity SQLite job ${e.jobId} was not found for status parity update.`);const r=this.db.prepare(`
|
|
2077
219
|
UPDATE goal_campaigns
|
|
2078
220
|
SET status = ?,
|
|
2079
221
|
reason_code = COALESCE(?, reason_code),
|
|
2080
222
|
updated_at = ?
|
|
2081
223
|
WHERE job_id = ?
|
|
2082
|
-
`).run(
|
|
2083
|
-
return this.toWriteResult({ changes: jobResult.changes + campaignResult.changes });
|
|
2084
|
-
});
|
|
2085
|
-
}
|
|
2086
|
-
scheduleNextTick(input) {
|
|
2087
|
-
return this.updateJobStatus({
|
|
2088
|
-
jobId: input.jobId,
|
|
2089
|
-
status: 'active',
|
|
2090
|
-
updatedAt: input.updatedAt,
|
|
2091
|
-
reasonCode: input.reasonCode,
|
|
2092
|
-
nextRunAt: input.scheduledAt,
|
|
2093
|
-
});
|
|
2094
|
-
}
|
|
2095
|
-
acquireLease(input) {
|
|
2096
|
-
this.assertCanMutate();
|
|
2097
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2098
|
-
try {
|
|
2099
|
-
const result = this.db.prepare(`
|
|
224
|
+
`).run(e.status,e.reasonCode??null,e.updatedAt,e.jobId);return this.toWriteResult({changes:a.changes+r.changes})})}scheduleNextTick(e){return this.updateJobStatus({jobId:e.jobId,status:"active",updatedAt:e.updatedAt,reasonCode:e.reasonCode,nextRunAt:e.scheduledAt})}acquireLease(e){this.assertCanMutate();const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
2100
225
|
INSERT OR IGNORE INTO job_leases (
|
|
2101
226
|
lease_id, job_id, runner_id, status, acquired_at, expires_at,
|
|
2102
227
|
operation_id, idempotency_key, metadata_json
|
|
2103
228
|
) VALUES (?, ?, ?, 'acquired', ?, ?, ?, ?, ?)
|
|
2104
|
-
`).run(
|
|
2105
|
-
return this.toWriteResult(result);
|
|
2106
|
-
}
|
|
2107
|
-
catch (error) {
|
|
2108
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
releaseLease(input) {
|
|
2112
|
-
this.assertCanMutate();
|
|
2113
|
-
try {
|
|
2114
|
-
const result = this.db.prepare(`
|
|
229
|
+
`).run(e.leaseId,e.jobId,e.runnerId,e.acquiredAt,e.expiresAt,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}releaseLease(e){this.assertCanMutate();try{const a=this.db.prepare(`
|
|
2115
230
|
UPDATE job_leases
|
|
2116
231
|
SET status = 'released', released_at = ?
|
|
2117
232
|
WHERE lease_id = ? AND released_at IS NULL
|
|
2118
|
-
`).run(
|
|
2119
|
-
if (result.changes === 0) {
|
|
2120
|
-
throw new ContinuitySqliteStoreError('sqlite-foreign-key-failed', `Continuity SQLite lease ${input.leaseId} was not found for release.`);
|
|
2121
|
-
}
|
|
2122
|
-
return this.toWriteResult(result);
|
|
2123
|
-
}
|
|
2124
|
-
catch (error) {
|
|
2125
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
recordStateSnapshot(input) {
|
|
2129
|
-
this.assertCanMutate();
|
|
2130
|
-
assertPublicSafeText(input.summary, 'state_snapshots.summary');
|
|
2131
|
-
assertPublicSafeText(input.relativePath, 'state_snapshots.relative_path');
|
|
2132
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2133
|
-
try {
|
|
2134
|
-
const result = this.db.prepare(`
|
|
233
|
+
`).run(e.releasedAt,e.leaseId);if(a.changes===0)throw new i("sqlite-foreign-key-failed",`Continuity SQLite lease ${e.leaseId} was not found for release.`);return this.toWriteResult(a)}catch(a){throw m(a,"sqlite-mutation-failed")}}recordStateSnapshot(e){this.assertCanMutate(),b(e.summary,"state_snapshots.summary"),b(e.relativePath,"state_snapshots.relative_path");const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
2135
234
|
INSERT OR IGNORE INTO state_snapshots (
|
|
2136
235
|
snapshot_id, job_id, kind, relative_path, sha256, summary, captured_at,
|
|
2137
236
|
operation_id, idempotency_key, metadata_json
|
|
2138
237
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2139
|
-
`).run(
|
|
2140
|
-
return this.toWriteResult(result);
|
|
2141
|
-
}
|
|
2142
|
-
catch (error) {
|
|
2143
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
recordArtifact(input) {
|
|
2147
|
-
this.assertCanMutate();
|
|
2148
|
-
assertPublicSafeText(input.kind, 'artifacts.kind');
|
|
2149
|
-
assertPublicSafeText(input.relativePath, 'artifacts.relative_path');
|
|
2150
|
-
assertPublicSafeText(input.summary, 'artifacts.summary');
|
|
2151
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2152
|
-
try {
|
|
2153
|
-
const result = this.db.prepare(`
|
|
238
|
+
`).run(e.snapshotId,e.jobId??null,e.kind,e.relativePath??null,e.sha256??null,e.summary??null,e.capturedAt,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}recordArtifact(e){this.assertCanMutate(),b(e.kind,"artifacts.kind"),b(e.relativePath,"artifacts.relative_path"),b(e.summary,"artifacts.summary");const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
2154
239
|
INSERT OR IGNORE INTO artifacts (
|
|
2155
240
|
artifact_id, job_id, kind, relative_path, sha256, summary,
|
|
2156
241
|
operation_id, idempotency_key, created_at, metadata_json
|
|
2157
242
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2158
|
-
`).run(
|
|
2159
|
-
return this.toWriteResult(result);
|
|
2160
|
-
}
|
|
2161
|
-
catch (error) {
|
|
2162
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
enqueueOutbox(input) {
|
|
2166
|
-
this.assertCanMutate();
|
|
2167
|
-
assertOutboxIdentity({
|
|
2168
|
-
outboxId: input.outboxId,
|
|
2169
|
-
jobId: input.jobId,
|
|
2170
|
-
operationId: input.operationId,
|
|
2171
|
-
idempotencyKey: input.idempotencyKey,
|
|
2172
|
-
type: input.type,
|
|
2173
|
-
status: input.status,
|
|
2174
|
-
deliveryCursor: input.deliveryCursor,
|
|
2175
|
-
timestamp: input.createdAt,
|
|
2176
|
-
reasonCode: input.lastErrorReason ?? input.lastErrorCode,
|
|
2177
|
-
summary: input.lastErrorSummary ?? input.deadLetterSummary,
|
|
2178
|
-
});
|
|
2179
|
-
assertOutboxIdentity({ outboxId: input.outboxId, timestamp: input.updatedAt });
|
|
2180
|
-
assertBoundedPublicText(input.nextAttemptAt, 'outbox.nextAttemptAt', 80);
|
|
2181
|
-
assertBoundedPublicText(input.lastAttemptAt, 'outbox.lastAttemptAt', 80);
|
|
2182
|
-
assertBoundedPublicText(input.sentAt, 'outbox.sentAt', 80);
|
|
2183
|
-
assertBoundedPublicText(input.ackedAt, 'outbox.ackedAt', 80);
|
|
2184
|
-
assertBoundedPublicText(input.deadLetteredAt, 'outbox.deadLetteredAt', 80);
|
|
2185
|
-
assertNonNegativeInteger(input.retryCount, 'outbox.retryCount');
|
|
2186
|
-
assertNonNegativeInteger(input.attemptCount, 'outbox.attemptCount');
|
|
2187
|
-
const payloadJson = stringifyPublicMetadata(input.payload);
|
|
2188
|
-
try {
|
|
2189
|
-
const result = this.db.prepare(`
|
|
243
|
+
`).run(e.artifactId,e.jobId??null,e.kind,e.relativePath,e.sha256??null,e.summary??null,e.operationId??null,e.idempotencyKey??null,e.createdAt,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}enqueueOutbox(e){this.assertCanMutate(),v({outboxId:e.outboxId,jobId:e.jobId,operationId:e.operationId,idempotencyKey:e.idempotencyKey,type:e.type,status:e.status,deliveryCursor:e.deliveryCursor,timestamp:e.createdAt,reasonCode:e.lastErrorReason??e.lastErrorCode,summary:e.lastErrorSummary??e.deadLetterSummary}),v({outboxId:e.outboxId,timestamp:e.updatedAt}),c(e.nextAttemptAt,"outbox.nextAttemptAt",80),c(e.lastAttemptAt,"outbox.lastAttemptAt",80),c(e.sentAt,"outbox.sentAt",80),c(e.ackedAt,"outbox.ackedAt",80),c(e.deadLetteredAt,"outbox.deadLetteredAt",80),N(e.retryCount,"outbox.retryCount"),N(e.attemptCount,"outbox.attemptCount");const a=S(e.payload);try{const r=this.db.prepare(`
|
|
2190
244
|
INSERT OR IGNORE INTO outbox (
|
|
2191
245
|
outbox_id, job_id, operation_id, idempotency_key, type, status,
|
|
2192
246
|
payload_json, retry_count, attempt_count, delivery_cursor,
|
|
@@ -2194,57 +248,18 @@ export class ContinuitySqliteStore {
|
|
|
2194
248
|
last_error_reason, last_error_summary, dead_letter_summary,
|
|
2195
249
|
dead_lettered_at, created_at, updated_at
|
|
2196
250
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2197
|
-
`).run(
|
|
2198
|
-
return this.toWriteResult(result);
|
|
2199
|
-
}
|
|
2200
|
-
catch (error) {
|
|
2201
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
selectOutboxBatch(options) {
|
|
2205
|
-
assertOutboxIdentity({ outboxId: 'outbox-query', timestamp: options.nowIso });
|
|
2206
|
-
const limit = Math.max(1, Math.min(100, Math.floor(options.limit)));
|
|
2207
|
-
const statuses = uniqueValues(options.statuses?.length ? options.statuses : ['pending', 'failed']);
|
|
2208
|
-
for (const status of statuses) {
|
|
2209
|
-
if (!isOutboxStatus(status)) {
|
|
2210
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Continuity SQLite outbox batch query rejected an unknown status.');
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
const placeholders = statuses.map(() => '?').join(', ');
|
|
2214
|
-
const rows = this.db.prepare(`
|
|
251
|
+
`).run(e.outboxId,e.jobId??null,e.operationId,e.idempotencyKey??null,e.type,e.status??"pending",a??"{}",e.retryCount??0,e.attemptCount??0,e.deliveryCursor??null,e.nextAttemptAt??null,e.lastAttemptAt??null,e.sentAt??null,e.ackedAt??null,e.lastErrorCode??null,e.lastErrorReason??e.lastErrorCode??null,e.lastErrorSummary??null,e.deadLetterSummary??null,e.deadLetteredAt??null,e.createdAt,e.updatedAt);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}selectOutboxBatch(e){v({outboxId:"outbox-query",timestamp:e.nowIso});const a=Math.max(1,Math.min(100,Math.floor(e.limit))),r=q(e.statuses?.length?e.statuses:["pending","failed"]);for(const d of r)if(!Ee(d))throw new i("sqlite-public-safety-rejected","Continuity SQLite outbox batch query rejected an unknown status.");const s=r.map(()=>"?").join(", ");return this.db.prepare(`
|
|
2215
252
|
SELECT outbox_id, job_id, operation_id, idempotency_key, type, status,
|
|
2216
253
|
payload_json, retry_count, attempt_count, delivery_cursor,
|
|
2217
254
|
next_attempt_at, last_attempt_at, sent_at, acked_at, last_error_code,
|
|
2218
255
|
last_error_reason, last_error_summary, dead_letter_summary,
|
|
2219
256
|
dead_lettered_at, created_at, updated_at
|
|
2220
257
|
FROM outbox
|
|
2221
|
-
WHERE status IN (${
|
|
258
|
+
WHERE status IN (${s})
|
|
2222
259
|
AND (status NOT IN ('pending', 'failed') OR next_attempt_at IS NULL OR next_attempt_at <= ?)
|
|
2223
260
|
ORDER BY COALESCE(next_attempt_at, created_at) ASC, created_at ASC, outbox_id ASC
|
|
2224
261
|
LIMIT ?
|
|
2225
|
-
`).all(...
|
|
2226
|
-
return rows.map(toOutboxDeliveryRow);
|
|
2227
|
-
}
|
|
2228
|
-
markOutboxSent(input) {
|
|
2229
|
-
this.assertCanMutate();
|
|
2230
|
-
assertOutboxIdentity({
|
|
2231
|
-
outboxId: input.outboxId,
|
|
2232
|
-
deliveryCursor: input.deliveryCursor,
|
|
2233
|
-
timestamp: input.sentAt,
|
|
2234
|
-
reasonCode: input.reasonCode,
|
|
2235
|
-
});
|
|
2236
|
-
try {
|
|
2237
|
-
return this.runInTransaction(() => {
|
|
2238
|
-
const row = this.getOutboxTransitionRow(input.outboxId);
|
|
2239
|
-
if (!row)
|
|
2240
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'missing', changes: 0, replay: false, reasonCode: 'outbox-missing' });
|
|
2241
|
-
if ((row.status === 'sent' || row.status === 'acked') && row.delivery_cursor === input.deliveryCursor) {
|
|
2242
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: row.status, changes: 0, replay: true, deliveryCursor: input.deliveryCursor, reasonCode: input.reasonCode ?? 'outbox-delivery-replay' });
|
|
2243
|
-
}
|
|
2244
|
-
if (row.status === 'sent' || row.status === 'acked' || row.status === 'dead_letter') {
|
|
2245
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'conflict', changes: 0, replay: false, deliveryCursor: row.delivery_cursor ?? undefined, reasonCode: 'outbox-delivery-conflict' });
|
|
2246
|
-
}
|
|
2247
|
-
const result = this.db.prepare(`
|
|
262
|
+
`).all(...r,e.nowIso,a).map(yt)}markOutboxSent(e){this.assertCanMutate(),v({outboxId:e.outboxId,deliveryCursor:e.deliveryCursor,timestamp:e.sentAt,reasonCode:e.reasonCode});try{return this.runInTransaction(()=>{const a=this.getOutboxTransitionRow(e.outboxId);if(!a)return C({outboxId:e.outboxId,status:"missing",changes:0,replay:!1,reasonCode:"outbox-missing"});if((a.status==="sent"||a.status==="acked")&&a.delivery_cursor===e.deliveryCursor)return C({outboxId:e.outboxId,status:a.status,changes:0,replay:!0,deliveryCursor:e.deliveryCursor,reasonCode:e.reasonCode??"outbox-delivery-replay"});if(a.status==="sent"||a.status==="acked"||a.status==="dead_letter")return C({outboxId:e.outboxId,status:"conflict",changes:0,replay:!1,deliveryCursor:a.delivery_cursor??void 0,reasonCode:"outbox-delivery-conflict"});const r=this.db.prepare(`
|
|
2248
263
|
UPDATE outbox
|
|
2249
264
|
SET status = 'sent',
|
|
2250
265
|
delivery_cursor = ?,
|
|
@@ -2255,34 +270,7 @@ export class ContinuitySqliteStore {
|
|
|
2255
270
|
updated_at = ?
|
|
2256
271
|
WHERE outbox_id = ?
|
|
2257
272
|
AND status IN ('pending', 'failed')
|
|
2258
|
-
`).run(
|
|
2259
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'sent', changes: result.changes, replay: false, deliveryCursor: input.deliveryCursor, reasonCode: input.reasonCode ?? 'outbox-sent' });
|
|
2260
|
-
});
|
|
2261
|
-
}
|
|
2262
|
-
catch (error) {
|
|
2263
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
ackOutbox(input) {
|
|
2267
|
-
this.assertCanMutate();
|
|
2268
|
-
assertOutboxIdentity({
|
|
2269
|
-
outboxId: input.outboxId,
|
|
2270
|
-
deliveryCursor: input.deliveryCursor,
|
|
2271
|
-
timestamp: input.ackedAt,
|
|
2272
|
-
reasonCode: input.reasonCode,
|
|
2273
|
-
});
|
|
2274
|
-
try {
|
|
2275
|
-
return this.runInTransaction(() => {
|
|
2276
|
-
const row = this.getOutboxTransitionRow(input.outboxId);
|
|
2277
|
-
if (!row)
|
|
2278
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'missing', changes: 0, replay: false, reasonCode: 'outbox-missing' });
|
|
2279
|
-
if (row.status === 'acked' && (!input.deliveryCursor || row.delivery_cursor === input.deliveryCursor)) {
|
|
2280
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'acked', changes: 0, replay: true, deliveryCursor: row.delivery_cursor ?? undefined, reasonCode: input.reasonCode ?? 'outbox-ack-replay' });
|
|
2281
|
-
}
|
|
2282
|
-
if (row.status !== 'sent' || (input.deliveryCursor && row.delivery_cursor !== input.deliveryCursor)) {
|
|
2283
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'conflict', changes: 0, replay: false, deliveryCursor: row.delivery_cursor ?? undefined, reasonCode: 'outbox-ack-conflict' });
|
|
2284
|
-
}
|
|
2285
|
-
const result = this.db.prepare(`
|
|
273
|
+
`).run(e.deliveryCursor,e.sentAt,e.sentAt,e.sentAt,e.outboxId);return C({outboxId:e.outboxId,status:"sent",changes:r.changes,replay:!1,deliveryCursor:e.deliveryCursor,reasonCode:e.reasonCode??"outbox-sent"})})}catch(a){throw m(a,"sqlite-mutation-failed")}}ackOutbox(e){this.assertCanMutate(),v({outboxId:e.outboxId,deliveryCursor:e.deliveryCursor,timestamp:e.ackedAt,reasonCode:e.reasonCode});try{return this.runInTransaction(()=>{const a=this.getOutboxTransitionRow(e.outboxId);if(!a)return C({outboxId:e.outboxId,status:"missing",changes:0,replay:!1,reasonCode:"outbox-missing"});if(a.status==="acked"&&(!e.deliveryCursor||a.delivery_cursor===e.deliveryCursor))return C({outboxId:e.outboxId,status:"acked",changes:0,replay:!0,deliveryCursor:a.delivery_cursor??void 0,reasonCode:e.reasonCode??"outbox-ack-replay"});if(a.status!=="sent"||e.deliveryCursor&&a.delivery_cursor!==e.deliveryCursor)return C({outboxId:e.outboxId,status:"conflict",changes:0,replay:!1,deliveryCursor:a.delivery_cursor??void 0,reasonCode:"outbox-ack-conflict"});const r=this.db.prepare(`
|
|
2286
274
|
UPDATE outbox
|
|
2287
275
|
SET status = 'acked',
|
|
2288
276
|
acked_at = ?,
|
|
@@ -2292,48 +280,7 @@ export class ContinuitySqliteStore {
|
|
|
2292
280
|
last_error_reason = NULL
|
|
2293
281
|
WHERE outbox_id = ?
|
|
2294
282
|
AND status = 'sent'
|
|
2295
|
-
`).run(
|
|
2296
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'acked', changes: result.changes, replay: false, deliveryCursor: row.delivery_cursor ?? undefined, reasonCode: input.reasonCode ?? 'outbox-acked' });
|
|
2297
|
-
});
|
|
2298
|
-
}
|
|
2299
|
-
catch (error) {
|
|
2300
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
failOutboxDelivery(input) {
|
|
2304
|
-
this.assertCanMutate();
|
|
2305
|
-
assertOutboxIdentity({
|
|
2306
|
-
outboxId: input.outboxId,
|
|
2307
|
-
deliveryCursor: input.deliveryCursor,
|
|
2308
|
-
timestamp: input.failedAt,
|
|
2309
|
-
reasonCode: input.reasonCode,
|
|
2310
|
-
summary: input.summary,
|
|
2311
|
-
});
|
|
2312
|
-
assertBoundedPublicText(input.nextRetryAt, 'outbox.nextRetryAt', 80);
|
|
2313
|
-
try {
|
|
2314
|
-
return this.runInTransaction(() => {
|
|
2315
|
-
const row = this.getOutboxTransitionRow(input.outboxId);
|
|
2316
|
-
if (!row)
|
|
2317
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'missing', changes: 0, replay: false, reasonCode: 'outbox-missing' });
|
|
2318
|
-
if (row.status === 'acked') {
|
|
2319
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'acked', changes: 0, replay: true, deliveryCursor: row.delivery_cursor ?? undefined, reasonCode: 'outbox-already-acked' });
|
|
2320
|
-
}
|
|
2321
|
-
if (row.status === 'dead_letter') {
|
|
2322
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'dead_letter', changes: 0, replay: true, deliveryCursor: row.delivery_cursor ?? undefined, deadLetteredAt: row.dead_lettered_at ?? undefined, reasonCode: row.last_error_reason ?? row.last_error_code ?? input.reasonCode });
|
|
2323
|
-
}
|
|
2324
|
-
if (input.deliveryCursor && row.delivery_cursor && row.delivery_cursor !== input.deliveryCursor) {
|
|
2325
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'conflict', changes: 0, replay: false, deliveryCursor: row.delivery_cursor, reasonCode: 'outbox-fail-conflict' });
|
|
2326
|
-
}
|
|
2327
|
-
const attemptCount = Math.max(1, row.attempt_count ?? 0, row.retry_count ?? 0);
|
|
2328
|
-
const decision = computeContinuityOutboxFailureDecision({
|
|
2329
|
-
nowIso: input.failedAt,
|
|
2330
|
-
attemptCount,
|
|
2331
|
-
reasonCode: input.reasonCode,
|
|
2332
|
-
summary: input.summary,
|
|
2333
|
-
nextRetryAt: input.nextRetryAt,
|
|
2334
|
-
policy: input.policy,
|
|
2335
|
-
});
|
|
2336
|
-
const result = this.db.prepare(`
|
|
283
|
+
`).run(e.ackedAt,e.ackedAt,e.outboxId);return C({outboxId:e.outboxId,status:"acked",changes:r.changes,replay:!1,deliveryCursor:a.delivery_cursor??void 0,reasonCode:e.reasonCode??"outbox-acked"})})}catch(a){throw m(a,"sqlite-mutation-failed")}}failOutboxDelivery(e){this.assertCanMutate(),v({outboxId:e.outboxId,deliveryCursor:e.deliveryCursor,timestamp:e.failedAt,reasonCode:e.reasonCode,summary:e.summary}),c(e.nextRetryAt,"outbox.nextRetryAt",80);try{return this.runInTransaction(()=>{const a=this.getOutboxTransitionRow(e.outboxId);if(!a)return C({outboxId:e.outboxId,status:"missing",changes:0,replay:!1,reasonCode:"outbox-missing"});if(a.status==="acked")return C({outboxId:e.outboxId,status:"acked",changes:0,replay:!0,deliveryCursor:a.delivery_cursor??void 0,reasonCode:"outbox-already-acked"});if(a.status==="dead_letter")return C({outboxId:e.outboxId,status:"dead_letter",changes:0,replay:!0,deliveryCursor:a.delivery_cursor??void 0,deadLetteredAt:a.dead_lettered_at??void 0,reasonCode:a.last_error_reason??a.last_error_code??e.reasonCode});if(e.deliveryCursor&&a.delivery_cursor&&a.delivery_cursor!==e.deliveryCursor)return C({outboxId:e.outboxId,status:"conflict",changes:0,replay:!1,deliveryCursor:a.delivery_cursor,reasonCode:"outbox-fail-conflict"});const r=Math.max(1,a.attempt_count??0,a.retry_count??0),s=Ue({nowIso:e.failedAt,attemptCount:r,reasonCode:e.reasonCode,summary:e.summary,nextRetryAt:e.nextRetryAt,policy:e.policy}),n=this.db.prepare(`
|
|
2337
284
|
UPDATE outbox
|
|
2338
285
|
SET status = ?,
|
|
2339
286
|
retry_count = MAX(retry_count, ?),
|
|
@@ -2346,40 +293,7 @@ export class ContinuitySqliteStore {
|
|
|
2346
293
|
updated_at = ?
|
|
2347
294
|
WHERE outbox_id = ?
|
|
2348
295
|
AND status IN ('pending', 'sent', 'failed')
|
|
2349
|
-
`).run(
|
|
2350
|
-
return outboxTransitionResult({
|
|
2351
|
-
outboxId: input.outboxId,
|
|
2352
|
-
status: decision.status,
|
|
2353
|
-
changes: result.changes,
|
|
2354
|
-
replay: false,
|
|
2355
|
-
deliveryCursor: row.delivery_cursor ?? input.deliveryCursor,
|
|
2356
|
-
reasonCode: decision.reasonCode,
|
|
2357
|
-
...(decision.nextRetryAt ? { nextRetryAt: decision.nextRetryAt } : {}),
|
|
2358
|
-
...(decision.deadLetteredAt ? { deadLetteredAt: decision.deadLetteredAt } : {}),
|
|
2359
|
-
});
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
catch (error) {
|
|
2363
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
deadLetterOutbox(input) {
|
|
2367
|
-
this.assertCanMutate();
|
|
2368
|
-
assertOutboxIdentity({
|
|
2369
|
-
outboxId: input.outboxId,
|
|
2370
|
-
timestamp: input.deadLetteredAt,
|
|
2371
|
-
reasonCode: input.reasonCode,
|
|
2372
|
-
summary: input.summary,
|
|
2373
|
-
});
|
|
2374
|
-
try {
|
|
2375
|
-
return this.runInTransaction(() => {
|
|
2376
|
-
const row = this.getOutboxTransitionRow(input.outboxId);
|
|
2377
|
-
if (!row)
|
|
2378
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'missing', changes: 0, replay: false, reasonCode: 'outbox-missing' });
|
|
2379
|
-
if (row.status === 'dead_letter') {
|
|
2380
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'dead_letter', changes: 0, replay: true, deliveryCursor: row.delivery_cursor ?? undefined, deadLetteredAt: row.dead_lettered_at ?? undefined, reasonCode: row.last_error_reason ?? row.last_error_code ?? input.reasonCode });
|
|
2381
|
-
}
|
|
2382
|
-
const result = this.db.prepare(`
|
|
296
|
+
`).run(s.status,r,s.status==="failed"?s.nextRetryAt??e.failedAt:null,e.reasonCode,e.reasonCode,e.summary??null,s.status==="dead_letter"?e.summary??e.reasonCode:null,s.deadLetteredAt??null,e.failedAt,e.outboxId);return C({outboxId:e.outboxId,status:s.status,changes:n.changes,replay:!1,deliveryCursor:a.delivery_cursor??e.deliveryCursor,reasonCode:s.reasonCode,...s.nextRetryAt?{nextRetryAt:s.nextRetryAt}:{},...s.deadLetteredAt?{deadLetteredAt:s.deadLetteredAt}:{}})})}catch(a){throw m(a,"sqlite-mutation-failed")}}deadLetterOutbox(e){this.assertCanMutate(),v({outboxId:e.outboxId,timestamp:e.deadLetteredAt,reasonCode:e.reasonCode,summary:e.summary});try{return this.runInTransaction(()=>{const a=this.getOutboxTransitionRow(e.outboxId);if(!a)return C({outboxId:e.outboxId,status:"missing",changes:0,replay:!1,reasonCode:"outbox-missing"});if(a.status==="dead_letter")return C({outboxId:e.outboxId,status:"dead_letter",changes:0,replay:!0,deliveryCursor:a.delivery_cursor??void 0,deadLetteredAt:a.dead_lettered_at??void 0,reasonCode:a.last_error_reason??a.last_error_code??e.reasonCode});const r=this.db.prepare(`
|
|
2383
297
|
UPDATE outbox
|
|
2384
298
|
SET status = 'dead_letter',
|
|
2385
299
|
next_attempt_at = NULL,
|
|
@@ -2391,39 +305,13 @@ export class ContinuitySqliteStore {
|
|
|
2391
305
|
updated_at = ?
|
|
2392
306
|
WHERE outbox_id = ?
|
|
2393
307
|
AND status <> 'acked'
|
|
2394
|
-
`).run(
|
|
2395
|
-
return outboxTransitionResult({ outboxId: input.outboxId, status: 'dead_letter', changes: result.changes, replay: false, deliveryCursor: row.delivery_cursor ?? undefined, deadLetteredAt: input.deadLetteredAt, reasonCode: input.reasonCode });
|
|
2396
|
-
});
|
|
2397
|
-
}
|
|
2398
|
-
catch (error) {
|
|
2399
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
retryOutboxEntries(options) {
|
|
2403
|
-
this.assertCanMutate();
|
|
2404
|
-
const limit = Math.max(1, Math.min(100, Math.floor(options.limit)));
|
|
2405
|
-
const statuses = uniqueValues(options.statuses?.length ? options.statuses : ['failed', 'dead_letter']);
|
|
2406
|
-
const reasonCode = options.reasonCode ?? 'backoff-scheduled';
|
|
2407
|
-
assertPublicSafeText(reasonCode, 'outbox.last_error_code');
|
|
2408
|
-
assertPublicSafeText(options.nextAttemptAt, 'outbox.next_attempt_at');
|
|
2409
|
-
try {
|
|
2410
|
-
return this.runInTransaction(() => {
|
|
2411
|
-
const placeholders = statuses.map(() => '?').join(', ');
|
|
2412
|
-
const rows = this.db.prepare(`
|
|
308
|
+
`).run(e.reasonCode,e.reasonCode,e.summary,e.summary,e.deadLetteredAt,e.deadLetteredAt,e.outboxId);return C({outboxId:e.outboxId,status:"dead_letter",changes:r.changes,replay:!1,deliveryCursor:a.delivery_cursor??void 0,deadLetteredAt:e.deadLetteredAt,reasonCode:e.reasonCode})})}catch(a){throw m(a,"sqlite-mutation-failed")}}retryOutboxEntries(e){this.assertCanMutate();const a=Math.max(1,Math.min(100,Math.floor(e.limit))),r=q(e.statuses?.length?e.statuses:["failed","dead_letter"]),s=e.reasonCode??"backoff-scheduled";b(s,"outbox.last_error_code"),b(e.nextAttemptAt,"outbox.next_attempt_at");try{return this.runInTransaction(()=>{const n=r.map(()=>"?").join(", "),o=this.db.prepare(`
|
|
2413
309
|
SELECT outbox_id
|
|
2414
310
|
FROM outbox
|
|
2415
|
-
WHERE status IN (${
|
|
311
|
+
WHERE status IN (${n})
|
|
2416
312
|
ORDER BY updated_at ASC, outbox_id ASC
|
|
2417
313
|
LIMIT ?
|
|
2418
|
-
`).all(...
|
|
2419
|
-
const retriedOutboxIds = rows
|
|
2420
|
-
.map((row) => row.outbox_id)
|
|
2421
|
-
.filter((outboxId) => typeof outboxId === 'string' && outboxId.length > 0);
|
|
2422
|
-
if (retriedOutboxIds.length === 0) {
|
|
2423
|
-
return { retriedOutboxIds: [], changes: 0, reasonCode, publicSafe: true };
|
|
2424
|
-
}
|
|
2425
|
-
const idPlaceholders = retriedOutboxIds.map(() => '?').join(', ');
|
|
2426
|
-
const result = this.db.prepare(`
|
|
314
|
+
`).all(...r,a).map(h=>h.outbox_id).filter(h=>typeof h=="string"&&h.length>0);if(o.length===0)return{retriedOutboxIds:[],changes:0,reasonCode:s,publicSafe:!0};const l=o.map(()=>"?").join(", "),I=this.db.prepare(`
|
|
2427
315
|
UPDATE outbox
|
|
2428
316
|
SET status = 'pending',
|
|
2429
317
|
retry_count = retry_count + 1,
|
|
@@ -2436,22 +324,8 @@ export class ContinuitySqliteStore {
|
|
|
2436
324
|
dead_letter_summary = NULL,
|
|
2437
325
|
dead_lettered_at = NULL,
|
|
2438
326
|
updated_at = ?
|
|
2439
|
-
WHERE outbox_id IN (${
|
|
2440
|
-
`).run(
|
|
2441
|
-
return { retriedOutboxIds, changes: result.changes, reasonCode, publicSafe: true };
|
|
2442
|
-
});
|
|
2443
|
-
}
|
|
2444
|
-
catch (error) {
|
|
2445
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
upsertGoalCampaign(input) {
|
|
2449
|
-
this.assertCanMutate();
|
|
2450
|
-
assertPublicSafeText(input.title, 'goal_campaigns.title');
|
|
2451
|
-
assertPublicSafeText(input.publicSummary, 'goal_campaigns.public_summary');
|
|
2452
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2453
|
-
try {
|
|
2454
|
-
const result = this.db.prepare(`
|
|
327
|
+
WHERE outbox_id IN (${l})
|
|
328
|
+
`).run(e.nextAttemptAt??e.nowIso,s,s,e.nowIso,...o);return{retriedOutboxIds:o,changes:I.changes,reasonCode:s,publicSafe:!0}})}catch(n){throw m(n,"sqlite-mutation-failed")}}upsertGoalCampaign(e){this.assertCanMutate(),b(e.title,"goal_campaigns.title"),b(e.publicSummary,"goal_campaigns.public_summary");const a=S(e.metadata);try{const r=this.db.prepare(`
|
|
2455
329
|
INSERT INTO goal_campaigns (
|
|
2456
330
|
campaign_id, job_id, status, title, public_summary, reason_code,
|
|
2457
331
|
created_at, updated_at, operation_id, idempotency_key, metadata_json
|
|
@@ -2465,20 +339,7 @@ export class ContinuitySqliteStore {
|
|
|
2465
339
|
operation_id = excluded.operation_id,
|
|
2466
340
|
idempotency_key = excluded.idempotency_key,
|
|
2467
341
|
metadata_json = excluded.metadata_json
|
|
2468
|
-
`).run(
|
|
2469
|
-
return this.toWriteResult(result);
|
|
2470
|
-
}
|
|
2471
|
-
catch (error) {
|
|
2472
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
upsertGoalLedgerItem(input) {
|
|
2476
|
-
this.assertCanMutate();
|
|
2477
|
-
assertPublicSafeText(input.summary, 'goal_ledger_items.summary');
|
|
2478
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2479
|
-
try {
|
|
2480
|
-
this.ensureGoalCampaign(input.campaignId, input.jobId, input.createdAt, input.updatedAt);
|
|
2481
|
-
const result = this.db.prepare(`
|
|
342
|
+
`).run(e.campaignId,e.jobId,e.status,e.title??null,e.publicSummary??null,e.reasonCode??null,e.createdAt,e.updatedAt,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}upsertGoalLedgerItem(e){this.assertCanMutate(),b(e.summary,"goal_ledger_items.summary");const a=S(e.metadata);try{this.ensureGoalCampaign(e.campaignId,e.jobId,e.createdAt,e.updatedAt);const r=this.db.prepare(`
|
|
2482
343
|
INSERT INTO goal_ledger_items (
|
|
2483
344
|
item_id, campaign_id, operation_id, idempotency_key, item_type, status,
|
|
2484
345
|
summary, reason_code, created_at, updated_at, metadata_json
|
|
@@ -2489,21 +350,7 @@ export class ContinuitySqliteStore {
|
|
|
2489
350
|
reason_code = excluded.reason_code,
|
|
2490
351
|
updated_at = excluded.updated_at,
|
|
2491
352
|
metadata_json = excluded.metadata_json
|
|
2492
|
-
`).run(
|
|
2493
|
-
return this.toWriteResult(result);
|
|
2494
|
-
}
|
|
2495
|
-
catch (error) {
|
|
2496
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
upsertGoalVerifier(input) {
|
|
2500
|
-
this.assertCanMutate();
|
|
2501
|
-
assertPublicSafeText(input.summary, 'goal_verifiers.summary');
|
|
2502
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2503
|
-
try {
|
|
2504
|
-
const fallbackTimestamp = input.lastCheckedAt ?? this.now();
|
|
2505
|
-
this.ensureGoalCampaign(input.campaignId, input.jobId, fallbackTimestamp, fallbackTimestamp);
|
|
2506
|
-
const result = this.db.prepare(`
|
|
353
|
+
`).run(e.itemId,e.campaignId,e.operationId??null,e.idempotencyKey??null,e.itemType,e.status,e.summary,e.reasonCode??null,e.createdAt,e.updatedAt,a);return this.toWriteResult(r)}catch(r){throw m(r,"sqlite-mutation-failed")}}upsertGoalVerifier(e){this.assertCanMutate(),b(e.summary,"goal_verifiers.summary");const a=S(e.metadata);try{const r=e.lastCheckedAt??this.now();this.ensureGoalCampaign(e.campaignId,e.jobId,r,r);const s=this.db.prepare(`
|
|
2507
354
|
INSERT INTO goal_verifiers (
|
|
2508
355
|
verifier_id, campaign_id, kind, status, summary, reason_code,
|
|
2509
356
|
last_checked_at, operation_id, idempotency_key, metadata_json
|
|
@@ -2514,21 +361,7 @@ export class ContinuitySqliteStore {
|
|
|
2514
361
|
reason_code = excluded.reason_code,
|
|
2515
362
|
last_checked_at = excluded.last_checked_at,
|
|
2516
363
|
metadata_json = excluded.metadata_json
|
|
2517
|
-
`).run(
|
|
2518
|
-
return this.toWriteResult(result);
|
|
2519
|
-
}
|
|
2520
|
-
catch (error) {
|
|
2521
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
upsertGoalReviewGate(input) {
|
|
2525
|
-
this.assertCanMutate();
|
|
2526
|
-
assertPublicSafeText(input.summary, 'goal_review_gates.summary');
|
|
2527
|
-
const metadataJson = stringifyPublicMetadata(input.metadata);
|
|
2528
|
-
try {
|
|
2529
|
-
const fallbackTimestamp = this.now();
|
|
2530
|
-
this.ensureGoalCampaign(input.campaignId, input.jobId, fallbackTimestamp, fallbackTimestamp);
|
|
2531
|
-
const result = this.db.prepare(`
|
|
364
|
+
`).run(e.verifierId,e.campaignId,e.kind,e.status,e.summary??null,e.reasonCode??null,e.lastCheckedAt??null,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(s)}catch(r){throw m(r,"sqlite-mutation-failed")}}upsertGoalReviewGate(e){this.assertCanMutate(),b(e.summary,"goal_review_gates.summary");const a=S(e.metadata);try{const r=this.now();this.ensureGoalCampaign(e.campaignId,e.jobId,r,r);const s=this.db.prepare(`
|
|
2532
365
|
INSERT INTO goal_review_gates (
|
|
2533
366
|
gate_id, campaign_id, status, summary, required, reason_code,
|
|
2534
367
|
operation_id, idempotency_key, metadata_json
|
|
@@ -2539,45 +372,7 @@ export class ContinuitySqliteStore {
|
|
|
2539
372
|
required = excluded.required,
|
|
2540
373
|
reason_code = excluded.reason_code,
|
|
2541
374
|
metadata_json = excluded.metadata_json
|
|
2542
|
-
`).run(
|
|
2543
|
-
return this.toWriteResult(result);
|
|
2544
|
-
}
|
|
2545
|
-
catch (error) {
|
|
2546
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
recordOperationAudit(input) {
|
|
2550
|
-
return this.appendReceipt(input);
|
|
2551
|
-
}
|
|
2552
|
-
hasOperationRef(operationId, idempotencyKey) {
|
|
2553
|
-
this.assertCanMutate();
|
|
2554
|
-
const tables = [
|
|
2555
|
-
'jobs',
|
|
2556
|
-
'job_leases',
|
|
2557
|
-
'job_events',
|
|
2558
|
-
'job_receipts',
|
|
2559
|
-
'goal_campaigns',
|
|
2560
|
-
'goal_ledger_items',
|
|
2561
|
-
'goal_verifiers',
|
|
2562
|
-
'goal_review_gates',
|
|
2563
|
-
'loop_jobs',
|
|
2564
|
-
'artifacts',
|
|
2565
|
-
'state_snapshots',
|
|
2566
|
-
'headless_execution_attempts',
|
|
2567
|
-
'runner_config',
|
|
2568
|
-
'outbox',
|
|
2569
|
-
];
|
|
2570
|
-
const clauses = tables.map((table) => `SELECT 1 AS found FROM ${table} WHERE operation_id = ?${idempotencyKey ? ' OR idempotency_key = ?' : ''}`);
|
|
2571
|
-
const params = tables.flatMap(() => idempotencyKey ? [operationId, idempotencyKey] : [operationId]);
|
|
2572
|
-
const row = this.db.prepare(`${clauses.join(' UNION ALL ')} LIMIT 1`).get(...params);
|
|
2573
|
-
return Boolean(row);
|
|
2574
|
-
}
|
|
2575
|
-
jobExists(jobId) {
|
|
2576
|
-
const row = this.db.prepare('SELECT 1 AS found FROM jobs WHERE job_id = ? LIMIT 1').get(jobId);
|
|
2577
|
-
return Boolean(row);
|
|
2578
|
-
}
|
|
2579
|
-
getHeadlessExecutionAttempt(attemptId) {
|
|
2580
|
-
return this.db.prepare(`
|
|
375
|
+
`).run(e.gateId,e.campaignId,e.status,e.summary??null,e.required?1:0,e.reasonCode??null,e.operationId??null,e.idempotencyKey??null,a);return this.toWriteResult(s)}catch(r){throw m(r,"sqlite-mutation-failed")}}recordOperationAudit(e){return this.appendReceipt(e)}hasOperationRef(e,a){this.assertCanMutate();const r=["jobs","job_leases","job_events","job_receipts","goal_campaigns","goal_ledger_items","goal_verifiers","goal_review_gates","loop_jobs","artifacts","state_snapshots","headless_execution_attempts","runner_config","outbox"],s=r.map(o=>`SELECT 1 AS found FROM ${o} WHERE operation_id = ?${a?" OR idempotency_key = ?":""}`),n=r.flatMap(()=>a?[e,a]:[e]);return!!this.db.prepare(`${s.join(" UNION ALL ")} LIMIT 1`).get(...n)}jobExists(e){return!!this.db.prepare("SELECT 1 AS found FROM jobs WHERE job_id = ? LIMIT 1").get(e)}getHeadlessExecutionAttempt(e){return this.db.prepare(`
|
|
2581
376
|
SELECT attempt_id, execution_id, job_id, directive_hash, directive_kind,
|
|
2582
377
|
command_class, target_kind, target_ref, story_id, epic_id,
|
|
2583
378
|
runtime_id, model_id, runtime_source, status, reason_code,
|
|
@@ -2591,22 +386,13 @@ export class ContinuitySqliteStore {
|
|
|
2591
386
|
FROM headless_execution_attempts
|
|
2592
387
|
WHERE attempt_id = ?
|
|
2593
388
|
LIMIT 1
|
|
2594
|
-
`).get(
|
|
2595
|
-
}
|
|
2596
|
-
getOutboxTransitionRow(outboxId) {
|
|
2597
|
-
const row = this.db.prepare(`
|
|
389
|
+
`).get(e)}getOutboxTransitionRow(e){const a=this.db.prepare(`
|
|
2598
390
|
SELECT outbox_id, status, delivery_cursor, retry_count, attempt_count,
|
|
2599
391
|
last_error_code, last_error_reason, dead_lettered_at
|
|
2600
392
|
FROM outbox
|
|
2601
393
|
WHERE outbox_id = ?
|
|
2602
394
|
LIMIT 1
|
|
2603
|
-
`).get(
|
|
2604
|
-
if (!row || !isRecord(row))
|
|
2605
|
-
return undefined;
|
|
2606
|
-
return row;
|
|
2607
|
-
}
|
|
2608
|
-
selectReceiptRetentionCandidates(input) {
|
|
2609
|
-
const rows = this.db.prepare(`
|
|
395
|
+
`).get(e);if(!(!a||!E(a)))return a}selectReceiptRetentionCandidates(e){return this.db.prepare(`
|
|
2610
396
|
WITH sized AS (
|
|
2611
397
|
SELECT receipt_id, job_id, status, reason_code, created_at,
|
|
2612
398
|
length(receipt_id) + length(job_id) + length(status) + length(created_at)
|
|
@@ -2625,23 +411,7 @@ export class ContinuitySqliteStore {
|
|
|
2625
411
|
WHERE created_at < ? OR retain_rank > ? OR cumulative_bytes > ?
|
|
2626
412
|
ORDER BY created_at ASC, receipt_id ASC
|
|
2627
413
|
LIMIT ?
|
|
2628
|
-
`).all(
|
|
2629
|
-
return rows.flatMap((row) => {
|
|
2630
|
-
if (typeof row.receipt_id !== 'string' || typeof row.job_id !== 'string')
|
|
2631
|
-
return [];
|
|
2632
|
-
return [{
|
|
2633
|
-
kind: 'receipt',
|
|
2634
|
-
id: row.receipt_id,
|
|
2635
|
-
jobId: row.job_id,
|
|
2636
|
-
status: typeof row.status === 'string' ? row.status : 'unknown',
|
|
2637
|
-
reasonCode: typeof row.reason_code === 'string' ? row.reason_code : 'continuity-ok',
|
|
2638
|
-
createdAt: typeof row.created_at === 'string' ? row.created_at : input.cutoff,
|
|
2639
|
-
approxBytes: typeof row.approx_bytes === 'number' && row.approx_bytes >= 0 ? Math.floor(row.approx_bytes) : 0,
|
|
2640
|
-
}];
|
|
2641
|
-
});
|
|
2642
|
-
}
|
|
2643
|
-
selectOutputRetentionCandidates(input) {
|
|
2644
|
-
const rows = this.db.prepare(`
|
|
414
|
+
`).all(e.cutoff,e.maxItems,e.maxBytes,e.limit).flatMap(r=>typeof r.receipt_id!="string"||typeof r.job_id!="string"?[]:[{kind:"receipt",id:r.receipt_id,jobId:r.job_id,status:typeof r.status=="string"?r.status:"unknown",reasonCode:typeof r.reason_code=="string"?r.reason_code:"continuity-ok",createdAt:typeof r.created_at=="string"?r.created_at:e.cutoff,approxBytes:typeof r.approx_bytes=="number"&&r.approx_bytes>=0?Math.floor(r.approx_bytes):0}])}selectOutputRetentionCandidates(e){return this.db.prepare(`
|
|
2645
415
|
WITH sized AS (
|
|
2646
416
|
SELECT event_id, job_id, type, reason_code, created_at,
|
|
2647
417
|
length(event_id) + length(job_id) + length(operation_id) + length(type)
|
|
@@ -2660,42 +430,13 @@ export class ContinuitySqliteStore {
|
|
|
2660
430
|
WHERE created_at < ? OR retain_rank > ? OR cumulative_bytes > ?
|
|
2661
431
|
ORDER BY created_at ASC, event_id ASC
|
|
2662
432
|
LIMIT ?
|
|
2663
|
-
`).all(
|
|
2664
|
-
return rows.flatMap((row) => {
|
|
2665
|
-
if (typeof row.event_id !== 'string' || typeof row.job_id !== 'string')
|
|
2666
|
-
return [];
|
|
2667
|
-
return [{
|
|
2668
|
-
kind: 'output',
|
|
2669
|
-
id: row.event_id,
|
|
2670
|
-
jobId: row.job_id,
|
|
2671
|
-
status: typeof row.type === 'string' ? row.type : CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE,
|
|
2672
|
-
reasonCode: typeof row.reason_code === 'string' ? row.reason_code : 'continuity-ok',
|
|
2673
|
-
createdAt: typeof row.created_at === 'string' ? row.created_at : input.cutoff,
|
|
2674
|
-
approxBytes: typeof row.approx_bytes === 'number' && row.approx_bytes >= 0 ? Math.floor(row.approx_bytes) : 0,
|
|
2675
|
-
}];
|
|
2676
|
-
});
|
|
2677
|
-
}
|
|
2678
|
-
deleteRetentionCandidates(table, idColumn, ids) {
|
|
2679
|
-
if (ids.length === 0)
|
|
2680
|
-
return;
|
|
2681
|
-
const placeholders = ids.map(() => '?').join(', ');
|
|
2682
|
-
this.db.prepare(`DELETE FROM ${table} WHERE ${idColumn} IN (${placeholders})`).run(...ids);
|
|
2683
|
-
}
|
|
2684
|
-
preserveReceiptDerivedIteration(jobId, at) {
|
|
2685
|
-
const row = this.db.prepare(`
|
|
433
|
+
`).all(O,e.cutoff,e.maxItems,e.maxBytes,e.limit).flatMap(r=>typeof r.event_id!="string"||typeof r.job_id!="string"?[]:[{kind:"output",id:r.event_id,jobId:r.job_id,status:typeof r.type=="string"?r.type:O,reasonCode:typeof r.reason_code=="string"?r.reason_code:"continuity-ok",createdAt:typeof r.created_at=="string"?r.created_at:e.cutoff,approxBytes:typeof r.approx_bytes=="number"&&r.approx_bytes>=0?Math.floor(r.approx_bytes):0}])}deleteRetentionCandidates(e,a,r){if(r.length===0)return;const s=r.map(()=>"?").join(", ");this.db.prepare(`DELETE FROM ${e} WHERE ${a} IN (${s})`).run(...r)}preserveReceiptDerivedIteration(e,a){const r=this.db.prepare(`
|
|
2686
434
|
SELECT COUNT(*) AS iteration
|
|
2687
435
|
FROM job_receipts
|
|
2688
436
|
WHERE job_id = ?
|
|
2689
437
|
AND (operation_id LIKE 'tick-%' OR operation_id LIKE 'loop-tick-%')
|
|
2690
438
|
AND status IN ('accepted', 'applied')
|
|
2691
|
-
`).get(jobId)
|
|
2692
|
-
const iteration = typeof row?.iteration === 'number' ? row.iteration : 0;
|
|
2693
|
-
if (iteration > 0) {
|
|
2694
|
-
this.recordJobStageProgress({ jobId, iteration, at, appendHistory: false });
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
retainedReceiptOutputCounts(jobId) {
|
|
2698
|
-
const receipt = this.db.prepare(`
|
|
439
|
+
`).get(e),s=typeof r?.iteration=="number"?r.iteration:0;s>0&&this.recordJobStageProgress({jobId:e,iteration:s,at:a,appendHistory:!1})}retainedReceiptOutputCounts(e){const a=this.db.prepare(`
|
|
2699
440
|
SELECT COUNT(*) AS count,
|
|
2700
441
|
COALESCE(SUM(length(receipt_id) + length(job_id) + length(status) + length(created_at)
|
|
2701
442
|
+ COALESCE(length(operation_id), 0) + COALESCE(length(idempotency_key), 0)
|
|
@@ -2703,23 +444,14 @@ export class ContinuitySqliteStore {
|
|
|
2703
444
|
+ COALESCE(length(ignored_operation_ids_json), 0) + COALESCE(length(metadata_json), 0)), 0) AS bytes
|
|
2704
445
|
FROM job_receipts
|
|
2705
446
|
WHERE job_id = ?
|
|
2706
|
-
`).get(
|
|
2707
|
-
const output = this.db.prepare(`
|
|
447
|
+
`).get(e),r=this.db.prepare(`
|
|
2708
448
|
SELECT COUNT(*) AS count,
|
|
2709
449
|
COALESCE(SUM(length(event_id) + length(job_id) + length(operation_id) + length(type)
|
|
2710
450
|
+ length(summary) + length(created_at) + COALESCE(length(idempotency_key), 0)
|
|
2711
451
|
+ COALESCE(length(reason_code), 0) + COALESCE(length(metadata_json), 0)), 0) AS bytes
|
|
2712
452
|
FROM job_events
|
|
2713
453
|
WHERE job_id = ? AND type = ?
|
|
2714
|
-
`).get(
|
|
2715
|
-
return {
|
|
2716
|
-
receipts: typeof receipt?.count === 'number' ? receipt.count : 0,
|
|
2717
|
-
outputs: typeof output?.count === 'number' ? output.count : 0,
|
|
2718
|
-
bytes: (typeof receipt?.bytes === 'number' ? receipt.bytes : 0) + (typeof output?.bytes === 'number' ? output.bytes : 0),
|
|
2719
|
-
};
|
|
2720
|
-
}
|
|
2721
|
-
pruneReceiptCompactionSummaries(jobId, limit) {
|
|
2722
|
-
this.db.prepare(`
|
|
454
|
+
`).get(e,O);return{receipts:typeof a?.count=="number"?a.count:0,outputs:typeof r?.count=="number"?r.count:0,bytes:(typeof a?.bytes=="number"?a.bytes:0)+(typeof r?.bytes=="number"?r.bytes:0)}}pruneReceiptCompactionSummaries(e,a){this.db.prepare(`
|
|
2723
455
|
DELETE FROM job_events
|
|
2724
456
|
WHERE event_id IN (
|
|
2725
457
|
SELECT event_id
|
|
@@ -2728,69 +460,20 @@ export class ContinuitySqliteStore {
|
|
|
2728
460
|
ORDER BY created_at DESC, event_id DESC
|
|
2729
461
|
LIMIT -1 OFFSET ?
|
|
2730
462
|
)
|
|
2731
|
-
`).run(
|
|
2732
|
-
}
|
|
2733
|
-
runInTransaction(fn) {
|
|
2734
|
-
this.assertCanMutate();
|
|
2735
|
-
const tx = this.db.transaction(fn);
|
|
2736
|
-
try {
|
|
2737
|
-
if (typeof tx.immediate === 'function')
|
|
2738
|
-
return tx.immediate();
|
|
2739
|
-
return tx();
|
|
2740
|
-
}
|
|
2741
|
-
catch (error) {
|
|
2742
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
collectSnapshotRows(options) {
|
|
2746
|
-
const countRows = (sql, ...params) => {
|
|
2747
|
-
const row = this.db.prepare(sql).get(...params);
|
|
2748
|
-
return isRecord(row) && typeof row.count === 'number' ? row.count : 0;
|
|
2749
|
-
};
|
|
2750
|
-
const outboxRows = this.db.prepare(`
|
|
463
|
+
`).run(e,W,a)}runInTransaction(e){this.assertCanMutate();const a=this.db.transaction(e);try{return typeof a.immediate=="function"?a.immediate():a()}catch(r){throw m(r,"sqlite-mutation-failed")}}collectSnapshotRows(e){const a=(p,...x)=>{const A=this.db.prepare(p).get(...x);return E(A)&&typeof A.count=="number"?A.count:0},r=this.db.prepare(`
|
|
2751
464
|
SELECT status, COUNT(*) AS count FROM outbox GROUP BY status
|
|
2752
|
-
`).all();
|
|
2753
|
-
const outboxCounts = {};
|
|
2754
|
-
for (const row of outboxRows) {
|
|
2755
|
-
if (typeof row.status === 'string' && typeof row.count === 'number')
|
|
2756
|
-
outboxCounts[row.status] = row.count;
|
|
2757
|
-
}
|
|
2758
|
-
const queuePolicyRow = this.db.prepare(`
|
|
465
|
+
`).all(),s={};for(const p of r)typeof p.status=="string"&&typeof p.count=="number"&&(s[p.status]=p.count);const n=this.db.prepare(`
|
|
2759
466
|
SELECT value_json
|
|
2760
467
|
FROM runner_config
|
|
2761
468
|
WHERE key = 'headless_queue_policy'
|
|
2762
469
|
LIMIT 1
|
|
2763
|
-
`).get();
|
|
2764
|
-
let queuePolicy = DEFAULT_CONTINUITY_QUEUE_POLICY;
|
|
2765
|
-
if (isRecord(queuePolicyRow) && typeof queuePolicyRow.value_json === 'string') {
|
|
2766
|
-
try {
|
|
2767
|
-
const parsed = JSON.parse(queuePolicyRow.value_json);
|
|
2768
|
-
if (isRecord(parsed) && typeof parsed.maxItems === 'number') {
|
|
2769
|
-
queuePolicy = normalizeContinuityQueuePolicy({ maxItems: parsed.maxItems });
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
catch {
|
|
2773
|
-
// SQLite json_valid guards this row; retain bounded defaults on unexpected parse failure.
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
const queueQueued = countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'queued' AND dead_lettered_at IS NULL");
|
|
2777
|
-
const queueRetrying = countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'backoff' AND dead_lettered_at IS NULL");
|
|
2778
|
-
const queueRunning = countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running' AND dead_lettered_at IS NULL");
|
|
2779
|
-
const queueDeadLetter = countRows('SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE dead_lettered_at IS NOT NULL');
|
|
2780
|
-
const queueDepth = queueQueued + queueRetrying + queueRunning;
|
|
2781
|
-
const nextRetryRow = this.db.prepare(`
|
|
470
|
+
`).get();let d=De;if(E(n)&&typeof n.value_json=="string")try{const p=JSON.parse(n.value_json);E(p)&&typeof p.maxItems=="number"&&(d=B({maxItems:p.maxItems}))}catch{}const o=a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'queued' AND dead_lettered_at IS NULL"),l=a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'backoff' AND dead_lettered_at IS NULL"),I=a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running' AND dead_lettered_at IS NULL"),h=a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE dead_lettered_at IS NOT NULL"),f=o+l+I,_=this.db.prepare(`
|
|
2782
471
|
SELECT MIN(next_retry_at) AS next_retry_at
|
|
2783
472
|
FROM headless_execution_attempts
|
|
2784
473
|
WHERE status = 'backoff'
|
|
2785
474
|
AND dead_lettered_at IS NULL
|
|
2786
475
|
AND next_retry_at IS NOT NULL
|
|
2787
|
-
`).get();
|
|
2788
|
-
const nextRetryAt = isRecord(nextRetryRow) && typeof nextRetryRow.next_retry_at === 'string'
|
|
2789
|
-
? nextRetryRow.next_retry_at
|
|
2790
|
-
: undefined;
|
|
2791
|
-
return {
|
|
2792
|
-
schemaVersion: this.getSchemaVersion(),
|
|
2793
|
-
jobs: this.db.prepare(`
|
|
476
|
+
`).get(),u=E(_)&&typeof _.next_retry_at=="string"?_.next_retry_at:void 0;return{schemaVersion:this.getSchemaVersion(),jobs:this.db.prepare(`
|
|
2794
477
|
SELECT job_id, kind, status, summary, reason_code, created_at, updated_at,
|
|
2795
478
|
next_run_at, metadata_json,
|
|
2796
479
|
(SELECT COUNT(*)
|
|
@@ -2840,35 +523,30 @@ export class ContinuitySqliteStore {
|
|
|
2840
523
|
FROM jobs
|
|
2841
524
|
ORDER BY updated_at DESC, job_id ASC
|
|
2842
525
|
LIMIT ?
|
|
2843
|
-
`).all(
|
|
2844
|
-
staleLeases: this.db.prepare(`
|
|
526
|
+
`).all(e.jobLimit),staleLeases:this.db.prepare(`
|
|
2845
527
|
SELECT lease_id, job_id, runner_id, expires_at
|
|
2846
528
|
FROM job_leases
|
|
2847
529
|
WHERE released_at IS NULL AND expires_at <= ?
|
|
2848
530
|
ORDER BY expires_at ASC, lease_id ASC
|
|
2849
531
|
LIMIT ?
|
|
2850
|
-
`).all(
|
|
2851
|
-
receipts: this.db.prepare(`
|
|
532
|
+
`).all(e.nowIso,e.staleLeaseLimit),receipts:this.db.prepare(`
|
|
2852
533
|
SELECT receipt_id, job_id, operation_id, status, reason_code, created_at, metadata_json
|
|
2853
534
|
FROM job_receipts
|
|
2854
535
|
ORDER BY created_at DESC, receipt_id ASC
|
|
2855
536
|
LIMIT ?
|
|
2856
|
-
`).all(
|
|
2857
|
-
events: this.db.prepare(`
|
|
537
|
+
`).all(e.receiptLimit),events:this.db.prepare(`
|
|
2858
538
|
SELECT event_id, job_id, operation_id, type, summary, reason_code, created_at, metadata_json
|
|
2859
539
|
FROM job_events
|
|
2860
540
|
ORDER BY created_at DESC, event_id ASC
|
|
2861
541
|
LIMIT ?
|
|
2862
|
-
`).all(
|
|
2863
|
-
stateSnapshots: this.db.prepare(`
|
|
542
|
+
`).all(e.eventLimit),stateSnapshots:this.db.prepare(`
|
|
2864
543
|
SELECT snapshot_id, job_id, kind, relative_path, sha256, summary,
|
|
2865
544
|
captured_at, operation_id, metadata_json
|
|
2866
545
|
FROM state_snapshots
|
|
2867
546
|
WHERE kind = 'context_compaction'
|
|
2868
547
|
ORDER BY captured_at DESC, snapshot_id ASC
|
|
2869
548
|
LIMIT ?
|
|
2870
|
-
`).all(
|
|
2871
|
-
headlessExecutionAttempts: this.db.prepare(`
|
|
549
|
+
`).all(e.snapshotLimit),headlessExecutionAttempts:this.db.prepare(`
|
|
2872
550
|
SELECT attempt_id, execution_id, job_id, status, reason_code, runtime_id,
|
|
2873
551
|
model_id, retry_count, max_attempts, lease_id, runner_id,
|
|
2874
552
|
lease_expires_at, heartbeat_at, public_summary, enqueued_at,
|
|
@@ -2879,90 +557,19 @@ export class ContinuitySqliteStore {
|
|
|
2879
557
|
FROM headless_execution_attempts
|
|
2880
558
|
ORDER BY enqueued_at DESC, attempt_id ASC
|
|
2881
559
|
LIMIT ?
|
|
2882
|
-
`).all(
|
|
2883
|
-
counts: {
|
|
2884
|
-
jobs: countRows('SELECT COUNT(*) AS count FROM jobs'),
|
|
2885
|
-
activeJobs: countRows("SELECT COUNT(*) AS count FROM jobs WHERE status IN ('planned', 'active', 'running', 'waiting_external', 'waiting_human', 'backoff', 'paused', 'blocked')"),
|
|
2886
|
-
activeLeases: countRows('SELECT COUNT(*) AS count FROM job_leases WHERE released_at IS NULL'),
|
|
2887
|
-
dueJobs: countRows("SELECT COUNT(*) AS count FROM jobs WHERE next_run_at IS NOT NULL AND next_run_at <= ? AND status NOT IN ('completed', 'aborted', 'cancelled')", options.nowIso),
|
|
2888
|
-
staleLeases: countRows('SELECT COUNT(*) AS count FROM job_leases WHERE released_at IS NULL AND expires_at <= ?', options.nowIso),
|
|
2889
|
-
receipts: countRows('SELECT COUNT(*) AS count FROM job_receipts'),
|
|
2890
|
-
events: countRows('SELECT COUNT(*) AS count FROM job_events'),
|
|
2891
|
-
processingOutputSummaries: countRows('SELECT COUNT(*) AS count FROM job_events WHERE type = ?', CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE),
|
|
2892
|
-
goalLedgerItems: countRows('SELECT COUNT(*) AS count FROM goal_ledger_items'),
|
|
2893
|
-
goalVerifiers: countRows('SELECT COUNT(*) AS count FROM goal_verifiers'),
|
|
2894
|
-
goalReviewGates: countRows('SELECT COUNT(*) AS count FROM goal_review_gates'),
|
|
2895
|
-
stateSnapshots: countRows('SELECT COUNT(*) AS count FROM state_snapshots'),
|
|
2896
|
-
headlessExecutionAttempts: countRows('SELECT COUNT(*) AS count FROM headless_execution_attempts'),
|
|
2897
|
-
headlessQueuedAttempts: countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status IN ('queued', 'backoff')"),
|
|
2898
|
-
headlessRunningAttempts: countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running'"),
|
|
2899
|
-
headlessRetryingAttempts: queueRetrying,
|
|
2900
|
-
headlessDeadLetterAttempts: queueDeadLetter,
|
|
2901
|
-
staleHeadlessExecutionAttempts: countRows("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running' AND lease_released_at IS NULL AND lease_expires_at <= ?", options.nowIso),
|
|
2902
|
-
outbox: countRows('SELECT COUNT(*) AS count FROM outbox'),
|
|
2903
|
-
},
|
|
2904
|
-
outboxCounts,
|
|
2905
|
-
queue: {
|
|
2906
|
-
depth: queueDepth,
|
|
2907
|
-
queued: queueQueued,
|
|
2908
|
-
running: queueRunning,
|
|
2909
|
-
retrying: queueRetrying,
|
|
2910
|
-
deadLetter: queueDeadLetter,
|
|
2911
|
-
capacity: queuePolicy.maxItems,
|
|
2912
|
-
available: Math.max(0, queuePolicy.maxItems - queueDepth),
|
|
2913
|
-
backpressure: queueDepth >= queuePolicy.maxItems,
|
|
2914
|
-
generatedAt: options.nowIso,
|
|
2915
|
-
...(nextRetryAt ? { nextRetryAt } : {}),
|
|
2916
|
-
...(queueDepth >= queuePolicy.maxItems ? { reasonCode: CONTINUITY_QUEUE_REASON_CODES.CAP_REACHED } : {}),
|
|
2917
|
-
publicSafe: true,
|
|
2918
|
-
},
|
|
2919
|
-
};
|
|
2920
|
-
}
|
|
2921
|
-
listProcessingOutputSummaries(options = {}) {
|
|
2922
|
-
const limit = normalizeProcessingSummaryLimit(options.limit);
|
|
2923
|
-
const targetPredicate = processingSummaryTargetPredicate(options);
|
|
2924
|
-
const selectSql = `
|
|
560
|
+
`).all(e.jobLimit),counts:{jobs:a("SELECT COUNT(*) AS count FROM jobs"),activeJobs:a("SELECT COUNT(*) AS count FROM jobs WHERE status IN ('planned', 'active', 'running', 'waiting_external', 'waiting_human', 'backoff', 'paused', 'blocked')"),activeLeases:a("SELECT COUNT(*) AS count FROM job_leases WHERE released_at IS NULL"),dueJobs:a("SELECT COUNT(*) AS count FROM jobs WHERE next_run_at IS NOT NULL AND next_run_at <= ? AND status NOT IN ('completed', 'aborted', 'cancelled')",e.nowIso),staleLeases:a("SELECT COUNT(*) AS count FROM job_leases WHERE released_at IS NULL AND expires_at <= ?",e.nowIso),receipts:a("SELECT COUNT(*) AS count FROM job_receipts"),events:a("SELECT COUNT(*) AS count FROM job_events"),processingOutputSummaries:a("SELECT COUNT(*) AS count FROM job_events WHERE type = ?",O),goalLedgerItems:a("SELECT COUNT(*) AS count FROM goal_ledger_items"),goalVerifiers:a("SELECT COUNT(*) AS count FROM goal_verifiers"),goalReviewGates:a("SELECT COUNT(*) AS count FROM goal_review_gates"),stateSnapshots:a("SELECT COUNT(*) AS count FROM state_snapshots"),headlessExecutionAttempts:a("SELECT COUNT(*) AS count FROM headless_execution_attempts"),headlessQueuedAttempts:a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status IN ('queued', 'backoff')"),headlessRunningAttempts:a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running'"),headlessRetryingAttempts:l,headlessDeadLetterAttempts:h,staleHeadlessExecutionAttempts:a("SELECT COUNT(*) AS count FROM headless_execution_attempts WHERE status = 'running' AND lease_released_at IS NULL AND lease_expires_at <= ?",e.nowIso),outbox:a("SELECT COUNT(*) AS count FROM outbox")},outboxCounts:s,queue:{depth:f,queued:o,running:I,retrying:l,deadLetter:h,capacity:d.maxItems,available:Math.max(0,d.maxItems-f),backpressure:f>=d.maxItems,generatedAt:e.nowIso,...u?{nextRetryAt:u}:{},...f>=d.maxItems?{reasonCode:k.CAP_REACHED}:{},publicSafe:!0}}}listProcessingOutputSummaries(e={}){const a=he(e.limit),r=pe(e),s=`
|
|
2925
561
|
SELECT event_id, job_id, operation_id, summary, reason_code, created_at, metadata_json
|
|
2926
562
|
FROM job_events
|
|
2927
|
-
WHERE type = ?${
|
|
563
|
+
WHERE type = ?${r.sql}
|
|
2928
564
|
ORDER BY created_at DESC, event_id DESC
|
|
2929
565
|
LIMIT ?
|
|
2930
|
-
`;
|
|
2931
|
-
const rows = this.db.prepare(`SELECT * FROM (${selectSql}) ORDER BY created_at ASC, event_id ASC`).all(CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE, ...targetPredicate.params, limit);
|
|
2932
|
-
return rows.map(toProcessingOutputSummaryRow);
|
|
2933
|
-
}
|
|
2934
|
-
pollProcessingOutputSummaries(options = {}) {
|
|
2935
|
-
const limit = normalizeProcessingSummaryLimit(options.limit);
|
|
2936
|
-
const decodedCursor = decodeProcessingSummaryCursor(options.cursor);
|
|
2937
|
-
const targetPredicate = processingSummaryTargetPredicate(options);
|
|
2938
|
-
const cursorPredicate = decodedCursor
|
|
2939
|
-
? ' AND (created_at > ? OR (created_at = ? AND event_id > ?))'
|
|
2940
|
-
: '';
|
|
2941
|
-
const sql = `
|
|
566
|
+
`;return this.db.prepare(`SELECT * FROM (${s}) ORDER BY created_at ASC, event_id ASC`).all(O,...r.params,a).map(be)}pollProcessingOutputSummaries(e={}){const a=he(e.limit),r=lt(e.cursor),s=pe(e),n=r?" AND (created_at > ? OR (created_at = ? AND event_id > ?))":"",d=`
|
|
2942
567
|
SELECT event_id, job_id, operation_id, summary, reason_code, created_at, metadata_json
|
|
2943
568
|
FROM job_events
|
|
2944
|
-
WHERE type = ?${
|
|
569
|
+
WHERE type = ?${s.sql}${n}
|
|
2945
570
|
ORDER BY created_at ASC, event_id ASC
|
|
2946
571
|
LIMIT ?
|
|
2947
|
-
|
|
2948
|
-
const params = [CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE];
|
|
2949
|
-
params.push(...targetPredicate.params);
|
|
2950
|
-
if (decodedCursor)
|
|
2951
|
-
params.push(decodedCursor.createdAt, decodedCursor.createdAt, decodedCursor.eventId);
|
|
2952
|
-
params.push(limit + 1);
|
|
2953
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
2954
|
-
const summaries = rows.slice(0, limit).map(toProcessingOutputSummaryRow);
|
|
2955
|
-
const last = summaries[summaries.length - 1];
|
|
2956
|
-
return {
|
|
2957
|
-
summaries,
|
|
2958
|
-
...(last ? { nextCursor: last.cursor } : options.cursor ? { nextCursor: options.cursor } : {}),
|
|
2959
|
-
hasMore: rows.length > limit,
|
|
2960
|
-
publicSafe: true,
|
|
2961
|
-
};
|
|
2962
|
-
}
|
|
2963
|
-
listDueJobs(options) {
|
|
2964
|
-
const limit = Math.max(1, Math.min(50, Math.floor(options.limit)));
|
|
2965
|
-
return this.db.prepare(`
|
|
572
|
+
`,o=[O];o.push(...s.params),r&&o.push(r.createdAt,r.createdAt,r.eventId),o.push(a+1);const l=this.db.prepare(d).all(...o),I=l.slice(0,a).map(be),h=I[I.length-1];return{summaries:I,...h?{nextCursor:h.cursor}:e.cursor?{nextCursor:e.cursor}:{},hasMore:l.length>a,publicSafe:!0}}listDueJobs(e){const a=Math.max(1,Math.min(50,Math.floor(e.limit)));return this.db.prepare(`
|
|
2966
573
|
SELECT j.job_id, j.kind, j.status, j.summary, j.reason_code, j.created_at,
|
|
2967
574
|
j.updated_at, j.next_run_at, j.metadata_json,
|
|
2968
575
|
(SELECT COUNT(*)
|
|
@@ -2980,63 +587,19 @@ export class ContinuitySqliteStore {
|
|
|
2980
587
|
AND j.status NOT IN ('waiting_external', 'waiting_human', 'paused', 'blocked', 'completed', 'aborted', 'cancelled')
|
|
2981
588
|
ORDER BY j.next_run_at ASC, j.job_id ASC
|
|
2982
589
|
LIMIT ?
|
|
2983
|
-
`).all(
|
|
2984
|
-
}
|
|
2985
|
-
recoverStaleLeases(options) {
|
|
2986
|
-
this.assertCanMutate();
|
|
2987
|
-
const nowMs = Date.parse(options.nowIso);
|
|
2988
|
-
if (!Number.isFinite(nowMs)) {
|
|
2989
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Continuity SQLite stale lease recovery received an invalid timestamp.');
|
|
2990
|
-
}
|
|
2991
|
-
const staleAfterMs = Math.max(0, Math.floor(options.staleAfterMs));
|
|
2992
|
-
const limit = Math.max(1, Math.min(100, Math.floor(options.limit)));
|
|
2993
|
-
const safeExpiredAtOrBefore = new Date(nowMs - staleAfterMs).toISOString();
|
|
2994
|
-
try {
|
|
2995
|
-
return this.runInTransaction(() => {
|
|
2996
|
-
const rows = this.db.prepare(`
|
|
590
|
+
`).all(e.nowIso,a)}recoverStaleLeases(e){this.assertCanMutate();const a=Date.parse(e.nowIso);if(!Number.isFinite(a))throw new i("sqlite-public-safety-rejected","Continuity SQLite stale lease recovery received an invalid timestamp.");const r=Math.max(0,Math.floor(e.staleAfterMs)),s=Math.max(1,Math.min(100,Math.floor(e.limit))),n=new Date(a-r).toISOString();try{return this.runInTransaction(()=>{const o=this.db.prepare(`
|
|
2997
591
|
SELECT lease_id
|
|
2998
592
|
FROM job_leases
|
|
2999
593
|
WHERE released_at IS NULL
|
|
3000
594
|
AND expires_at <= ?
|
|
3001
595
|
ORDER BY expires_at ASC, lease_id ASC
|
|
3002
596
|
LIMIT ?
|
|
3003
|
-
`).all(safeExpiredAtOrBefore,
|
|
3004
|
-
const recoveredLeaseIds = rows
|
|
3005
|
-
.map((row) => row.lease_id)
|
|
3006
|
-
.filter((leaseId) => typeof leaseId === 'string' && leaseId.length > 0);
|
|
3007
|
-
if (recoveredLeaseIds.length === 0) {
|
|
3008
|
-
return { recoveredLeaseIds: [], safeExpiredAtOrBefore, changes: 0 };
|
|
3009
|
-
}
|
|
3010
|
-
const placeholders = recoveredLeaseIds.map(() => '?').join(', ');
|
|
3011
|
-
const result = this.db.prepare(`
|
|
597
|
+
`).all(n,s).map(h=>h.lease_id).filter(h=>typeof h=="string"&&h.length>0);if(o.length===0)return{recoveredLeaseIds:[],safeExpiredAtOrBefore:n,changes:0};const l=o.map(()=>"?").join(", "),I=this.db.prepare(`
|
|
3012
598
|
UPDATE job_leases
|
|
3013
599
|
SET status = 'expired', released_at = ?
|
|
3014
600
|
WHERE released_at IS NULL
|
|
3015
|
-
AND lease_id IN (${
|
|
3016
|
-
`).run(
|
|
3017
|
-
return {
|
|
3018
|
-
recoveredLeaseIds,
|
|
3019
|
-
safeExpiredAtOrBefore,
|
|
3020
|
-
changes: result.changes,
|
|
3021
|
-
};
|
|
3022
|
-
});
|
|
3023
|
-
}
|
|
3024
|
-
catch (error) {
|
|
3025
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
recoverStaleHeadlessExecutionAttempts(options) {
|
|
3029
|
-
this.assertCanMutate();
|
|
3030
|
-
const nowMs = Date.parse(options.nowIso);
|
|
3031
|
-
if (!Number.isFinite(nowMs)) {
|
|
3032
|
-
throw new ContinuitySqliteStoreError('sqlite-public-safety-rejected', 'Continuity SQLite headless stale recovery received an invalid timestamp.');
|
|
3033
|
-
}
|
|
3034
|
-
const staleAfterMs = Math.max(0, Math.floor(options.staleAfterMs));
|
|
3035
|
-
const limit = Math.max(1, Math.min(100, Math.floor(options.limit)));
|
|
3036
|
-
const safeExpiredAtOrBefore = new Date(nowMs - staleAfterMs).toISOString();
|
|
3037
|
-
try {
|
|
3038
|
-
return this.runInTransaction(() => {
|
|
3039
|
-
const rows = this.db.prepare(`
|
|
601
|
+
AND lease_id IN (${l})
|
|
602
|
+
`).run(e.nowIso,...o);return{recoveredLeaseIds:o,safeExpiredAtOrBefore:n,changes:I.changes}})}catch(d){throw m(d,"sqlite-mutation-failed")}}recoverStaleHeadlessExecutionAttempts(e){this.assertCanMutate();const a=Date.parse(e.nowIso);if(!Number.isFinite(a))throw new i("sqlite-public-safety-rejected","Continuity SQLite headless stale recovery received an invalid timestamp.");const r=Math.max(0,Math.floor(e.staleAfterMs)),s=Math.max(1,Math.min(100,Math.floor(e.limit))),n=new Date(a-r).toISOString();try{return this.runInTransaction(()=>{const d=this.db.prepare(`
|
|
3040
603
|
SELECT attempt_id, retry_count, max_attempts, attempt_count
|
|
3041
604
|
FROM headless_execution_attempts
|
|
3042
605
|
WHERE status = 'running'
|
|
@@ -3044,30 +607,7 @@ export class ContinuitySqliteStore {
|
|
|
3044
607
|
AND lease_expires_at <= ?
|
|
3045
608
|
ORDER BY lease_expires_at ASC, attempt_id ASC
|
|
3046
609
|
LIMIT ?
|
|
3047
|
-
`).all(
|
|
3048
|
-
const retryableAttempts = [];
|
|
3049
|
-
const deadLetteredAttemptIds = [];
|
|
3050
|
-
const policy = normalizeContinuityQueuePolicy(options.policy);
|
|
3051
|
-
for (const row of rows) {
|
|
3052
|
-
if (typeof row.attempt_id !== 'string' || row.attempt_id.length === 0)
|
|
3053
|
-
continue;
|
|
3054
|
-
const retryCount = typeof row.retry_count === 'number' ? row.retry_count : 0;
|
|
3055
|
-
const maxAttempts = typeof row.max_attempts === 'number' ? row.max_attempts : 1;
|
|
3056
|
-
const attemptCount = typeof row.attempt_count === 'number' ? row.attempt_count : 0;
|
|
3057
|
-
const consumedAttempts = Math.max(attemptCount, retryCount + 1);
|
|
3058
|
-
if (consumedAttempts >= maxAttempts) {
|
|
3059
|
-
deadLetteredAttemptIds.push(row.attempt_id);
|
|
3060
|
-
}
|
|
3061
|
-
else {
|
|
3062
|
-
retryableAttempts.push({
|
|
3063
|
-
attemptId: row.attempt_id,
|
|
3064
|
-
nextRetryAt: computeContinuityQueueRetryAt(options.nowIso, retryCount + 1, policy),
|
|
3065
|
-
});
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
let changes = 0;
|
|
3069
|
-
for (const retryable of retryableAttempts) {
|
|
3070
|
-
changes += this.db.prepare(`
|
|
610
|
+
`).all(n,s),o=[],l=[],I=B(e.policy);for(const _ of d){if(typeof _.attempt_id!="string"||_.attempt_id.length===0)continue;const u=typeof _.retry_count=="number"?_.retry_count:0,p=typeof _.max_attempts=="number"?_.max_attempts:1,x=typeof _.attempt_count=="number"?_.attempt_count:0;Math.max(x,u+1)>=p?l.push(_.attempt_id):o.push({attemptId:_.attempt_id,nextRetryAt:we(e.nowIso,u+1,I)})}let h=0;for(const _ of o)h+=this.db.prepare(`
|
|
3071
611
|
UPDATE headless_execution_attempts
|
|
3072
612
|
SET status = 'backoff',
|
|
3073
613
|
retry_count = retry_count + 1,
|
|
@@ -3078,11 +618,7 @@ export class ContinuitySqliteStore {
|
|
|
3078
618
|
WHERE attempt_id = ?
|
|
3079
619
|
AND status = 'running'
|
|
3080
620
|
AND lease_released_at IS NULL
|
|
3081
|
-
`).run(
|
|
3082
|
-
}
|
|
3083
|
-
if (deadLetteredAttemptIds.length > 0) {
|
|
3084
|
-
const placeholders = deadLetteredAttemptIds.map(() => '?').join(', ');
|
|
3085
|
-
changes += this.db.prepare(`
|
|
621
|
+
`).run(k.LEASE_TIMEOUT_RETRY,e.nowIso,_.nextRetryAt,_.nextRetryAt,_.attemptId).changes;if(l.length>0){const _=l.map(()=>"?").join(", ");h+=this.db.prepare(`
|
|
3086
622
|
UPDATE headless_execution_attempts
|
|
3087
623
|
SET status = 'failed',
|
|
3088
624
|
retry_count = retry_count + 1,
|
|
@@ -3092,85 +628,18 @@ export class ContinuitySqliteStore {
|
|
|
3092
628
|
dead_lettered_at = ?,
|
|
3093
629
|
dead_letter_reason_code = ?,
|
|
3094
630
|
next_retry_at = NULL
|
|
3095
|
-
WHERE attempt_id IN (${
|
|
631
|
+
WHERE attempt_id IN (${_})
|
|
3096
632
|
AND status = 'running'
|
|
3097
633
|
AND lease_released_at IS NULL
|
|
3098
|
-
`).run(
|
|
3099
|
-
}
|
|
3100
|
-
const retryingAttemptIds = retryableAttempts.map((attempt) => attempt.attemptId);
|
|
3101
|
-
return {
|
|
3102
|
-
recoveredAttemptIds: [...retryingAttemptIds, ...deadLetteredAttemptIds],
|
|
3103
|
-
failedAttemptIds: deadLetteredAttemptIds,
|
|
3104
|
-
retryingAttemptIds,
|
|
3105
|
-
deadLetteredAttemptIds,
|
|
3106
|
-
safeExpiredAtOrBefore,
|
|
3107
|
-
changes,
|
|
3108
|
-
publicSafe: true,
|
|
3109
|
-
};
|
|
3110
|
-
});
|
|
3111
|
-
}
|
|
3112
|
-
catch (error) {
|
|
3113
|
-
throw mapSqliteError(error, 'sqlite-mutation-failed');
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
ensureGoalCampaign(campaignId, jobId, createdAt, updatedAt) {
|
|
3117
|
-
this.db.prepare(`
|
|
634
|
+
`).run(k.ATTEMPTS_EXHAUSTED,e.nowIso,e.nowIso,e.nowIso,k.ATTEMPTS_EXHAUSTED,...l).changes}const f=o.map(_=>_.attemptId);return{recoveredAttemptIds:[...f,...l],failedAttemptIds:l,retryingAttemptIds:f,deadLetteredAttemptIds:l,safeExpiredAtOrBefore:n,changes:h,publicSafe:!0}})}catch(d){throw m(d,"sqlite-mutation-failed")}}ensureGoalCampaign(e,a,r,s){this.db.prepare(`
|
|
3118
635
|
INSERT OR IGNORE INTO goal_campaigns (
|
|
3119
636
|
campaign_id, job_id, status, public_summary, created_at, updated_at
|
|
3120
637
|
) VALUES (?, ?, 'active', 'Public goal campaign summary unavailable', ?, ?)
|
|
3121
|
-
`).run(
|
|
3122
|
-
}
|
|
3123
|
-
configureConnection() {
|
|
3124
|
-
const journalMode = String(asSinglePragmaValue(this.db.pragma('journal_mode = WAL', { simple: true })) ?? '').toLowerCase();
|
|
3125
|
-
if (journalMode !== 'wal') {
|
|
3126
|
-
throw new ContinuitySqliteStoreError('sqlite-migration-failed', `Continuity SQLite journal_mode did not switch to WAL (observed ${journalMode || 'unknown'}).`);
|
|
3127
|
-
}
|
|
3128
|
-
this.db.pragma('foreign_keys = ON');
|
|
3129
|
-
this.db.pragma(`busy_timeout = ${this.busyTimeoutMs}`);
|
|
3130
|
-
const foreignKeys = Number(asSinglePragmaValue(this.db.pragma('foreign_keys', { simple: true })) ?? 0);
|
|
3131
|
-
if (foreignKeys !== 1) {
|
|
3132
|
-
throw new ContinuitySqliteStoreError('sqlite-migration-failed', 'Continuity SQLite foreign_keys pragma is not enabled.');
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
ensureMigrationTable() {
|
|
3136
|
-
this.db.exec(`
|
|
638
|
+
`).run(e,a,r,s)}configureConnection(){const e=String(U(this.db.pragma("journal_mode = WAL",{simple:!0}))??"").toLowerCase();if(e!=="wal")throw new i("sqlite-migration-failed",`Continuity SQLite journal_mode did not switch to WAL (observed ${e||"unknown"}).`);if(this.db.pragma("foreign_keys = ON"),this.db.pragma(`busy_timeout = ${this.busyTimeoutMs}`),Number(U(this.db.pragma("foreign_keys",{simple:!0}))??0)!==1)throw new i("sqlite-migration-failed","Continuity SQLite foreign_keys pragma is not enabled.")}ensureMigrationTable(){this.db.exec(`
|
|
3137
639
|
CREATE TABLE IF NOT EXISTS continuity_migrations (
|
|
3138
640
|
version INTEGER PRIMARY KEY,
|
|
3139
641
|
name TEXT NOT NULL,
|
|
3140
642
|
checksum TEXT NOT NULL,
|
|
3141
643
|
applied_at TEXT NOT NULL
|
|
3142
644
|
);
|
|
3143
|
-
`);
|
|
3144
|
-
}
|
|
3145
|
-
applyMigrations() {
|
|
3146
|
-
const apply = this.db.transaction(() => {
|
|
3147
|
-
for (const migration of CONTINUITY_MIGRATIONS) {
|
|
3148
|
-
const checksum = checksumMigration(migration.sql);
|
|
3149
|
-
const existing = this.db.prepare('SELECT version, name, checksum FROM continuity_migrations WHERE version = ?').get(migration.version);
|
|
3150
|
-
if (existing) {
|
|
3151
|
-
if (existing.checksum !== checksum || existing.name !== migration.name) {
|
|
3152
|
-
throw new ContinuitySqliteStoreError('sqlite-migration-conflict', `Continuity SQLite migration ${migration.version} checksum conflict.`);
|
|
3153
|
-
}
|
|
3154
|
-
continue;
|
|
3155
|
-
}
|
|
3156
|
-
this.db.exec(migration.sql);
|
|
3157
|
-
this.db.prepare('INSERT INTO continuity_migrations (version, name, checksum, applied_at) VALUES (?, ?, ?, ?)').run(migration.version, migration.name, checksum, this.now());
|
|
3158
|
-
this.db.pragma(`user_version = ${migration.version}`);
|
|
3159
|
-
}
|
|
3160
|
-
});
|
|
3161
|
-
apply();
|
|
3162
|
-
}
|
|
3163
|
-
assertCanMutate() {
|
|
3164
|
-
if (this.mutationBlocked) {
|
|
3165
|
-
throw new ContinuitySqliteStoreError('sqlite-mutation-blocked', 'Continuity SQLite mutation is blocked after integrity failure.', { mutationBlocked: true });
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3168
|
-
toWriteResult(result) {
|
|
3169
|
-
const inserted = result.changes > 0;
|
|
3170
|
-
return {
|
|
3171
|
-
inserted,
|
|
3172
|
-
replay: !inserted,
|
|
3173
|
-
changes: result.changes,
|
|
3174
|
-
};
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
645
|
+
`)}applyMigrations(){this.db.transaction(()=>{for(const a of Je){const r=st(a.sql),s=this.db.prepare("SELECT version, name, checksum FROM continuity_migrations WHERE version = ?").get(a.version);if(s){if(s.checksum!==r||s.name!==a.name)throw new i("sqlite-migration-conflict",`Continuity SQLite migration ${a.version} checksum conflict.`);continue}this.db.exec(a.sql),this.db.prepare("INSERT INTO continuity_migrations (version, name, checksum, applied_at) VALUES (?, ?, ?, ?)").run(a.version,a.name,r,this.now()),this.db.pragma(`user_version = ${a.version}`)}})()}assertCanMutate(){if(this.mutationBlocked)throw new i("sqlite-mutation-blocked","Continuity SQLite mutation is blocked after integrity failure.",{mutationBlocked:!0})}toWriteResult(e){const a=e.changes>0;return{inserted:a,replay:!a,changes:e.changes}}}export{O as CONTINUITY_PROCESSING_OUTPUT_SUMMARY_EVENT_TYPE,Be as CONTINUITY_PROCESSING_OUTPUT_SUMMARY_MAX_CHARS,Ye as CONTINUITY_PUBLIC_STAGES,We as CONTINUITY_SQLITE_FILENAME,ne as CONTINUITY_SQLITE_RELATIVE_DIR,Dt as CONTINUITY_SQLITE_SCHEMA_VERSION,ce as CONTINUITY_STAGE_HISTORY_MAX,Ct as ContinuitySqliteStore,i as ContinuitySqliteStoreError,de as DEFAULT_CONTINUITY_PROCESSING_SUMMARY_LIMIT,Fe as DEFAULT_CONTINUITY_SQLITE_BUSY_TIMEOUT_MS,$e as MAX_CONTINUITY_PROCESSING_SUMMARY_LIMIT,ie as SQLITE_ADAPTER_INSTALL_GUIDANCE,Ie as continuitySqliteStorePath,Ut as inspectEpicOperationalSqliteBinding,le as isContinuityPublicStage,Et as loadBetterSqlite3Adapter,Ge as normalizeContinuityStageIteration,ft as openContinuitySqliteReadOnly,wt as openContinuitySqliteStore,Qe as sanitizeContinuityStageHistory,ue as sanitizeContinuityStageTimestamp};
|