@cleocode/core 2026.9.24 → 2026.9.26
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/admin/import-tasks.d.ts.map +1 -1
- package/dist/admin/import-tasks.js +5 -2
- package/dist/admin/import-tasks.js.map +1 -1
- package/dist/admin/import.d.ts.map +1 -1
- package/dist/admin/import.js +7 -2
- package/dist/admin/import.js.map +1 -1
- package/dist/audit-prune.d.ts +32 -0
- package/dist/audit-prune.d.ts.map +1 -1
- package/dist/audit-prune.js +37 -3
- package/dist/audit-prune.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/crypto.d.ts +1 -2
- package/dist/cloud/crypto.d.ts.map +1 -1
- package/dist/cloud/crypto.js +1 -11
- package/dist/cloud/crypto.js.map +1 -1
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.d.ts.map +1 -1
- package/dist/cloud/index.js +1 -0
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/nexus-auth.d.ts +26 -1
- package/dist/cloud/nexus-auth.d.ts.map +1 -1
- package/dist/cloud/nexus-auth.js +61 -32
- package/dist/cloud/nexus-auth.js.map +1 -1
- package/dist/cloud/nexus-credentials.js +2 -2
- package/dist/cloud/nexus-credentials.js.map +1 -1
- package/dist/cloud/nexus-device.d.ts +809 -0
- package/dist/cloud/nexus-device.d.ts.map +1 -0
- package/dist/cloud/nexus-device.js +1680 -0
- package/dist/cloud/nexus-device.js.map +1 -0
- package/dist/cloud/nexus-enrol.d.ts +210 -0
- package/dist/cloud/nexus-enrol.d.ts.map +1 -0
- package/dist/cloud/nexus-enrol.js +1023 -0
- package/dist/cloud/nexus-enrol.js.map +1 -0
- package/dist/cloud/nexus-link.d.ts +3 -0
- package/dist/cloud/nexus-link.d.ts.map +1 -1
- package/dist/cloud/nexus-link.js +38 -4
- package/dist/cloud/nexus-link.js.map +1 -1
- package/dist/cloud/signing.d.ts +20 -0
- package/dist/cloud/signing.d.ts.map +1 -1
- package/dist/cloud/signing.js +14 -0
- package/dist/cloud/signing.js.map +1 -1
- package/dist/cloud/uuidv7.d.ts +9 -0
- package/dist/cloud/uuidv7.d.ts.map +1 -0
- package/dist/cloud/uuidv7.js +19 -0
- package/dist/cloud/uuidv7.js.map +1 -0
- package/dist/crypto/credentials.d.ts +61 -0
- package/dist/crypto/credentials.d.ts.map +1 -1
- package/dist/crypto/credentials.js +154 -58
- package/dist/crypto/credentials.js.map +1 -1
- package/dist/decide/bench/index.d.ts +1 -1
- package/dist/decide/bench/index.d.ts.map +1 -1
- package/dist/decide/bench/index.js +1 -1
- package/dist/decide/bench/index.js.map +1 -1
- package/dist/decide/bench/operation.d.ts +1 -1
- package/dist/decide/bench/operation.d.ts.map +1 -1
- package/dist/decide/bench/operation.js +1 -1
- package/dist/decide/bench/operation.js.map +1 -1
- package/dist/decide/bench/profiles.d.ts +50 -22
- package/dist/decide/bench/profiles.d.ts.map +1 -1
- package/dist/decide/bench/profiles.js +103 -59
- package/dist/decide/bench/profiles.js.map +1 -1
- package/dist/decide/bench/report.d.ts.map +1 -1
- package/dist/decide/bench/report.js +6 -1
- package/dist/decide/bench/report.js.map +1 -1
- package/dist/decide/bench/runner.d.ts +6 -1
- package/dist/decide/bench/runner.d.ts.map +1 -1
- package/dist/decide/bench/runner.js +1 -0
- package/dist/decide/bench/runner.js.map +1 -1
- package/dist/decide/client.d.ts.map +1 -1
- package/dist/decide/client.js +11 -5
- package/dist/decide/client.js.map +1 -1
- package/dist/decide/jev-wire.d.ts +31 -1
- package/dist/decide/jev-wire.d.ts.map +1 -1
- package/dist/decide/jev-wire.js +61 -7
- package/dist/decide/jev-wire.js.map +1 -1
- package/dist/docs/export-document.js +12907 -10622
- package/dist/docs/export-document.js.map +3 -3
- package/dist/doctor/dependency-cycles.d.ts +41 -0
- package/dist/doctor/dependency-cycles.d.ts.map +1 -0
- package/dist/doctor/dependency-cycles.js +39 -0
- package/dist/doctor/dependency-cycles.js.map +1 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/orphan-ac-bindings.d.ts +64 -0
- package/dist/doctor/orphan-ac-bindings.d.ts.map +1 -0
- package/dist/doctor/orphan-ac-bindings.js +59 -0
- package/dist/doctor/orphan-ac-bindings.js.map +1 -0
- package/dist/doctor/projects.d.ts.map +1 -1
- package/dist/doctor/projects.js +43 -29
- package/dist/doctor/projects.js.map +1 -1
- package/dist/doctor/row-identity.d.ts +42 -0
- package/dist/doctor/row-identity.d.ts.map +1 -0
- package/dist/doctor/row-identity.js +151 -0
- package/dist/doctor/row-identity.js.map +1 -0
- package/dist/doctor/twin-collapse.d.ts +58 -2
- package/dist/doctor/twin-collapse.d.ts.map +1 -1
- package/dist/doctor/twin-collapse.js +212 -14
- package/dist/doctor/twin-collapse.js.map +1 -1
- package/dist/error-catalog.d.ts.map +1 -1
- package/dist/error-catalog.js +4 -0
- package/dist/error-catalog.js.map +1 -1
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/error-registry.js +8 -0
- package/dist/error-registry.js.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.js +10 -0
- package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
- package/dist/llm/plugin-facade.js +17767 -15482
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +19 -10
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manifest-identity.d.ts +171 -0
- package/dist/memory/manifest-identity.d.ts.map +1 -0
- package/dist/memory/manifest-identity.js +345 -0
- package/dist/memory/manifest-identity.js.map +1 -0
- package/dist/memory/mental-model-queue.d.ts +14 -4
- package/dist/memory/mental-model-queue.d.ts.map +1 -1
- package/dist/memory/mental-model-queue.js +64 -35
- package/dist/memory/mental-model-queue.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +47 -5
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +130 -14
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +2 -0
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/transcript-scanner.js +1 -1
- package/dist/nexus/deps.d.ts.map +1 -1
- package/dist/nexus/deps.js +7 -14
- package/dist/nexus/deps.js.map +1 -1
- package/dist/nexus/discover.d.ts.map +1 -1
- package/dist/nexus/discover.js +6 -9
- package/dist/nexus/discover.js.map +1 -1
- package/dist/nexus/git-state.d.ts.map +1 -1
- package/dist/nexus/git-state.js +10 -1
- package/dist/nexus/git-state.js.map +1 -1
- package/dist/nexus/projects-clean.d.ts.map +1 -1
- package/dist/nexus/projects-clean.js +22 -11
- package/dist/nexus/projects-clean.js.map +1 -1
- package/dist/nexus/projects-scan.d.ts.map +1 -1
- package/dist/nexus/projects-scan.js +8 -13
- package/dist/nexus/projects-scan.js.map +1 -1
- package/dist/nexus/query.d.ts.map +1 -1
- package/dist/nexus/query.js +4 -12
- package/dist/nexus/query.js.map +1 -1
- package/dist/nexus/registry-roots.d.ts +5 -2
- package/dist/nexus/registry-roots.d.ts.map +1 -1
- package/dist/nexus/registry-roots.js +8 -10
- package/dist/nexus/registry-roots.js.map +1 -1
- package/dist/nexus/registry.d.ts +18 -0
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +37 -16
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/workspace.d.ts.map +1 -1
- package/dist/nexus/workspace.js +54 -4
- package/dist/nexus/workspace.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +1 -1
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/pivot.d.ts.map +1 -1
- package/dist/orchestrate/pivot.js +4 -2
- package/dist/orchestrate/pivot.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +3 -3
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.js +1 -1
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestrate/worktree-complete.d.ts.map +1 -1
- package/dist/orchestrate/worktree-complete.js +40 -2
- package/dist/orchestrate/worktree-complete.js.map +1 -1
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +3 -3
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/parallel.js +1 -1
- package/dist/orchestration/parallel.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts +26 -0
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +62 -91
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/wave-topic.d.ts +4 -2
- package/dist/orchestration/wave-topic.d.ts.map +1 -1
- package/dist/orchestration/wave-topic.js +6 -4
- package/dist/orchestration/wave-topic.js.map +1 -1
- package/dist/orchestration/waves.d.ts +2 -1
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +4 -3
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +4 -2
- package/dist/phases/index.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +190 -0
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +436 -4
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/sdk/validator-ac-pull.d.ts.map +1 -1
- package/dist/sdk/validator-ac-pull.js +2 -1
- package/dist/sdk/validator-ac-pull.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +12807 -10522
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/cross-project-hygiene.d.ts +5 -0
- package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
- package/dist/sentient/cross-project-hygiene.js +7 -1
- package/dist/sentient/cross-project-hygiene.js.map +1 -1
- package/dist/sentient/propose-tick.d.ts.map +1 -1
- package/dist/sentient/propose-tick.js +2 -3
- package/dist/sentient/propose-tick.js.map +1 -1
- package/dist/sentient/stage-drift-tick.d.ts.map +1 -1
- package/dist/sentient/stage-drift-tick.js +2 -3
- package/dist/sentient/stage-drift-tick.js.map +1 -1
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +77 -49
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/focus-state-store.d.ts +69 -23
- package/dist/sessions/focus-state-store.d.ts.map +1 -1
- package/dist/sessions/focus-state-store.js +113 -27
- package/dist/sessions/focus-state-store.js.map +1 -1
- package/dist/sessions/handoff.js +1 -1
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +24 -13
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/terminal-identity.d.ts +165 -17
- package/dist/sessions/terminal-identity.d.ts.map +1 -1
- package/dist/sessions/terminal-identity.js +341 -32
- package/dist/sessions/terminal-identity.js.map +1 -1
- package/dist/skills/manifests/research.js +2 -0
- package/dist/skills/manifests/research.js.map +1 -1
- package/dist/skills/orchestrator/startup.js +2 -2
- package/dist/skills/orchestrator/startup.js.map +1 -1
- package/dist/skills/orchestrator/validator.d.ts.map +1 -1
- package/dist/skills/orchestrator/validator.js +9 -2
- package/dist/skills/orchestrator/validator.js.map +1 -1
- package/dist/skills/validation.d.ts +4 -1
- package/dist/skills/validation.d.ts.map +1 -1
- package/dist/skills/validation.js +11 -3
- package/dist/skills/validation.js.map +1 -1
- package/dist/snapshot/index.d.ts +4 -0
- package/dist/snapshot/index.d.ts.map +1 -1
- package/dist/snapshot/index.js +50 -3
- package/dist/snapshot/index.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts +111 -7
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +296 -8
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/store/ac-binding-prune.d.ts +144 -0
- package/dist/store/ac-binding-prune.d.ts.map +1 -0
- package/dist/store/ac-binding-prune.js +293 -0
- package/dist/store/ac-binding-prune.js.map +1 -0
- package/dist/store/agent-registry-store.d.ts.map +1 -1
- package/dist/store/agent-registry-store.js +3 -2
- package/dist/store/agent-registry-store.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +85 -2
- package/dist/store/backup-sidecar.d.ts.map +1 -1
- package/dist/store/backup-sidecar.js +173 -1
- package/dist/store/backup-sidecar.js.map +1 -1
- package/dist/store/conduit-sqlite.d.ts.map +1 -1
- package/dist/store/conduit-sqlite.js +3 -2
- package/dist/store/conduit-sqlite.js.map +1 -1
- package/dist/store/credential-transfer.d.ts.map +1 -1
- package/dist/store/credential-transfer.js +3 -0
- package/dist/store/credential-transfer.js.map +1 -1
- package/dist/store/db-helpers.d.ts +32 -13
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +144 -40
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dependency-cycles.d.ts +164 -0
- package/dist/store/dependency-cycles.d.ts.map +1 -0
- package/dist/store/dependency-cycles.js +360 -0
- package/dist/store/dependency-cycles.js.map +1 -0
- package/dist/store/display-id-alias.d.ts +585 -0
- package/dist/store/display-id-alias.d.ts.map +1 -0
- package/dist/store/display-id-alias.js +1538 -0
- package/dist/store/display-id-alias.js.map +1 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +48 -0
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +8 -1
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/reconcile.d.ts.map +1 -1
- package/dist/store/exodus/reconcile.js +7 -2
- package/dist/store/exodus/reconcile.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts.map +1 -1
- package/dist/store/exodus/recovery.js +20 -1
- package/dist/store/exodus/recovery.js.map +1 -1
- package/dist/store/global-salt.d.ts +35 -0
- package/dist/store/global-salt.d.ts.map +1 -1
- package/dist/store/global-salt.js +136 -16
- package/dist/store/global-salt.js.map +1 -1
- package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
- package/dist/store/legacy-tasks-lineage.js +19 -2
- package/dist/store/legacy-tasks-lineage.js.map +1 -1
- package/dist/store/migration-sqlite.d.ts.map +1 -1
- package/dist/store/migration-sqlite.js +19 -11
- package/dist/store/migration-sqlite.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +6 -0
- package/dist/store/portable-bundle-scan.js.map +1 -1
- package/dist/store/row-identity-flag.d.ts +26 -0
- package/dist/store/row-identity-flag.d.ts.map +1 -0
- package/dist/store/row-identity-flag.js +31 -0
- package/dist/store/row-identity-flag.js.map +1 -0
- package/dist/store/row-identity-registry.d.ts +61 -0
- package/dist/store/row-identity-registry.d.ts.map +1 -0
- package/dist/store/row-identity-registry.js +206 -0
- package/dist/store/row-identity-registry.js.map +1 -0
- package/dist/store/row-identity.d.ts +480 -0
- package/dist/store/row-identity.d.ts.map +1 -0
- package/dist/store/row-identity.js +1509 -0
- package/dist/store/row-identity.js.map +1 -0
- package/dist/store/safety-data-accessor.d.ts +5 -2
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +9 -2
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/schema/attachments.d.ts +1 -1
- package/dist/store/schema/audit.d.ts +4 -4
- package/dist/store/schema/background-jobs.d.ts +1 -1
- package/dist/store/schema/cleo-global/nexus.d.ts +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +2 -2
- package/dist/store/schema/cleo-project/audit.d.ts +4 -4
- package/dist/store/schema/cleo-project/docs.d.ts +1 -1
- package/dist/store/schema/cleo-project/lifecycle.d.ts +4 -4
- package/dist/store/schema/cleo-project/nexus-graph.d.ts +3 -3
- package/dist/store/schema/cleo-project/provenance-commits.d.ts +2 -2
- package/dist/store/schema/cleo-project/provenance-rest.d.ts +3 -3
- package/dist/store/schema/cleo-project/runtime.d.ts +3 -3
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts +87 -1
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core-batch2.js +35 -0
- package/dist/store/schema/cleo-project/tasks-core-batch2.js.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +183 -7
- package/dist/store/schema/cleo-project/tasks-core.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.js +52 -1
- package/dist/store/schema/cleo-project/tasks-core.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +6 -6
- package/dist/store/schema/lifecycle.d.ts +4 -4
- package/dist/store/schema/manifest.d.ts +1 -1
- package/dist/store/schema/memory-schema.d.ts +5 -5
- package/dist/store/schema/nexus-schema.d.ts +3 -3
- package/dist/store/schema/provenance/releases.d.ts +1 -1
- package/dist/store/schema/skills-schema.d.ts +2 -2
- package/dist/store/schema/tasks.d.ts +6 -6
- package/dist/store/session-store.d.ts +63 -3
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +121 -8
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/split-brain-import.d.ts +2 -14
- package/dist/store/split-brain-import.d.ts.map +1 -1
- package/dist/store/split-brain-import.js +40 -19
- package/dist/store/split-brain-import.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +326 -2
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +782 -49
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sync/clock-store.d.ts +67 -0
- package/dist/store/sync/clock-store.d.ts.map +1 -0
- package/dist/store/sync/clock-store.js +117 -0
- package/dist/store/sync/clock-store.js.map +1 -0
- package/dist/store/sync/flags.d.ts +51 -0
- package/dist/store/sync/flags.d.ts.map +1 -0
- package/dist/store/sync/flags.js +81 -0
- package/dist/store/sync/flags.js.map +1 -0
- package/dist/store/sync/hlc.d.ts +115 -0
- package/dist/store/sync/hlc.d.ts.map +1 -0
- package/dist/store/sync/hlc.js +172 -0
- package/dist/store/sync/hlc.js.map +1 -0
- package/dist/store/sync/index.d.ts +20 -0
- package/dist/store/sync/index.d.ts.map +1 -0
- package/dist/store/sync/index.js +20 -0
- package/dist/store/sync/index.js.map +1 -0
- package/dist/store/sync/replica-registry.d.ts +85 -0
- package/dist/store/sync/replica-registry.d.ts.map +1 -0
- package/dist/store/sync/replica-registry.js +147 -0
- package/dist/store/sync/replica-registry.js.map +1 -0
- package/dist/store/sync/replica.d.ts +181 -0
- package/dist/store/sync/replica.d.ts.map +1 -0
- package/dist/store/sync/replica.js +305 -0
- package/dist/store/sync/replica.js.map +1 -0
- package/dist/store/sync/schema.d.ts +68 -0
- package/dist/store/sync/schema.d.ts.map +1 -0
- package/dist/store/sync/schema.js +130 -0
- package/dist/store/sync/schema.js.map +1 -0
- package/dist/store/sync/skew-hold.d.ts +71 -0
- package/dist/store/sync/skew-hold.d.ts.map +1 -0
- package/dist/store/sync/skew-hold.js +89 -0
- package/dist/store/sync/skew-hold.js.map +1 -0
- package/dist/store/table-classification.d.ts +10 -8
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +93 -25
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +23 -3
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/twin-collapse.d.ts +113 -2
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +247 -19
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +5 -2
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +9 -2
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +241 -49
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/health.d.ts +2 -0
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +57 -0
- package/dist/system/health.js.map +1 -1
- package/dist/system/project-health.d.ts +9 -2
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +8 -3
- package/dist/system/project-health.js.map +1 -1
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +9 -6
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +3 -0
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/ac-table.d.ts +35 -7
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +119 -14
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +48 -2
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/analyze.d.ts.map +1 -1
- package/dist/tasks/analyze.js +3 -2
- package/dist/tasks/analyze.js.map +1 -1
- package/dist/tasks/backfill-child-projections.js +47 -6
- package/dist/tasks/backfill-child-projections.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +6 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/completion-evaluation.d.ts.map +1 -1
- package/dist/tasks/completion-evaluation.js +3 -0
- package/dist/tasks/completion-evaluation.js.map +1 -1
- package/dist/tasks/evidence.d.ts +19 -1
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +93 -21
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/infer-add-params.js +1 -1
- package/dist/tasks/infer-add-params.js.map +1 -1
- package/dist/tasks/task-context.d.ts.map +1 -1
- package/dist/tasks/task-context.js +3 -1
- package/dist/tasks/task-context.js.map +1 -1
- package/dist/tasks/task-import.d.ts.map +1 -1
- package/dist/tasks/task-import.js +11 -3
- package/dist/tasks/task-import.js.map +1 -1
- package/dist/tasks/task-next.d.ts +5 -1
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +7 -2
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/validation/compliance.d.ts +5 -1
- package/dist/validation/compliance.d.ts.map +1 -1
- package/dist/validation/compliance.js +24 -3
- package/dist/validation/compliance.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +3 -2
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/protocol-common.d.ts +47 -2
- package/dist/validation/protocol-common.d.ts.map +1 -1
- package/dist/validation/protocol-common.js +86 -21
- package/dist/validation/protocol-common.js.map +1 -1
- package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
- package/dist/workgraph/scaffold-apply.js +14 -5
- package/dist/workgraph/scaffold-apply.js.map +1 -1
- package/migrations/README.md +12 -0
- package/migrations/drizzle-cleo-project/20260928120000_t12341-row-uids/migration.sql +169 -0
- package/migrations/drizzle-cleo-project/20260930160000_t12886-dependency-cycle-guard/migration.sql +76 -0
- package/migrations/drizzle-cleo-project/20260930160000_t12886-dependency-cycle-guard/revert.sql +7 -0
- package/migrations/sync-journal/20260929140000_t12342-sync-clock/migration.sql +42 -0
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +2 -2
- package/templates/CLEO-REFERENCE.md +5 -1
|
@@ -16,16 +16,21 @@ import { ARCHIVE_REASON_TOMBSTONE, ExitCode, } from '@cleocode/contracts';
|
|
|
16
16
|
import { and, eq, getTableColumns, inArray, isNull, like, lte, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
17
17
|
import { CleoError } from '../errors.js';
|
|
18
18
|
import { captureProjectScope as captureTaskAccessorScope, getProjectRoot, worktreeScope, } from '../project-scope.js';
|
|
19
|
+
import { acTextHash, buildAcRowId } from '../tasks/ac-table.js';
|
|
20
|
+
import { generateAuditLogId, pruneAcBindingsForAcIds, pruneAcBindingsForTask, pruneOrphanAcBindings, selectOrphanAcBindings, } from './ac-binding-prune.js';
|
|
19
21
|
import { withBackgroundOpCommitBoundary } from './background-ops.js';
|
|
20
22
|
import { archivedTaskToRow, rowToSession, rowToTask, taskToRow } from './converters.js';
|
|
21
23
|
import { cleanupBrainRefsOnSessionDelete } from './cross-db-cleanup.js';
|
|
22
24
|
import { batchUpdateDependencies, insertNewTask, loadDependenciesForTasks, loadRelationsForTasks, parseLabels, updateDependencies, updateTaskLabels, upsertSession, upsertTask, } from './db-helpers.js';
|
|
25
|
+
import { fillRowUids, parseStoreTimestamp, registerRowIdentityWriters, rowIdentityShared, } from './row-identity.js';
|
|
26
|
+
import { rowUidFillEnabled } from './row-identity-flag.js';
|
|
27
|
+
import { ROW_IDENTITY } from './row-identity-registry.js';
|
|
23
28
|
import { tasksAuditLog } from './schema/cleo-project/audit.js';
|
|
24
29
|
import { resolveCurrentSession } from './session-store.js';
|
|
25
30
|
import { closeDb, getDb, getNativeTasksDb } from './sqlite.js';
|
|
26
31
|
import { TERMINAL_TASK_STATUSES } from './status-registry.js';
|
|
27
32
|
import { claimAllows, claimColumnsOf, claimFromColumns, isClaimExpired, leaseExpiresAtFrom, taskClaimedError, taskClaimLeaseMs, } from './task-claim.js';
|
|
28
|
-
import { nextTaskVersion, taskConflictError, taskVersion } from './task-version.js';
|
|
33
|
+
import { nextTaskVersion, taskConflictError, taskVersion, } from './task-version.js';
|
|
29
34
|
import * as schema from './tasks-schema.js';
|
|
30
35
|
import { assertTwinCollapseWritable, bareCounterOf, mirrorCounterToBare } from './twin-collapse.js';
|
|
31
36
|
import { runHeartbeatWrite, withWriteRetry } from './with-retry.js';
|
|
@@ -37,13 +42,67 @@ import { runHeartbeatWrite, withWriteRetry } from './with-retry.js';
|
|
|
37
42
|
*/
|
|
38
43
|
let _txSavepointCounter = 0;
|
|
39
44
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
45
|
+
* Evidence bindings for a set of CURRENT AC ids (T12341).
|
|
46
|
+
*
|
|
47
|
+
* A binding is matched through the criterion's uid (`ac_uid`), which survives
|
|
48
|
+
* an AC edit, and falls back to `ac_id` for a binding whose uid is not filled.
|
|
49
|
+
* A binding recorded against an older text of a criterion is reported under
|
|
50
|
+
* the criterion's current id, so callers keyed by AC id keep working, and is
|
|
51
|
+
* marked `stale` when the text hash it recorded (`ac_text_hash`) differs from
|
|
52
|
+
* the criterion's current text: evidence for different text never satisfies a
|
|
53
|
+
* gate (spec §8.2).
|
|
42
54
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
async function selectAcBindings(db, acIds) {
|
|
56
|
+
if (acIds.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const b = schema.evidenceAcBindings;
|
|
59
|
+
const ac = schema.taskAcceptanceCriteria;
|
|
60
|
+
const ids = acIds;
|
|
61
|
+
const columns = {
|
|
62
|
+
id: b.id,
|
|
63
|
+
evidenceAtomId: b.evidenceAtomId,
|
|
64
|
+
acId: b.acId,
|
|
65
|
+
currentAcId: ac.id,
|
|
66
|
+
currentAcText: ac.text,
|
|
67
|
+
recordedTextHash: b.acTextHash,
|
|
68
|
+
bindingType: b.bindingType,
|
|
69
|
+
createdAt: b.createdAt,
|
|
70
|
+
};
|
|
71
|
+
// Two indexed lookups rather than one OR across the join: by the current
|
|
72
|
+
// criterion's uid, then by the recorded ac_id.
|
|
73
|
+
const byUid = await db
|
|
74
|
+
.select(columns)
|
|
75
|
+
.from(b)
|
|
76
|
+
.innerJoin(ac, eq(ac.uid, b.acUid))
|
|
77
|
+
.where(inArray(ac.id, ids))
|
|
78
|
+
.all();
|
|
79
|
+
const byId = await db
|
|
80
|
+
.select(columns)
|
|
81
|
+
.from(b)
|
|
82
|
+
.leftJoin(ac, eq(ac.uid, b.acUid))
|
|
83
|
+
.where(inArray(b.acId, ids))
|
|
84
|
+
.all();
|
|
85
|
+
const rows = [...byUid, ...byId];
|
|
86
|
+
const wanted = new Set(ids);
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const r of rows) {
|
|
90
|
+
if (seen.has(r.id))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(r.id);
|
|
93
|
+
const stale = r.recordedTextHash !== null &&
|
|
94
|
+
r.currentAcText !== null &&
|
|
95
|
+
acTextHash(r.currentAcText) !== r.recordedTextHash;
|
|
96
|
+
out.push({
|
|
97
|
+
id: r.id,
|
|
98
|
+
evidenceAtomId: r.evidenceAtomId,
|
|
99
|
+
acId: r.currentAcId && wanted.has(r.currentAcId) ? r.currentAcId : r.acId,
|
|
100
|
+
bindingType: r.bindingType,
|
|
101
|
+
createdAt: r.createdAt,
|
|
102
|
+
...(stale ? { stale: true } : {}),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
47
106
|
}
|
|
48
107
|
// ---- Schema meta helpers ----
|
|
49
108
|
/** Read a JSON blob from the schema_meta table by key. */
|
|
@@ -132,6 +191,519 @@ export function advanceTaskIdSequence(nativeDb, floor) {
|
|
|
132
191
|
.get();
|
|
133
192
|
return row?.counter;
|
|
134
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Give the task with `uid` the display id `toId`: a compare-and-set on the
|
|
196
|
+
* stored version AND claim lease that advances the version (so a stale
|
|
197
|
+
* `--if-match` writer fails, #1698), with the claim columns carried unchanged
|
|
198
|
+
* (#1701), then every local reference rewritten. Runs in the caller's
|
|
199
|
+
* transaction with foreign keys deferred to its commit.
|
|
200
|
+
*
|
|
201
|
+
* A collision re-mint is a system write: it passes no claim guard and never
|
|
202
|
+
* takes or drops a lease, but the version bump makes the lease holder's next
|
|
203
|
+
* guarded write re-read. A caller acting for a session (a manual rename)
|
|
204
|
+
* passes `guard`: `expectedUpdatedAt` fails with `E_CONFLICT` when the task
|
|
205
|
+
* moved on, `claim` fails with `E_TASK_CLAIMED` when another session holds it.
|
|
206
|
+
*
|
|
207
|
+
* @param nativeDb - The project `cleo.db` handle holding the transaction.
|
|
208
|
+
* @param uid - Uid of the task to rename.
|
|
209
|
+
* @param toId - Its new display id.
|
|
210
|
+
* @param refs - Columns that hold task display ids.
|
|
211
|
+
* @param guard - Optional version / claim guard.
|
|
212
|
+
* @returns The id it had and the rows rewritten per `table.column`.
|
|
213
|
+
* @task T12341
|
|
214
|
+
* @task T12748
|
|
215
|
+
*/
|
|
216
|
+
export function renameTaskDisplayIdNative(nativeDb, uid, toId, refs, guard = {}) {
|
|
217
|
+
const current = nativeDb
|
|
218
|
+
.prepare(`SELECT id, updated_at AS updatedAt, created_at AS createdAt,
|
|
219
|
+
claimed_by_session AS claimedBySession, claimed_by_agent AS claimedByAgent,
|
|
220
|
+
claimed_at AS claimedAt, lease_expires_at AS leaseExpiresAt
|
|
221
|
+
FROM tasks_tasks WHERE uid = ?`)
|
|
222
|
+
.get(uid);
|
|
223
|
+
if (!current)
|
|
224
|
+
throw new Error(`No task with uid ${uid}`);
|
|
225
|
+
if (guard.expectedUpdatedAt !== undefined && guard.expectedUpdatedAt !== taskVersion(current)) {
|
|
226
|
+
throw taskConflictError(current.id, guard.expectedUpdatedAt, current);
|
|
227
|
+
}
|
|
228
|
+
if (guard.claim)
|
|
229
|
+
assertClaimAllowed(current.id, current, guard.claim, []);
|
|
230
|
+
nativeDb.exec('PRAGMA defer_foreign_keys = ON');
|
|
231
|
+
const changed = nativeDb
|
|
232
|
+
.prepare(`UPDATE tasks_tasks SET id = ?, updated_at = ?
|
|
233
|
+
WHERE uid = ? AND id = ? AND updated_at IS ? AND claimed_by_session IS ?
|
|
234
|
+
AND lease_expires_at IS ?`)
|
|
235
|
+
.run(toId, nextTaskVersion(current), uid, current.id, current.updatedAt, current.claimedBySession, current.leaseExpiresAt).changes;
|
|
236
|
+
if (Number(changed) !== 1) {
|
|
237
|
+
throw taskConflictError(current.id, taskVersion(current), null);
|
|
238
|
+
}
|
|
239
|
+
recordLocalRename(nativeDb, uid, current.id, toId, taskVersion(current));
|
|
240
|
+
const rewritten = {};
|
|
241
|
+
const q = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
242
|
+
for (const ref of refs) {
|
|
243
|
+
const table = `main.${q(ref.table)}`;
|
|
244
|
+
const col = q(ref.column);
|
|
245
|
+
const n = ref.jsonArray
|
|
246
|
+
? nativeDb
|
|
247
|
+
.prepare(`UPDATE ${table} SET ${col} = (
|
|
248
|
+
SELECT json_group_array(CASE WHEN j.value = ? THEN ? ELSE j.value END)
|
|
249
|
+
FROM json_each(${table}.${col}) j)
|
|
250
|
+
WHERE json_valid(${col}) AND EXISTS (
|
|
251
|
+
SELECT 1 FROM json_each(${table}.${col}) j WHERE j.value = ?)`)
|
|
252
|
+
.run(current.id, toId, current.id).changes
|
|
253
|
+
: nativeDb.prepare(`UPDATE ${table} SET ${col} = ? WHERE ${col} = ?`).run(toId, current.id)
|
|
254
|
+
.changes;
|
|
255
|
+
if (Number(n) > 0)
|
|
256
|
+
rewritten[`${ref.table}.${ref.column}`] = Number(n);
|
|
257
|
+
}
|
|
258
|
+
for (const [key, n] of Object.entries(rederiveAcIdsNative(nativeDb, current.id, toId))) {
|
|
259
|
+
if (n > 0)
|
|
260
|
+
rewritten[key] = n;
|
|
261
|
+
}
|
|
262
|
+
return { fromId: current.id, rewritten };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* After a task's display id changed from `fromId` to `toId`, re-derive the
|
|
266
|
+
* ids of the acceptance criteria derived from it (`buildAcRowId(taskId,
|
|
267
|
+
* identity)`): the task's own criteria, and a parent's child-projection
|
|
268
|
+
* criteria (`child:<id>`, whose source key is rewritten too). The binding and
|
|
269
|
+
* history rows that point at a re-derived id follow. A criterion whose id was
|
|
270
|
+
* not derived from the old id (an explicit id) keeps it. Without this, two
|
|
271
|
+
* tasks that once shared a display id keep identical ids for same-text
|
|
272
|
+
* criteria ("tests pass") and collide on every merge (T12799).
|
|
273
|
+
*
|
|
274
|
+
* @param nativeDb - The project `cleo.db` handle, inside the rename's transaction.
|
|
275
|
+
* @param fromId - The task's old display id.
|
|
276
|
+
* @param toId - Its new display id (the task row already carries it).
|
|
277
|
+
* @returns Rows rewritten per `table.column`.
|
|
278
|
+
* @task T12799
|
|
279
|
+
*/
|
|
280
|
+
export function rederiveAcIdsNative(nativeDb, fromId, toId) {
|
|
281
|
+
const count = {};
|
|
282
|
+
const bump = (key, n) => {
|
|
283
|
+
count[key] = (count[key] ?? 0) + Number(n);
|
|
284
|
+
};
|
|
285
|
+
const has = (table) => nativeDb
|
|
286
|
+
.prepare("SELECT 1 AS x FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
287
|
+
.get(table) !== undefined;
|
|
288
|
+
if (!has('tasks_task_acceptance_criteria'))
|
|
289
|
+
return count;
|
|
290
|
+
const oldChild = `child:${fromId}`;
|
|
291
|
+
const rows = nativeDb
|
|
292
|
+
.prepare(`SELECT id, task_id AS taskId, kind, text, source_key AS sourceKey
|
|
293
|
+
FROM tasks_task_acceptance_criteria WHERE task_id = ? OR source_key = ?`)
|
|
294
|
+
.all(toId, oldChild);
|
|
295
|
+
const followers = ['tasks_evidence_ac_bindings', 'tasks_task_acceptance_criteria_history'].filter(has);
|
|
296
|
+
for (const row of rows) {
|
|
297
|
+
const ownerBefore = row.taskId === toId ? fromId : row.taskId;
|
|
298
|
+
const sourceAfter = row.sourceKey === oldChild ? `child:${toId}` : row.sourceKey;
|
|
299
|
+
const identity = (source) => (row.kind === 'text' ? row.text : (source ?? ''));
|
|
300
|
+
const derived = row.id === buildAcRowId(ownerBefore, identity(row.sourceKey));
|
|
301
|
+
const newId = derived ? buildAcRowId(row.taskId, identity(sourceAfter)) : row.id;
|
|
302
|
+
if (newId === row.id && sourceAfter === row.sourceKey)
|
|
303
|
+
continue;
|
|
304
|
+
bump('tasks_task_acceptance_criteria.id', nativeDb
|
|
305
|
+
.prepare('UPDATE tasks_task_acceptance_criteria SET id = ?, source_key = ? WHERE id = ?')
|
|
306
|
+
.run(newId, sourceAfter, row.id).changes);
|
|
307
|
+
if (newId === row.id)
|
|
308
|
+
continue;
|
|
309
|
+
for (const table of followers) {
|
|
310
|
+
bump(`${table}.ac_id`, nativeDb.prepare(`UPDATE ${table} SET ac_id = ? WHERE ac_id = ?`).run(newId, row.id)
|
|
311
|
+
.changes);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Insert a display-id alias row. Idempotent on its uid, and order-independent
|
|
318
|
+
* (T12800): when two displacements write one alias uid, the stored row is
|
|
319
|
+
* replaced only when `precedes(incoming, stored)` says the incoming one ranks
|
|
320
|
+
* first, so every replica keeps the same row whatever order the ops arrived
|
|
321
|
+
* in. The caller ranks by HLC order (`compareHlc`), never by text: stored
|
|
322
|
+
* values may be in either HLC format (T12802).
|
|
323
|
+
*
|
|
324
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
325
|
+
* @param row - The alias.
|
|
326
|
+
* @param precedes - Whether `incoming` ranks before the `stored` row of the same uid.
|
|
327
|
+
* @task T12341
|
|
328
|
+
*/
|
|
329
|
+
export function insertDisplayIdAliasNative(nativeDb, row, precedes) {
|
|
330
|
+
const stored = nativeDb
|
|
331
|
+
.prepare(`SELECT uid, entity_table AS entityTable, display_id AS displayId, entity_uid AS entityUid,
|
|
332
|
+
reason, origin, displaced_hlc AS displacedHlc, created_at AS createdAt,
|
|
333
|
+
entity_birth_fp AS entityBirthFp
|
|
334
|
+
FROM tasks_display_id_aliases WHERE uid = ?`)
|
|
335
|
+
.get(row.uid);
|
|
336
|
+
if (!stored) {
|
|
337
|
+
nativeDb
|
|
338
|
+
.prepare(`INSERT INTO tasks_display_id_aliases
|
|
339
|
+
(uid, entity_table, display_id, entity_uid, reason, origin, displaced_hlc, created_at,
|
|
340
|
+
entity_birth_fp)
|
|
341
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
342
|
+
.run(row.uid, row.entityTable, row.displayId, row.entityUid, row.reason, row.origin, row.displacedHlc, row.createdAt, row.entityBirthFp);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!precedes(row, stored))
|
|
346
|
+
return;
|
|
347
|
+
nativeDb
|
|
348
|
+
.prepare(`UPDATE tasks_display_id_aliases
|
|
349
|
+
SET origin = ?, displaced_hlc = ?, created_at = ?,
|
|
350
|
+
entity_birth_fp = coalesce(entity_birth_fp, ?)
|
|
351
|
+
WHERE uid = ?`)
|
|
352
|
+
.run(row.origin, row.displacedHlc, row.createdAt, row.entityBirthFp, row.uid);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Delete one display-id alias row (a local alias whose entity was re-keyed is
|
|
356
|
+
* re-inserted under its new entity uid by the caller).
|
|
357
|
+
*
|
|
358
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
359
|
+
* @param uid - The alias row's uid.
|
|
360
|
+
* @task T12341
|
|
361
|
+
*/
|
|
362
|
+
export function deleteDisplayIdAliasNative(nativeDb, uid) {
|
|
363
|
+
nativeDb.prepare('DELETE FROM tasks_display_id_aliases WHERE uid = ?').run(uid);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Insert a uid re-key alias row (idempotent on its uid).
|
|
367
|
+
*
|
|
368
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
369
|
+
* @param row - The alias.
|
|
370
|
+
* @task T12341
|
|
371
|
+
*/
|
|
372
|
+
export function insertUidAliasNative(nativeDb, row) {
|
|
373
|
+
nativeDb
|
|
374
|
+
.prepare(`INSERT OR IGNORE INTO tasks_uid_aliases
|
|
375
|
+
(uid, entity_table, old_uid, old_birth_fp, new_uid, origin, displaced_hlc, created_at)
|
|
376
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
377
|
+
.run(row.uid, row.entityTable, row.oldUid, row.oldBirthFp, row.newUid, row.origin, row.displacedHlc, row.createdAt);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Hold an incoming row the merge cannot place yet (replacing an older hold of
|
|
381
|
+
* the same row).
|
|
382
|
+
*
|
|
383
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
384
|
+
* @param row - The held row.
|
|
385
|
+
* @task T12341
|
|
386
|
+
*/
|
|
387
|
+
export function insertQuarantineNative(nativeDb, row) {
|
|
388
|
+
nativeDb
|
|
389
|
+
.prepare(`INSERT INTO tasks_identity_quarantine
|
|
390
|
+
(entity_table, uid, birth_fp, reason, contested_id, row_json, received_hlc, created_at)
|
|
391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
392
|
+
ON CONFLICT(entity_table, uid, birth_fp) DO UPDATE SET
|
|
393
|
+
reason = excluded.reason, contested_id = excluded.contested_id,
|
|
394
|
+
row_json = excluded.row_json, received_hlc = excluded.received_hlc,
|
|
395
|
+
created_at = excluded.created_at`)
|
|
396
|
+
.run(row.entityTable, row.uid, row.birthFp, row.reason, row.contestedId, row.rowJson, row.receivedHlc, row.createdAt);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Drop a held row (released or superseded).
|
|
400
|
+
*
|
|
401
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
402
|
+
* @param key - The held row's table, uid and birth fingerprint.
|
|
403
|
+
* @task T12341
|
|
404
|
+
*/
|
|
405
|
+
export function deleteQuarantineNative(nativeDb, key) {
|
|
406
|
+
nativeDb
|
|
407
|
+
.prepare('DELETE FROM tasks_identity_quarantine WHERE entity_table = ? AND uid = ? AND birth_fp = ?')
|
|
408
|
+
.run(key.entityTable, key.uid, key.birthFp);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Write a row-identity meta value (the recipe marker, a re-mint record).
|
|
412
|
+
*
|
|
413
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
414
|
+
* @param key - Meta key.
|
|
415
|
+
* @param value - Meta value.
|
|
416
|
+
* @task T12341
|
|
417
|
+
*/
|
|
418
|
+
export function writeRowIdentityMetaNative(nativeDb, key, value) {
|
|
419
|
+
nativeDb
|
|
420
|
+
.prepare(`INSERT INTO tasks_row_identity_meta (key, value) VALUES (?, ?)
|
|
421
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`)
|
|
422
|
+
.run(key, value);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Hand a criterion back the uid its deleted predecessor carried (AC uid
|
|
426
|
+
* graveyard re-link, spec §6.5), with that predecessor's birth fingerprint.
|
|
427
|
+
*
|
|
428
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
429
|
+
* @param acId - The criterion to re-link (its uid is NULL).
|
|
430
|
+
* @param uid - The uid to hand back.
|
|
431
|
+
* @param birthFp - The fingerprint that goes with it, when recorded.
|
|
432
|
+
* @returns Rows changed (0 or 1).
|
|
433
|
+
* @task T12341
|
|
434
|
+
*/
|
|
435
|
+
export function relinkAcUidNative(nativeDb, acId, uid, birthFp) {
|
|
436
|
+
return Number(nativeDb
|
|
437
|
+
.prepare('UPDATE tasks_task_acceptance_criteria SET uid = ?, birth_fp = COALESCE(?, birth_fp) WHERE id = ? AND uid IS NULL')
|
|
438
|
+
.run(uid, birthFp, acId).changes);
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Empty the AC uid graveyard (after the open pass consumed it).
|
|
442
|
+
*
|
|
443
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
444
|
+
* @task T12341
|
|
445
|
+
*/
|
|
446
|
+
export function clearAcUidGraveyardNative(nativeDb) {
|
|
447
|
+
nativeDb.exec('DELETE FROM tasks_ac_uid_graveyard');
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Read the birth of task `taskId` before an overwrite replaces it.
|
|
451
|
+
*
|
|
452
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
453
|
+
* @param taskId - The task about to be overwritten.
|
|
454
|
+
* @returns Its birth, or `undefined` when there is no such row.
|
|
455
|
+
* @task T12806
|
|
456
|
+
*/
|
|
457
|
+
export function readReplacedTaskBirthNative(nativeDb, taskId) {
|
|
458
|
+
return nativeDb
|
|
459
|
+
.prepare('SELECT created_at AS createdAt, uid FROM tasks_tasks WHERE id = ?')
|
|
460
|
+
.get(taskId);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* After an overwrite import replaced task `taskId`, keep or re-key its
|
|
464
|
+
* identity (T12806 review):
|
|
465
|
+
*
|
|
466
|
+
* - the SAME task (same id, same birth instant as before the overwrite) keeps
|
|
467
|
+
* its identity. Sameness is decided on the immutable birth facts, never on
|
|
468
|
+
* the title or type: a task retitled here and re-imported unchanged stays
|
|
469
|
+
* the same row;
|
|
470
|
+
* - refused once identity is shared: the overwrite would re-point a uid other
|
|
471
|
+
* devices hold at different work;
|
|
472
|
+
* - otherwise the task, the natural rows keyed by it (dependencies,
|
|
473
|
+
* relations, labels) and its criteria are re-derived from the new row, now,
|
|
474
|
+
* and every AC history row and binding that pointed at one of those
|
|
475
|
+
* criteria is RE-KEYED to the criterion's new uid, found by the criterion's
|
|
476
|
+
* old uid as well as by its current id. A binding whose `ac_id` is stale
|
|
477
|
+
* (#1731) keeps resolving: its evidence is kept, never dropped.
|
|
478
|
+
*
|
|
479
|
+
* @param nativeDb - The project `cleo.db` handle holding the transaction.
|
|
480
|
+
* @param taskId - The overwritten task.
|
|
481
|
+
* @param before - Its birth before the overwrite ({@link readReplacedTaskBirthNative}).
|
|
482
|
+
* @task T12806
|
|
483
|
+
*/
|
|
484
|
+
export function clearReplacedTaskIdentityNative(nativeDb, taskId, before) {
|
|
485
|
+
const row = nativeDb
|
|
486
|
+
.prepare('SELECT uid, birth_fp AS fp, created_at AS createdAt FROM tasks_tasks WHERE id = ?')
|
|
487
|
+
.get(taskId);
|
|
488
|
+
if (!row || (row.uid === null && row.fp === null))
|
|
489
|
+
return;
|
|
490
|
+
if (before) {
|
|
491
|
+
const was = parseStoreTimestamp(before.createdAt);
|
|
492
|
+
if (was !== null && was === parseStoreTimestamp(row.createdAt)) {
|
|
493
|
+
return; // the same task, re-imported: its identity stands
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (rowIdentityShared(nativeDb)) {
|
|
497
|
+
throw new CleoError(ExitCode.VALIDATION_ERROR, `Task ${taskId} cannot be overwritten by an import: its identity is shared with other devices`, {
|
|
498
|
+
fix: 'Import it under a new id (the default duplicate strategy), or edit the task instead of overwriting it.',
|
|
499
|
+
details: { field: 'taskId', actual: taskId },
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const has = (table) => nativeDb
|
|
503
|
+
.prepare("SELECT 1 AS x FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
504
|
+
.get(table) !== undefined;
|
|
505
|
+
nativeDb.prepare('UPDATE tasks_tasks SET uid = NULL, birth_fp = NULL WHERE id = ?').run(taskId);
|
|
506
|
+
for (const spec of ROW_IDENTITY.project) {
|
|
507
|
+
if (spec.kind !== 'natural' || !has(spec.table))
|
|
508
|
+
continue;
|
|
509
|
+
const cols = (spec.keyRefs ?? []).filter((r) => r.table === 'tasks_tasks').map((r) => r.column);
|
|
510
|
+
if (cols.length === 0)
|
|
511
|
+
continue;
|
|
512
|
+
nativeDb
|
|
513
|
+
.prepare(`UPDATE main.${quoteIdent(spec.table)} SET uid = NULL WHERE ${cols.map((c) => `${quoteIdent(c)} = ?`).join(' OR ')}`)
|
|
514
|
+
.run(...cols.map(() => taskId));
|
|
515
|
+
}
|
|
516
|
+
const children = ['tasks_task_acceptance_criteria_history', 'tasks_evidence_ac_bindings'].filter(has);
|
|
517
|
+
const criteria = has('tasks_task_acceptance_criteria')
|
|
518
|
+
? nativeDb
|
|
519
|
+
.prepare('SELECT id, uid FROM tasks_task_acceptance_criteria WHERE task_id = ?')
|
|
520
|
+
.all(taskId)
|
|
521
|
+
: [];
|
|
522
|
+
if (criteria.length > 0) {
|
|
523
|
+
nativeDb
|
|
524
|
+
.prepare('UPDATE tasks_task_acceptance_criteria SET uid = NULL, birth_fp = NULL WHERE task_id = ?')
|
|
525
|
+
.run(taskId);
|
|
526
|
+
}
|
|
527
|
+
// Re-derive the task, its natural rows and its criteria now, inside the
|
|
528
|
+
// import transaction, so the children can be re-keyed to the new uids.
|
|
529
|
+
fillRowUids(nativeDb, 'project');
|
|
530
|
+
const newUid = nativeDb.prepare('SELECT uid FROM tasks_task_acceptance_criteria WHERE id = ?');
|
|
531
|
+
let rekeyed = 0;
|
|
532
|
+
for (const ac of criteria) {
|
|
533
|
+
const now = newUid.get(ac.id)?.uid ?? null;
|
|
534
|
+
for (const table of children) {
|
|
535
|
+
// By the criterion's old uid (a stale ac_id keeps it) or its current id.
|
|
536
|
+
rekeyed += Number(nativeDb
|
|
537
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET ac_uid = ?, birth_fp = NULL
|
|
538
|
+
WHERE ac_id = ? OR (? IS NOT NULL AND ac_uid = ?)`)
|
|
539
|
+
.run(now, ac.id, ac.uid, ac.uid).changes);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Their fingerprints hash the criterion's: re-derive them against the new one.
|
|
543
|
+
if (rekeyed > 0)
|
|
544
|
+
fillRowUids(nativeDb, 'project');
|
|
545
|
+
}
|
|
546
|
+
/** Quote an identifier for the dynamic row-identity writes below. */
|
|
547
|
+
function quoteIdent(name) {
|
|
548
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Fill a NULL identity column (`uid`, `birth_fp`, a stored reference uid) of a
|
|
552
|
+
* declared table from its recipe SQL (the row-uid open pass, spec §6.1).
|
|
553
|
+
*
|
|
554
|
+
* @param nativeDb - The `cleo.db` handle holding the cold-open lease.
|
|
555
|
+
* @param table - Declared table.
|
|
556
|
+
* @param column - Identity column.
|
|
557
|
+
* @param valueSql - Recipe expression over `main."<table>"` columns.
|
|
558
|
+
* @param rowid - One row only (the clash fallback); all NULL rows otherwise.
|
|
559
|
+
* @returns Rows written.
|
|
560
|
+
* @task T12341
|
|
561
|
+
*/
|
|
562
|
+
export function fillIdentityColumnNative(nativeDb, table, column, valueSql, rowid) {
|
|
563
|
+
const col = quoteIdent(column);
|
|
564
|
+
const sql = `UPDATE main.${quoteIdent(table)} SET ${col} = ${valueSql} WHERE ${col} IS NULL`;
|
|
565
|
+
const changes = rowid === undefined
|
|
566
|
+
? nativeDb.prepare(sql).run().changes
|
|
567
|
+
: nativeDb.prepare(`${sql} AND rowid = ?`).run(rowid).changes;
|
|
568
|
+
return Number(changes);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Clear one pre-release birth fingerprint so the fill re-derives it (spec §12.1).
|
|
572
|
+
*
|
|
573
|
+
* @param nativeDb - The `cleo.db` handle holding the cold-open lease.
|
|
574
|
+
* @param table - Declared minted table.
|
|
575
|
+
* @param rowid - The row.
|
|
576
|
+
* @task T12341
|
|
577
|
+
*/
|
|
578
|
+
export function clearBirthFpNative(nativeDb, table, rowid) {
|
|
579
|
+
nativeDb
|
|
580
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET birth_fp = NULL WHERE rowid = ?`)
|
|
581
|
+
.run(rowid);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Insert one row into a declared identity table, with the columns the row
|
|
585
|
+
* carries that the table has (a row released from the identity quarantine).
|
|
586
|
+
*
|
|
587
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
588
|
+
* @param table - Declared table.
|
|
589
|
+
* @param row - Column → value.
|
|
590
|
+
* @task T12341
|
|
591
|
+
*/
|
|
592
|
+
export function insertIdentityRowNative(nativeDb, table, row) {
|
|
593
|
+
const have = new Set(nativeDb.prepare('SELECT name FROM pragma_table_info(?)').all(table).map((c) => c.name));
|
|
594
|
+
const cols = Object.keys(row).filter((c) => have.has(c));
|
|
595
|
+
nativeDb
|
|
596
|
+
.prepare(`INSERT INTO main.${quoteIdent(table)} (${cols.map(quoteIdent).join(', ')}) VALUES (${cols
|
|
597
|
+
.map(() => '?')
|
|
598
|
+
.join(', ')})`)
|
|
599
|
+
.run(...cols.map((c) => row[c] ?? null));
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Give the row of `table` identified by (`oldUid`, `birthFp`) the uid
|
|
603
|
+
* `newUid` (a uid re-key; the birth fingerprint never changes).
|
|
604
|
+
*
|
|
605
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
606
|
+
* @param table - Declared minted table.
|
|
607
|
+
* @param oldUid - The colliding uid.
|
|
608
|
+
* @param birthFp - The row's birth fingerprint.
|
|
609
|
+
* @param newUid - Its new uid.
|
|
610
|
+
* @returns Rows changed (0 or 1).
|
|
611
|
+
* @task T12341
|
|
612
|
+
*/
|
|
613
|
+
export function setRowUidNative(nativeDb, table, oldUid, birthFp, newUid) {
|
|
614
|
+
return Number(nativeDb
|
|
615
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET uid = ? WHERE uid = ? AND birth_fp = ?`)
|
|
616
|
+
.run(newUid, oldUid, birthFp).changes);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Point a stored reference uid column (`ac_uid`) of the rows that reference
|
|
620
|
+
* the re-keyed row by key at its new uid. Scoped by the key column, so a row
|
|
621
|
+
* referencing the OTHER holder of a colliding uid is never touched.
|
|
622
|
+
*
|
|
623
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
624
|
+
* @param table - Table holding the column.
|
|
625
|
+
* @param column - The stored reference uid column.
|
|
626
|
+
* @param fromColumn - The column that references the row by key (`ac_id`).
|
|
627
|
+
* @param key - The re-keyed row's local key.
|
|
628
|
+
* @param oldUid - Old referenced uid.
|
|
629
|
+
* @param newUid - New referenced uid.
|
|
630
|
+
* @returns Rows changed.
|
|
631
|
+
* @task T12341
|
|
632
|
+
*/
|
|
633
|
+
export function rewriteStoredRefUidNative(nativeDb, table, column, fromColumn, key, oldUid, newUid) {
|
|
634
|
+
const col = quoteIdent(column);
|
|
635
|
+
return Number(nativeDb
|
|
636
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET ${col} = ? WHERE ${col} = ? AND ${quoteIdent(fromColumn)} = ?`)
|
|
637
|
+
.run(newUid, oldUid, key).changes);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Clear one row's uid so the natural recipe re-derives it (a uid re-key
|
|
641
|
+
* changed an endpoint's uid).
|
|
642
|
+
*
|
|
643
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
644
|
+
* @param table - Declared natural table.
|
|
645
|
+
* @param rowid - The row.
|
|
646
|
+
* @task T12341
|
|
647
|
+
*/
|
|
648
|
+
export function clearRowUidNative(nativeDb, table, rowid) {
|
|
649
|
+
nativeDb.prepare(`UPDATE main.${quoteIdent(table)} SET uid = NULL WHERE rowid = ?`).run(rowid);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Delete a row-identity meta value (a re-mint record that moved with a re-key).
|
|
653
|
+
*
|
|
654
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
655
|
+
* @param key - Meta key.
|
|
656
|
+
* @task T12341
|
|
657
|
+
*/
|
|
658
|
+
export function deleteRowIdentityMetaNative(nativeDb, key) {
|
|
659
|
+
nativeDb.prepare('DELETE FROM tasks_row_identity_meta WHERE key = ?').run(key);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Set a natural row's uid by its current uid (a re-key re-derives the natural
|
|
663
|
+
* uids keyed by the re-keyed row; a receiver applies the published values).
|
|
664
|
+
*
|
|
665
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
666
|
+
* @param table - Declared natural table.
|
|
667
|
+
* @param oldUid - Current uid.
|
|
668
|
+
* @param newUid - New uid.
|
|
669
|
+
* @returns Rows changed.
|
|
670
|
+
* @task T12341
|
|
671
|
+
*/
|
|
672
|
+
export function setNaturalUidNative(nativeDb, table, oldUid, newUid) {
|
|
673
|
+
return Number(nativeDb
|
|
674
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET uid = ? WHERE uid = ?`)
|
|
675
|
+
.run(newUid, oldUid).changes);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Rewrite a held row (identity quarantine) under a new uid: a uid re-key of a
|
|
679
|
+
* row this replica holds but has not placed.
|
|
680
|
+
*
|
|
681
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
682
|
+
* @param key - The held row's table, uid and birth fingerprint.
|
|
683
|
+
* @param newUid - Its new uid.
|
|
684
|
+
* @param rowJson - Its wire form with the new uid.
|
|
685
|
+
* @task T12341
|
|
686
|
+
*/
|
|
687
|
+
export function rekeyQuarantineNative(nativeDb, key, newUid, rowJson) {
|
|
688
|
+
nativeDb
|
|
689
|
+
.prepare(`UPDATE tasks_identity_quarantine SET uid = ?, row_json = ?
|
|
690
|
+
WHERE entity_table = ? AND uid = ? AND birth_fp = ?`)
|
|
691
|
+
.run(newUid, rowJson, key.entityTable, key.uid, key.birthFp);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Add labels to a task's label junction (idempotent), for writers that
|
|
695
|
+
* insert a task with raw SQL (the sentient proposal ticks).
|
|
696
|
+
*
|
|
697
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
698
|
+
* @param taskId - The task.
|
|
699
|
+
* @param labels - Labels to add.
|
|
700
|
+
* @task T12341
|
|
701
|
+
*/
|
|
702
|
+
export function addTaskLabelsNative(nativeDb, taskId, labels) {
|
|
703
|
+
const stmt = nativeDb.prepare('INSERT OR IGNORE INTO tasks_task_labels (task_id, label) VALUES (?, ?)');
|
|
704
|
+
for (const label of labels)
|
|
705
|
+
stmt.run(taskId, label);
|
|
706
|
+
}
|
|
135
707
|
// One queue per shared native handle, independent of accessor identity. These
|
|
136
708
|
// hold coordination state only; the ProjectStore remains the owner of handles.
|
|
137
709
|
const taskTransactionQueue = new WeakMap();
|
|
@@ -219,6 +791,83 @@ function claimPredicate(guard, allowedHolders) {
|
|
|
219
791
|
: undefined;
|
|
220
792
|
return or(isNull(holder), mine, related, expired);
|
|
221
793
|
}
|
|
794
|
+
/** How long after a local rename a write to the old id is still read as meant for the renamed task. */
|
|
795
|
+
export const RENAMED_RECENT_MS = 24 * 60 * 60 * 1000;
|
|
796
|
+
function renameRecordKey(fromId) {
|
|
797
|
+
return `renamed_from:tasks_tasks:${fromId}`;
|
|
798
|
+
}
|
|
799
|
+
function hasIdentityMeta(nativeDb) {
|
|
800
|
+
return (nativeDb
|
|
801
|
+
.prepare("SELECT 1 AS x FROM sqlite_master WHERE type = 'table' AND name = 'tasks_row_identity_meta'")
|
|
802
|
+
.get() !== undefined);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Remember, locally, that `fromId` named the task `uid` at version
|
|
806
|
+
* `fromVersion` until now (T12800). Local-only (`tasks_row_identity_meta`):
|
|
807
|
+
* it answers "what did a caller of THIS store mean by `fromId`", which only
|
|
808
|
+
* this store's history can.
|
|
809
|
+
*/
|
|
810
|
+
function recordLocalRename(nativeDb, uid, fromId, toId, fromVersion) {
|
|
811
|
+
if (!hasIdentityMeta(nativeDb))
|
|
812
|
+
return;
|
|
813
|
+
const record = { uid, fromVersion, toId, at: new Date().toISOString() };
|
|
814
|
+
writeRowIdentityMetaNative(nativeDb, renameRecordKey(fromId), JSON.stringify(record));
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Refuse a guarded write that names a display id its task no longer carries
|
|
818
|
+
* (T12800), when the caller can only mean the renamed task:
|
|
819
|
+
*
|
|
820
|
+
* - it expects EXACTLY the version the renamed task had when it left the id
|
|
821
|
+
* (the version the caller read under the old id), and that is not the
|
|
822
|
+
* current holder's version: a stale write on the current holder stays a
|
|
823
|
+
* plain `E_CONFLICT`;
|
|
824
|
+
* - or it renews or releases a claim the caller's session holds on the
|
|
825
|
+
* renamed task (acquiring or taking over the new holder is legitimate: a
|
|
826
|
+
* pivot claims its target before it releases its source);
|
|
827
|
+
* - or no row holds the old id any more.
|
|
828
|
+
*
|
|
829
|
+
* Only a RECENT rename counts ({@link RENAMED_RECENT_MS}), and only one this
|
|
830
|
+
* store performed (the local rename record). `E_TASK_RENAMED` carries the new
|
|
831
|
+
* id in its details.
|
|
832
|
+
*/
|
|
833
|
+
function assertNotRenamed(nativeDb, taskId, guard, current) {
|
|
834
|
+
if (!nativeDb || !hasIdentityMeta(nativeDb))
|
|
835
|
+
return;
|
|
836
|
+
const raw = nativeDb
|
|
837
|
+
.prepare('SELECT value FROM tasks_row_identity_meta WHERE key = ?')
|
|
838
|
+
.get(renameRecordKey(taskId));
|
|
839
|
+
if (!raw)
|
|
840
|
+
return;
|
|
841
|
+
let record;
|
|
842
|
+
try {
|
|
843
|
+
record = JSON.parse(raw.value);
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (Date.now() - Date.parse(record.at) > RENAMED_RECENT_MS)
|
|
849
|
+
return;
|
|
850
|
+
const renamed = nativeDb
|
|
851
|
+
.prepare('SELECT id, claimed_by_session AS claimedBySession FROM tasks_tasks WHERE uid = ?')
|
|
852
|
+
.get(record.uid);
|
|
853
|
+
if (!renamed || renamed.id === taskId)
|
|
854
|
+
return;
|
|
855
|
+
const expected = guard.expectedUpdatedAt;
|
|
856
|
+
const session = guard.claim?.sessionId ?? null;
|
|
857
|
+
const holdsRenamedClaim = session !== null &&
|
|
858
|
+
(guard.claim?.mode === 'renew' || guard.claim?.mode === 'release') &&
|
|
859
|
+
renamed.claimedBySession === session &&
|
|
860
|
+
current?.claimedBySession !== session;
|
|
861
|
+
const readRenamed = expected !== undefined &&
|
|
862
|
+
expected === record.fromVersion &&
|
|
863
|
+
(current === undefined || taskVersion(current) !== expected);
|
|
864
|
+
if (!(current === undefined || holdsRenamedClaim || readRenamed))
|
|
865
|
+
return;
|
|
866
|
+
throw new CleoError(ExitCode.TASK_RENAMED, `Task ${taskId} is now ${renamed.id}: sync re-numbered it after a display-id collision, and ${taskId} ${current ? 'names another task' : 'names no task'}; nothing was written`, {
|
|
867
|
+
fix: `Re-read it with 'cleo show ${renamed.id}' and retry against ${renamed.id}.`,
|
|
868
|
+
details: { field: 'taskId', actual: taskId, expected: renamed.id },
|
|
869
|
+
});
|
|
870
|
+
}
|
|
222
871
|
/** Throw `E_TASK_CLAIMED` when the stored holder refuses this claim write. */
|
|
223
872
|
function assertClaimAllowed(taskId, row, guard, allowedHolders) {
|
|
224
873
|
const stored = claimFromColumns(row);
|
|
@@ -528,8 +1177,18 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
528
1177
|
async upsertSingleTask(task) {
|
|
529
1178
|
await accessor.transaction((tx) => tx.upsertSingleTask(task));
|
|
530
1179
|
},
|
|
531
|
-
async insertNewTask(task) {
|
|
532
|
-
await accessor.transaction((tx) => tx.insertNewTask(task));
|
|
1180
|
+
async insertNewTask(task, identity) {
|
|
1181
|
+
await accessor.transaction((tx) => tx.insertNewTask(task, identity));
|
|
1182
|
+
},
|
|
1183
|
+
async getTaskIdentities(taskIds) {
|
|
1184
|
+
if (taskIds.length === 0)
|
|
1185
|
+
return [];
|
|
1186
|
+
const db = await getDb(cwd);
|
|
1187
|
+
return db
|
|
1188
|
+
.select({ id: schema.tasks.id, uid: schema.tasks.uid, birthFp: schema.tasks.birthFp })
|
|
1189
|
+
.from(schema.tasks)
|
|
1190
|
+
.where(inArray(schema.tasks.id, taskIds))
|
|
1191
|
+
.all();
|
|
533
1192
|
},
|
|
534
1193
|
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
535
1194
|
return accessor.transaction(async () => {
|
|
@@ -607,6 +1266,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
607
1266
|
createdAt: schema.taskAcceptanceCriteria.createdAt,
|
|
608
1267
|
updatedAt: schema.taskAcceptanceCriteria.updatedAt,
|
|
609
1268
|
contentHash: schema.taskAcceptanceCriteria.contentHash,
|
|
1269
|
+
uid: schema.taskAcceptanceCriteria.uid,
|
|
1270
|
+
birthFp: schema.taskAcceptanceCriteria.birthFp,
|
|
610
1271
|
})
|
|
611
1272
|
.from(schema.taskAcceptanceCriteria)
|
|
612
1273
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
@@ -624,31 +1285,20 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
624
1285
|
createdAt: r.createdAt,
|
|
625
1286
|
updatedAt: r.updatedAt ?? null,
|
|
626
1287
|
contentHash: r.contentHash ?? null,
|
|
1288
|
+
uid: r.uid ?? null,
|
|
1289
|
+
birthFp: r.birthFp ?? null,
|
|
627
1290
|
}));
|
|
628
1291
|
},
|
|
629
1292
|
// ---- AC bindings (T10509 — AC-coverage gate) ----
|
|
630
1293
|
async getAcBindings(acIds) {
|
|
631
1294
|
if (acIds.length === 0)
|
|
632
1295
|
return [];
|
|
1296
|
+
return selectAcBindings(await getDb(cwd), acIds);
|
|
1297
|
+
},
|
|
1298
|
+
// ---- AC bindings — orphan scan (T12790) ----
|
|
1299
|
+
async findOrphanAcBindings() {
|
|
633
1300
|
const db = await getDb(cwd);
|
|
634
|
-
|
|
635
|
-
.select({
|
|
636
|
-
id: schema.evidenceAcBindings.id,
|
|
637
|
-
evidenceAtomId: schema.evidenceAcBindings.evidenceAtomId,
|
|
638
|
-
acId: schema.evidenceAcBindings.acId,
|
|
639
|
-
bindingType: schema.evidenceAcBindings.bindingType,
|
|
640
|
-
createdAt: schema.evidenceAcBindings.createdAt,
|
|
641
|
-
})
|
|
642
|
-
.from(schema.evidenceAcBindings)
|
|
643
|
-
.where(inArray(schema.evidenceAcBindings.acId, acIds))
|
|
644
|
-
.all();
|
|
645
|
-
return rows.map((r) => ({
|
|
646
|
-
id: r.id,
|
|
647
|
-
evidenceAtomId: r.evidenceAtomId,
|
|
648
|
-
acId: r.acId,
|
|
649
|
-
bindingType: r.bindingType,
|
|
650
|
-
createdAt: r.createdAt,
|
|
651
|
-
}));
|
|
1301
|
+
return selectOrphanAcBindings(db);
|
|
652
1302
|
},
|
|
653
1303
|
async archiveSingleTask(taskId, fields) {
|
|
654
1304
|
return accessor.transaction(async () => {
|
|
@@ -692,6 +1342,9 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
692
1342
|
.delete(schema.taskDependencies)
|
|
693
1343
|
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
694
1344
|
.run();
|
|
1345
|
+
// T12790: the bindings table has no FK to the AC rows that cascade
|
|
1346
|
+
// from tasks_tasks — remove (and audit) them explicitly.
|
|
1347
|
+
await pruneAcBindingsForTask(db, taskId);
|
|
695
1348
|
// Delete the task itself
|
|
696
1349
|
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
697
1350
|
});
|
|
@@ -1113,6 +1766,11 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1113
1766
|
.where(eq(schema.tasks.id, taskId))
|
|
1114
1767
|
.limit(1)
|
|
1115
1768
|
.all();
|
|
1769
|
+
// T12800: a guarded write aimed at a task that sync re-numbered must not
|
|
1770
|
+
// land on whatever row holds the old id now.
|
|
1771
|
+
if (guard?.claim || guard?.expectedUpdatedAt !== undefined) {
|
|
1772
|
+
assertNotRenamed(getNativeTasksDb(cwd), taskId, guard, current);
|
|
1773
|
+
}
|
|
1116
1774
|
if (!current)
|
|
1117
1775
|
throw new Error(`Task not found: ${taskId}`);
|
|
1118
1776
|
// T12502: refuse a claim write the stored holder does not allow before
|
|
@@ -1195,24 +1853,42 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1195
1853
|
nativeDb.prepare(`SAVEPOINT ${spName}`).run();
|
|
1196
1854
|
}
|
|
1197
1855
|
try {
|
|
1856
|
+
// The birth of each task an upsert replaced, for clearTaskIdentity
|
|
1857
|
+
// (T12806): sameness is decided on the birth before the overwrite.
|
|
1858
|
+
const replacedBirths = new Map();
|
|
1198
1859
|
const tx = {
|
|
1199
1860
|
async upsertSingleTask(task) {
|
|
1200
1861
|
scope.assertActive();
|
|
1201
1862
|
return accessor.transaction(async () => {
|
|
1202
1863
|
scope.assertActive();
|
|
1864
|
+
if (rowUidFillEnabled() && !replacedBirths.has(task.id)) {
|
|
1865
|
+
const before = readReplacedTaskBirthNative(nativeDb, task.id);
|
|
1866
|
+
if (before)
|
|
1867
|
+
replacedBirths.set(task.id, before);
|
|
1868
|
+
}
|
|
1203
1869
|
const row = taskToRow(task);
|
|
1204
1870
|
await upsertTask(db, row);
|
|
1205
1871
|
await updateDependencies(db, task.id, task.depends ?? []);
|
|
1206
1872
|
});
|
|
1207
1873
|
},
|
|
1208
|
-
async insertNewTask(task) {
|
|
1874
|
+
async insertNewTask(task, identity) {
|
|
1209
1875
|
scope.assertActive();
|
|
1210
1876
|
return accessor.transaction(async () => {
|
|
1211
1877
|
scope.assertActive();
|
|
1212
|
-
await insertNewTask(db, taskToRow(task));
|
|
1878
|
+
await insertNewTask(db, taskToRow(task), identity);
|
|
1213
1879
|
await updateDependencies(db, task.id, task.depends ?? []);
|
|
1214
1880
|
});
|
|
1215
1881
|
},
|
|
1882
|
+
async clearTaskIdentity(taskId) {
|
|
1883
|
+
scope.assertActive();
|
|
1884
|
+
// Row uids off: an overwrite changes no identity value (T12806 review).
|
|
1885
|
+
if (!rowUidFillEnabled())
|
|
1886
|
+
return;
|
|
1887
|
+
return accessor.transaction(async () => {
|
|
1888
|
+
scope.assertActive();
|
|
1889
|
+
clearReplacedTaskIdentityNative(nativeDb, taskId, replacedBirths.get(taskId));
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1216
1892
|
async archiveSingleTask(taskId, fields) {
|
|
1217
1893
|
scope.assertActive();
|
|
1218
1894
|
return accessor.transaction(async () => {
|
|
@@ -1242,6 +1918,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1242
1918
|
.delete(schema.taskDependencies)
|
|
1243
1919
|
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
1244
1920
|
.run();
|
|
1921
|
+
// T12790: bindings of the cascading AC rows (no FK).
|
|
1922
|
+
await pruneAcBindingsForTask(db, taskId);
|
|
1245
1923
|
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
1246
1924
|
});
|
|
1247
1925
|
},
|
|
@@ -1321,6 +1999,9 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1321
1999
|
projection: r.projection ?? 'legacy',
|
|
1322
2000
|
text: r.text,
|
|
1323
2001
|
contentHash: r.contentHash ?? null,
|
|
2002
|
+
// A kept criterion keeps its uid (T12341); else one is minted.
|
|
2003
|
+
...(r.uid ? { uid: r.uid } : {}),
|
|
2004
|
+
...(r.uid && r.birthFp ? { birthFp: r.birthFp } : {}),
|
|
1324
2005
|
})))
|
|
1325
2006
|
.run();
|
|
1326
2007
|
});
|
|
@@ -1340,6 +2021,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1340
2021
|
createdAt: schema.taskAcceptanceCriteria.createdAt,
|
|
1341
2022
|
updatedAt: schema.taskAcceptanceCriteria.updatedAt,
|
|
1342
2023
|
contentHash: schema.taskAcceptanceCriteria.contentHash,
|
|
2024
|
+
uid: schema.taskAcceptanceCriteria.uid,
|
|
2025
|
+
birthFp: schema.taskAcceptanceCriteria.birthFp,
|
|
1343
2026
|
})
|
|
1344
2027
|
.from(schema.taskAcceptanceCriteria)
|
|
1345
2028
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
@@ -1357,18 +2040,70 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1357
2040
|
createdAt: r.createdAt,
|
|
1358
2041
|
updatedAt: r.updatedAt ?? null,
|
|
1359
2042
|
contentHash: r.contentHash ?? null,
|
|
2043
|
+
uid: r.uid ?? null,
|
|
2044
|
+
birthFp: r.birthFp ?? null,
|
|
1360
2045
|
}));
|
|
1361
2046
|
},
|
|
1362
2047
|
async deleteAcRowsForTask(taskId) {
|
|
1363
2048
|
scope.assertActive();
|
|
1364
2049
|
return accessor.transaction(async () => {
|
|
1365
2050
|
scope.assertActive();
|
|
2051
|
+
// T12790: remove (and audit) the departing rows' bindings.
|
|
2052
|
+
await pruneAcBindingsForTask(db, taskId, 'ac-removed');
|
|
1366
2053
|
await db
|
|
1367
2054
|
.delete(schema.taskAcceptanceCriteria)
|
|
1368
2055
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
1369
2056
|
.run();
|
|
1370
2057
|
});
|
|
1371
2058
|
},
|
|
2059
|
+
async deleteAcRowsByIds(taskId, ids, keepBindingsForUids = []) {
|
|
2060
|
+
scope.assertActive();
|
|
2061
|
+
if (ids.length === 0)
|
|
2062
|
+
return;
|
|
2063
|
+
return accessor.transaction(async () => {
|
|
2064
|
+
scope.assertActive();
|
|
2065
|
+
// T12790: only ids this task actually owns leave the set —
|
|
2066
|
+
// prune (and audit) exactly their bindings.
|
|
2067
|
+
const owned = await db
|
|
2068
|
+
.select({ id: schema.taskAcceptanceCriteria.id })
|
|
2069
|
+
.from(schema.taskAcceptanceCriteria)
|
|
2070
|
+
.where(and(eq(schema.taskAcceptanceCriteria.taskId, taskId), inArray(schema.taskAcceptanceCriteria.id, ids)))
|
|
2071
|
+
.all();
|
|
2072
|
+
await pruneAcBindingsForAcIds(db, taskId, owned.map((r) => r.id), 'ac-removed', keepBindingsForUids);
|
|
2073
|
+
await db
|
|
2074
|
+
.delete(schema.taskAcceptanceCriteria)
|
|
2075
|
+
.where(and(eq(schema.taskAcceptanceCriteria.taskId, taskId), inArray(schema.taskAcceptanceCriteria.id, ids)))
|
|
2076
|
+
.run();
|
|
2077
|
+
});
|
|
2078
|
+
},
|
|
2079
|
+
async updateAcRows(rows) {
|
|
2080
|
+
scope.assertActive();
|
|
2081
|
+
if (rows.length === 0)
|
|
2082
|
+
return;
|
|
2083
|
+
return accessor.transaction(async () => {
|
|
2084
|
+
scope.assertActive();
|
|
2085
|
+
const updatedAt = new Date().toISOString();
|
|
2086
|
+
for (const r of rows) {
|
|
2087
|
+
const result = await db
|
|
2088
|
+
.update(schema.taskAcceptanceCriteria)
|
|
2089
|
+
.set({
|
|
2090
|
+
ordinal: r.ordinal,
|
|
2091
|
+
kind: r.kind ?? 'text',
|
|
2092
|
+
sourceKey: r.sourceKey ?? `text:${r.ordinal}`,
|
|
2093
|
+
targetTaskId: r.targetTaskId ?? null,
|
|
2094
|
+
projection: r.projection ?? 'legacy',
|
|
2095
|
+
text: r.text,
|
|
2096
|
+
contentHash: r.contentHash ?? null,
|
|
2097
|
+
updatedAt,
|
|
2098
|
+
})
|
|
2099
|
+
.where(and(eq(schema.taskAcceptanceCriteria.id, r.id), eq(schema.taskAcceptanceCriteria.taskId, r.taskId)))
|
|
2100
|
+
.run();
|
|
2101
|
+
if (Number(result.changes) === 0) {
|
|
2102
|
+
throw new CleoError(ExitCode.VALIDATION_ERROR, `Acceptance criterion ${r.id} does not exist on task ${r.taskId}; refusing to update it`);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
},
|
|
1372
2107
|
async appendAcHistory(rows) {
|
|
1373
2108
|
scope.assertActive();
|
|
1374
2109
|
return accessor.transaction(async () => {
|
|
@@ -1381,6 +2116,7 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1381
2116
|
acId: r.acId,
|
|
1382
2117
|
previousText: r.previousText,
|
|
1383
2118
|
reason: r.reason,
|
|
2119
|
+
...(r.acUid ? { acUid: r.acUid } : {}),
|
|
1384
2120
|
})))
|
|
1385
2121
|
.run();
|
|
1386
2122
|
});
|
|
@@ -1388,26 +2124,7 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1388
2124
|
// ---- AC bindings (T10509 — AC-coverage gate) ----
|
|
1389
2125
|
async getAcBindings(acIds) {
|
|
1390
2126
|
scope.assertActive();
|
|
1391
|
-
|
|
1392
|
-
return [];
|
|
1393
|
-
const out = await db
|
|
1394
|
-
.select({
|
|
1395
|
-
id: schema.evidenceAcBindings.id,
|
|
1396
|
-
evidenceAtomId: schema.evidenceAcBindings.evidenceAtomId,
|
|
1397
|
-
acId: schema.evidenceAcBindings.acId,
|
|
1398
|
-
bindingType: schema.evidenceAcBindings.bindingType,
|
|
1399
|
-
createdAt: schema.evidenceAcBindings.createdAt,
|
|
1400
|
-
})
|
|
1401
|
-
.from(schema.evidenceAcBindings)
|
|
1402
|
-
.where(inArray(schema.evidenceAcBindings.acId, acIds))
|
|
1403
|
-
.all();
|
|
1404
|
-
return out.map((r) => ({
|
|
1405
|
-
id: r.id,
|
|
1406
|
-
evidenceAtomId: r.evidenceAtomId,
|
|
1407
|
-
acId: r.acId,
|
|
1408
|
-
bindingType: r.bindingType,
|
|
1409
|
-
createdAt: r.createdAt,
|
|
1410
|
-
}));
|
|
2127
|
+
return selectAcBindings(db, acIds);
|
|
1411
2128
|
},
|
|
1412
2129
|
// ---- AC bindings — writer (T10511, Validator SDK tools) ----
|
|
1413
2130
|
async insertAcBindings(rows) {
|
|
@@ -1436,6 +2153,14 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1436
2153
|
.run();
|
|
1437
2154
|
});
|
|
1438
2155
|
},
|
|
2156
|
+
// ---- AC bindings — orphan repair (T12790) ----
|
|
2157
|
+
async pruneOrphanAcBindings() {
|
|
2158
|
+
scope.assertActive();
|
|
2159
|
+
return accessor.transaction(async () => {
|
|
2160
|
+
scope.assertActive();
|
|
2161
|
+
return pruneOrphanAcBindings(db);
|
|
2162
|
+
});
|
|
2163
|
+
},
|
|
1439
2164
|
};
|
|
1440
2165
|
const result = await fn(bindTaskAccessorScope(tx, accessorScope));
|
|
1441
2166
|
await scope.pending;
|
|
@@ -1624,4 +2349,12 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1624
2349
|
};
|
|
1625
2350
|
return accessor;
|
|
1626
2351
|
}
|
|
2352
|
+
// T12341: the row-identity open pass writes through these chokepoint helpers.
|
|
2353
|
+
registerRowIdentityWriters({
|
|
2354
|
+
relinkAcUidNative,
|
|
2355
|
+
clearAcUidGraveyardNative,
|
|
2356
|
+
writeRowIdentityMetaNative,
|
|
2357
|
+
fillIdentityColumnNative,
|
|
2358
|
+
clearBirthFpNative,
|
|
2359
|
+
});
|
|
1627
2360
|
//# sourceMappingURL=sqlite-data-accessor.js.map
|