@cleocode/core 2026.9.24 → 2026.9.25
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/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/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/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 +17181 -15536
- package/dist/docs/export-document.js.map +3 -3
- 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 +31 -0
- package/dist/doctor/row-identity.d.ts.map +1 -0
- package/dist/doctor/row-identity.js +93 -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/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 +17045 -15400
- 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/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/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/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 +17049 -15404
- 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/skills/manifests/research.js +2 -0
- package/dist/skills/manifests/research.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/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 +17 -11
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +61 -38
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/display-id-alias.d.ts +529 -0
- package/dist/store/display-id-alias.d.ts.map +1 -0
- package/dist/store/display-id-alias.js +1063 -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 +21 -0
- package/dist/store/dual-scope-db.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/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/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 +198 -0
- package/dist/store/row-identity-registry.js.map +1 -0
- package/dist/store/row-identity.d.ts +390 -0
- package/dist/store/row-identity.d.ts.map +1 -0
- package/dist/store/row-identity.js +1240 -0
- package/dist/store/row-identity.js.map +1 -0
- package/dist/store/safety-data-accessor.d.ts +2 -0
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +4 -0
- 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/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 +4 -19
- package/dist/store/split-brain-import.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +279 -2
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +544 -44
- 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 +9 -1
- 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 +2 -0
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +4 -0
- 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.map +1 -1
- package/dist/system/health.js +3 -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/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/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.map +1 -1
- package/dist/tasks/evidence.js +14 -3
- package/dist/tasks/evidence.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/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/migrations/README.md +12 -0
- package/migrations/drizzle-cleo-project/20260928120000_t12341-row-uids/migration.sql +169 -0
- package/migrations/sync-journal/20260929140000_t12342-sync-clock/migration.sql +42 -0
- package/package.json +13 -13
- package/templates/CLEO-REFERENCE.md +4 -0
|
@@ -16,10 +16,13 @@ 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 { registerRowIdentityWriters } from './row-identity.js';
|
|
23
26
|
import { tasksAuditLog } from './schema/cleo-project/audit.js';
|
|
24
27
|
import { resolveCurrentSession } from './session-store.js';
|
|
25
28
|
import { closeDb, getDb, getNativeTasksDb } from './sqlite.js';
|
|
@@ -37,13 +40,67 @@ import { runHeartbeatWrite, withWriteRetry } from './with-retry.js';
|
|
|
37
40
|
*/
|
|
38
41
|
let _txSavepointCounter = 0;
|
|
39
42
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
43
|
+
* Evidence bindings for a set of CURRENT AC ids (T12341).
|
|
44
|
+
*
|
|
45
|
+
* A binding is matched through the criterion's uid (`ac_uid`), which survives
|
|
46
|
+
* an AC edit, and falls back to `ac_id` for a binding whose uid is not filled.
|
|
47
|
+
* A binding recorded against an older text of a criterion is reported under
|
|
48
|
+
* the criterion's current id, so callers keyed by AC id keep working, and is
|
|
49
|
+
* marked `stale` when the text hash it recorded (`ac_text_hash`) differs from
|
|
50
|
+
* the criterion's current text: evidence for different text never satisfies a
|
|
51
|
+
* gate (spec §8.2).
|
|
42
52
|
*/
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
async function selectAcBindings(db, acIds) {
|
|
54
|
+
if (acIds.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
const b = schema.evidenceAcBindings;
|
|
57
|
+
const ac = schema.taskAcceptanceCriteria;
|
|
58
|
+
const ids = acIds;
|
|
59
|
+
const columns = {
|
|
60
|
+
id: b.id,
|
|
61
|
+
evidenceAtomId: b.evidenceAtomId,
|
|
62
|
+
acId: b.acId,
|
|
63
|
+
currentAcId: ac.id,
|
|
64
|
+
currentAcText: ac.text,
|
|
65
|
+
recordedTextHash: b.acTextHash,
|
|
66
|
+
bindingType: b.bindingType,
|
|
67
|
+
createdAt: b.createdAt,
|
|
68
|
+
};
|
|
69
|
+
// Two indexed lookups rather than one OR across the join: by the current
|
|
70
|
+
// criterion's uid, then by the recorded ac_id.
|
|
71
|
+
const byUid = await db
|
|
72
|
+
.select(columns)
|
|
73
|
+
.from(b)
|
|
74
|
+
.innerJoin(ac, eq(ac.uid, b.acUid))
|
|
75
|
+
.where(inArray(ac.id, ids))
|
|
76
|
+
.all();
|
|
77
|
+
const byId = await db
|
|
78
|
+
.select(columns)
|
|
79
|
+
.from(b)
|
|
80
|
+
.leftJoin(ac, eq(ac.uid, b.acUid))
|
|
81
|
+
.where(inArray(b.acId, ids))
|
|
82
|
+
.all();
|
|
83
|
+
const rows = [...byUid, ...byId];
|
|
84
|
+
const wanted = new Set(ids);
|
|
85
|
+
const seen = new Set();
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const r of rows) {
|
|
88
|
+
if (seen.has(r.id))
|
|
89
|
+
continue;
|
|
90
|
+
seen.add(r.id);
|
|
91
|
+
const stale = r.recordedTextHash !== null &&
|
|
92
|
+
r.currentAcText !== null &&
|
|
93
|
+
acTextHash(r.currentAcText) !== r.recordedTextHash;
|
|
94
|
+
out.push({
|
|
95
|
+
id: r.id,
|
|
96
|
+
evidenceAtomId: r.evidenceAtomId,
|
|
97
|
+
acId: r.currentAcId && wanted.has(r.currentAcId) ? r.currentAcId : r.acId,
|
|
98
|
+
bindingType: r.bindingType,
|
|
99
|
+
createdAt: r.createdAt,
|
|
100
|
+
...(stale ? { stale: true } : {}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
47
104
|
}
|
|
48
105
|
// ---- Schema meta helpers ----
|
|
49
106
|
/** Read a JSON blob from the schema_meta table by key. */
|
|
@@ -132,6 +189,398 @@ export function advanceTaskIdSequence(nativeDb, floor) {
|
|
|
132
189
|
.get();
|
|
133
190
|
return row?.counter;
|
|
134
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Give the task with `uid` the display id `toId`: a compare-and-set on the
|
|
194
|
+
* stored version AND claim lease that advances the version (so a stale
|
|
195
|
+
* `--if-match` writer fails, #1698), with the claim columns carried unchanged
|
|
196
|
+
* (#1701), then every local reference rewritten. Runs in the caller's
|
|
197
|
+
* transaction with foreign keys deferred to its commit.
|
|
198
|
+
*
|
|
199
|
+
* A collision re-mint is a system write: it passes no claim guard and never
|
|
200
|
+
* takes or drops a lease, but the version bump makes the lease holder's next
|
|
201
|
+
* guarded write re-read. A caller acting for a session (a manual rename)
|
|
202
|
+
* passes `guard`: `expectedUpdatedAt` fails with `E_CONFLICT` when the task
|
|
203
|
+
* moved on, `claim` fails with `E_TASK_CLAIMED` when another session holds it.
|
|
204
|
+
*
|
|
205
|
+
* @param nativeDb - The project `cleo.db` handle holding the transaction.
|
|
206
|
+
* @param uid - Uid of the task to rename.
|
|
207
|
+
* @param toId - Its new display id.
|
|
208
|
+
* @param refs - Columns that hold task display ids.
|
|
209
|
+
* @param guard - Optional version / claim guard.
|
|
210
|
+
* @returns The id it had and the rows rewritten per `table.column`.
|
|
211
|
+
* @task T12341
|
|
212
|
+
* @task T12748
|
|
213
|
+
*/
|
|
214
|
+
export function renameTaskDisplayIdNative(nativeDb, uid, toId, refs, guard = {}) {
|
|
215
|
+
const current = nativeDb
|
|
216
|
+
.prepare(`SELECT id, updated_at AS updatedAt, created_at AS createdAt,
|
|
217
|
+
claimed_by_session AS claimedBySession, claimed_by_agent AS claimedByAgent,
|
|
218
|
+
claimed_at AS claimedAt, lease_expires_at AS leaseExpiresAt
|
|
219
|
+
FROM tasks_tasks WHERE uid = ?`)
|
|
220
|
+
.get(uid);
|
|
221
|
+
if (!current)
|
|
222
|
+
throw new Error(`No task with uid ${uid}`);
|
|
223
|
+
if (guard.expectedUpdatedAt !== undefined && guard.expectedUpdatedAt !== taskVersion(current)) {
|
|
224
|
+
throw taskConflictError(current.id, guard.expectedUpdatedAt, current);
|
|
225
|
+
}
|
|
226
|
+
if (guard.claim)
|
|
227
|
+
assertClaimAllowed(current.id, current, guard.claim, []);
|
|
228
|
+
nativeDb.exec('PRAGMA defer_foreign_keys = ON');
|
|
229
|
+
const changed = nativeDb
|
|
230
|
+
.prepare(`UPDATE tasks_tasks SET id = ?, updated_at = ?
|
|
231
|
+
WHERE uid = ? AND id = ? AND updated_at IS ? AND claimed_by_session IS ?
|
|
232
|
+
AND lease_expires_at IS ?`)
|
|
233
|
+
.run(toId, nextTaskVersion(current), uid, current.id, current.updatedAt, current.claimedBySession, current.leaseExpiresAt).changes;
|
|
234
|
+
if (Number(changed) !== 1) {
|
|
235
|
+
throw taskConflictError(current.id, taskVersion(current), null);
|
|
236
|
+
}
|
|
237
|
+
const rewritten = {};
|
|
238
|
+
const q = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
239
|
+
for (const ref of refs) {
|
|
240
|
+
const table = `main.${q(ref.table)}`;
|
|
241
|
+
const col = q(ref.column);
|
|
242
|
+
const n = ref.jsonArray
|
|
243
|
+
? nativeDb
|
|
244
|
+
.prepare(`UPDATE ${table} SET ${col} = (
|
|
245
|
+
SELECT json_group_array(CASE WHEN j.value = ? THEN ? ELSE j.value END)
|
|
246
|
+
FROM json_each(${table}.${col}) j)
|
|
247
|
+
WHERE json_valid(${col}) AND EXISTS (
|
|
248
|
+
SELECT 1 FROM json_each(${table}.${col}) j WHERE j.value = ?)`)
|
|
249
|
+
.run(current.id, toId, current.id).changes
|
|
250
|
+
: nativeDb.prepare(`UPDATE ${table} SET ${col} = ? WHERE ${col} = ?`).run(toId, current.id)
|
|
251
|
+
.changes;
|
|
252
|
+
if (Number(n) > 0)
|
|
253
|
+
rewritten[`${ref.table}.${ref.column}`] = Number(n);
|
|
254
|
+
}
|
|
255
|
+
for (const [key, n] of Object.entries(rederiveAcIdsNative(nativeDb, current.id, toId))) {
|
|
256
|
+
if (n > 0)
|
|
257
|
+
rewritten[key] = n;
|
|
258
|
+
}
|
|
259
|
+
return { fromId: current.id, rewritten };
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* After a task's display id changed from `fromId` to `toId`, re-derive the
|
|
263
|
+
* ids of the acceptance criteria derived from it (`buildAcRowId(taskId,
|
|
264
|
+
* identity)`): the task's own criteria, and a parent's child-projection
|
|
265
|
+
* criteria (`child:<id>`, whose source key is rewritten too). The binding and
|
|
266
|
+
* history rows that point at a re-derived id follow. A criterion whose id was
|
|
267
|
+
* not derived from the old id (an explicit id) keeps it. Without this, two
|
|
268
|
+
* tasks that once shared a display id keep identical ids for same-text
|
|
269
|
+
* criteria ("tests pass") and collide on every merge (T12799).
|
|
270
|
+
*
|
|
271
|
+
* @param nativeDb - The project `cleo.db` handle, inside the rename's transaction.
|
|
272
|
+
* @param fromId - The task's old display id.
|
|
273
|
+
* @param toId - Its new display id (the task row already carries it).
|
|
274
|
+
* @returns Rows rewritten per `table.column`.
|
|
275
|
+
* @task T12799
|
|
276
|
+
*/
|
|
277
|
+
export function rederiveAcIdsNative(nativeDb, fromId, toId) {
|
|
278
|
+
const count = {};
|
|
279
|
+
const bump = (key, n) => {
|
|
280
|
+
count[key] = (count[key] ?? 0) + Number(n);
|
|
281
|
+
};
|
|
282
|
+
const has = (table) => nativeDb
|
|
283
|
+
.prepare("SELECT 1 AS x FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
284
|
+
.get(table) !== undefined;
|
|
285
|
+
if (!has('tasks_task_acceptance_criteria'))
|
|
286
|
+
return count;
|
|
287
|
+
const oldChild = `child:${fromId}`;
|
|
288
|
+
const rows = nativeDb
|
|
289
|
+
.prepare(`SELECT id, task_id AS taskId, kind, text, source_key AS sourceKey
|
|
290
|
+
FROM tasks_task_acceptance_criteria WHERE task_id = ? OR source_key = ?`)
|
|
291
|
+
.all(toId, oldChild);
|
|
292
|
+
const followers = ['tasks_evidence_ac_bindings', 'tasks_task_acceptance_criteria_history'].filter(has);
|
|
293
|
+
for (const row of rows) {
|
|
294
|
+
const ownerBefore = row.taskId === toId ? fromId : row.taskId;
|
|
295
|
+
const sourceAfter = row.sourceKey === oldChild ? `child:${toId}` : row.sourceKey;
|
|
296
|
+
const identity = (source) => (row.kind === 'text' ? row.text : (source ?? ''));
|
|
297
|
+
const derived = row.id === buildAcRowId(ownerBefore, identity(row.sourceKey));
|
|
298
|
+
const newId = derived ? buildAcRowId(row.taskId, identity(sourceAfter)) : row.id;
|
|
299
|
+
if (newId === row.id && sourceAfter === row.sourceKey)
|
|
300
|
+
continue;
|
|
301
|
+
bump('tasks_task_acceptance_criteria.id', nativeDb
|
|
302
|
+
.prepare('UPDATE tasks_task_acceptance_criteria SET id = ?, source_key = ? WHERE id = ?')
|
|
303
|
+
.run(newId, sourceAfter, row.id).changes);
|
|
304
|
+
if (newId === row.id)
|
|
305
|
+
continue;
|
|
306
|
+
for (const table of followers) {
|
|
307
|
+
bump(`${table}.ac_id`, nativeDb.prepare(`UPDATE ${table} SET ac_id = ? WHERE ac_id = ?`).run(newId, row.id)
|
|
308
|
+
.changes);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return count;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Insert a display-id alias row (idempotent on its uid).
|
|
315
|
+
*
|
|
316
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
317
|
+
* @param row - The alias.
|
|
318
|
+
* @task T12341
|
|
319
|
+
*/
|
|
320
|
+
export function insertDisplayIdAliasNative(nativeDb, row) {
|
|
321
|
+
nativeDb
|
|
322
|
+
.prepare(`INSERT OR IGNORE INTO tasks_display_id_aliases
|
|
323
|
+
(uid, entity_table, display_id, entity_uid, reason, origin, displaced_hlc, created_at,
|
|
324
|
+
entity_birth_fp)
|
|
325
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
326
|
+
.run(row.uid, row.entityTable, row.displayId, row.entityUid, row.reason, row.origin, row.displacedHlc, row.createdAt, row.entityBirthFp);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Delete one display-id alias row (a local alias whose entity was re-keyed is
|
|
330
|
+
* re-inserted under its new entity uid by the caller).
|
|
331
|
+
*
|
|
332
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
333
|
+
* @param uid - The alias row's uid.
|
|
334
|
+
* @task T12341
|
|
335
|
+
*/
|
|
336
|
+
export function deleteDisplayIdAliasNative(nativeDb, uid) {
|
|
337
|
+
nativeDb.prepare('DELETE FROM tasks_display_id_aliases WHERE uid = ?').run(uid);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Insert a uid re-key alias row (idempotent on its uid).
|
|
341
|
+
*
|
|
342
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
343
|
+
* @param row - The alias.
|
|
344
|
+
* @task T12341
|
|
345
|
+
*/
|
|
346
|
+
export function insertUidAliasNative(nativeDb, row) {
|
|
347
|
+
nativeDb
|
|
348
|
+
.prepare(`INSERT OR IGNORE INTO tasks_uid_aliases
|
|
349
|
+
(uid, entity_table, old_uid, old_birth_fp, new_uid, origin, displaced_hlc, created_at)
|
|
350
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
351
|
+
.run(row.uid, row.entityTable, row.oldUid, row.oldBirthFp, row.newUid, row.origin, row.displacedHlc, row.createdAt);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Hold an incoming row the merge cannot place yet (replacing an older hold of
|
|
355
|
+
* the same row).
|
|
356
|
+
*
|
|
357
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
358
|
+
* @param row - The held row.
|
|
359
|
+
* @task T12341
|
|
360
|
+
*/
|
|
361
|
+
export function insertQuarantineNative(nativeDb, row) {
|
|
362
|
+
nativeDb
|
|
363
|
+
.prepare(`INSERT INTO tasks_identity_quarantine
|
|
364
|
+
(entity_table, uid, birth_fp, reason, contested_id, row_json, received_hlc, created_at)
|
|
365
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
366
|
+
ON CONFLICT(entity_table, uid, birth_fp) DO UPDATE SET
|
|
367
|
+
reason = excluded.reason, contested_id = excluded.contested_id,
|
|
368
|
+
row_json = excluded.row_json, received_hlc = excluded.received_hlc,
|
|
369
|
+
created_at = excluded.created_at`)
|
|
370
|
+
.run(row.entityTable, row.uid, row.birthFp, row.reason, row.contestedId, row.rowJson, row.receivedHlc, row.createdAt);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Drop a held row (released or superseded).
|
|
374
|
+
*
|
|
375
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
376
|
+
* @param key - The held row's table, uid and birth fingerprint.
|
|
377
|
+
* @task T12341
|
|
378
|
+
*/
|
|
379
|
+
export function deleteQuarantineNative(nativeDb, key) {
|
|
380
|
+
nativeDb
|
|
381
|
+
.prepare('DELETE FROM tasks_identity_quarantine WHERE entity_table = ? AND uid = ? AND birth_fp = ?')
|
|
382
|
+
.run(key.entityTable, key.uid, key.birthFp);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Write a row-identity meta value (the recipe marker, a re-mint record).
|
|
386
|
+
*
|
|
387
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
388
|
+
* @param key - Meta key.
|
|
389
|
+
* @param value - Meta value.
|
|
390
|
+
* @task T12341
|
|
391
|
+
*/
|
|
392
|
+
export function writeRowIdentityMetaNative(nativeDb, key, value) {
|
|
393
|
+
nativeDb
|
|
394
|
+
.prepare(`INSERT INTO tasks_row_identity_meta (key, value) VALUES (?, ?)
|
|
395
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`)
|
|
396
|
+
.run(key, value);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Hand a criterion back the uid its deleted predecessor carried (AC uid
|
|
400
|
+
* graveyard re-link, spec §6.5), with that predecessor's birth fingerprint.
|
|
401
|
+
*
|
|
402
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
403
|
+
* @param acId - The criterion to re-link (its uid is NULL).
|
|
404
|
+
* @param uid - The uid to hand back.
|
|
405
|
+
* @param birthFp - The fingerprint that goes with it, when recorded.
|
|
406
|
+
* @returns Rows changed (0 or 1).
|
|
407
|
+
* @task T12341
|
|
408
|
+
*/
|
|
409
|
+
export function relinkAcUidNative(nativeDb, acId, uid, birthFp) {
|
|
410
|
+
return Number(nativeDb
|
|
411
|
+
.prepare('UPDATE tasks_task_acceptance_criteria SET uid = ?, birth_fp = COALESCE(?, birth_fp) WHERE id = ? AND uid IS NULL')
|
|
412
|
+
.run(uid, birthFp, acId).changes);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Empty the AC uid graveyard (after the open pass consumed it).
|
|
416
|
+
*
|
|
417
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
418
|
+
* @task T12341
|
|
419
|
+
*/
|
|
420
|
+
export function clearAcUidGraveyardNative(nativeDb) {
|
|
421
|
+
nativeDb.exec('DELETE FROM tasks_ac_uid_graveyard');
|
|
422
|
+
}
|
|
423
|
+
/** Quote an identifier for the dynamic row-identity writes below. */
|
|
424
|
+
function quoteIdent(name) {
|
|
425
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Fill a NULL identity column (`uid`, `birth_fp`, a stored reference uid) of a
|
|
429
|
+
* declared table from its recipe SQL (the row-uid open pass, spec §6.1).
|
|
430
|
+
*
|
|
431
|
+
* @param nativeDb - The `cleo.db` handle holding the cold-open lease.
|
|
432
|
+
* @param table - Declared table.
|
|
433
|
+
* @param column - Identity column.
|
|
434
|
+
* @param valueSql - Recipe expression over `main."<table>"` columns.
|
|
435
|
+
* @param rowid - One row only (the clash fallback); all NULL rows otherwise.
|
|
436
|
+
* @returns Rows written.
|
|
437
|
+
* @task T12341
|
|
438
|
+
*/
|
|
439
|
+
export function fillIdentityColumnNative(nativeDb, table, column, valueSql, rowid) {
|
|
440
|
+
const col = quoteIdent(column);
|
|
441
|
+
const sql = `UPDATE main.${quoteIdent(table)} SET ${col} = ${valueSql} WHERE ${col} IS NULL`;
|
|
442
|
+
const changes = rowid === undefined
|
|
443
|
+
? nativeDb.prepare(sql).run().changes
|
|
444
|
+
: nativeDb.prepare(`${sql} AND rowid = ?`).run(rowid).changes;
|
|
445
|
+
return Number(changes);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Clear one pre-release birth fingerprint so the fill re-derives it (spec §12.1).
|
|
449
|
+
*
|
|
450
|
+
* @param nativeDb - The `cleo.db` handle holding the cold-open lease.
|
|
451
|
+
* @param table - Declared minted table.
|
|
452
|
+
* @param rowid - The row.
|
|
453
|
+
* @task T12341
|
|
454
|
+
*/
|
|
455
|
+
export function clearBirthFpNative(nativeDb, table, rowid) {
|
|
456
|
+
nativeDb
|
|
457
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET birth_fp = NULL WHERE rowid = ?`)
|
|
458
|
+
.run(rowid);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Insert one row into a declared identity table, with the columns the row
|
|
462
|
+
* carries that the table has (a row released from the identity quarantine).
|
|
463
|
+
*
|
|
464
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
465
|
+
* @param table - Declared table.
|
|
466
|
+
* @param row - Column → value.
|
|
467
|
+
* @task T12341
|
|
468
|
+
*/
|
|
469
|
+
export function insertIdentityRowNative(nativeDb, table, row) {
|
|
470
|
+
const have = new Set(nativeDb.prepare('SELECT name FROM pragma_table_info(?)').all(table).map((c) => c.name));
|
|
471
|
+
const cols = Object.keys(row).filter((c) => have.has(c));
|
|
472
|
+
nativeDb
|
|
473
|
+
.prepare(`INSERT INTO main.${quoteIdent(table)} (${cols.map(quoteIdent).join(', ')}) VALUES (${cols
|
|
474
|
+
.map(() => '?')
|
|
475
|
+
.join(', ')})`)
|
|
476
|
+
.run(...cols.map((c) => row[c] ?? null));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Give the row of `table` identified by (`oldUid`, `birthFp`) the uid
|
|
480
|
+
* `newUid` (a uid re-key; the birth fingerprint never changes).
|
|
481
|
+
*
|
|
482
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
483
|
+
* @param table - Declared minted table.
|
|
484
|
+
* @param oldUid - The colliding uid.
|
|
485
|
+
* @param birthFp - The row's birth fingerprint.
|
|
486
|
+
* @param newUid - Its new uid.
|
|
487
|
+
* @returns Rows changed (0 or 1).
|
|
488
|
+
* @task T12341
|
|
489
|
+
*/
|
|
490
|
+
export function setRowUidNative(nativeDb, table, oldUid, birthFp, newUid) {
|
|
491
|
+
return Number(nativeDb
|
|
492
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET uid = ? WHERE uid = ? AND birth_fp = ?`)
|
|
493
|
+
.run(newUid, oldUid, birthFp).changes);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Point a stored reference uid column (`ac_uid`) of the rows that reference
|
|
497
|
+
* the re-keyed row by key at its new uid. Scoped by the key column, so a row
|
|
498
|
+
* referencing the OTHER holder of a colliding uid is never touched.
|
|
499
|
+
*
|
|
500
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
501
|
+
* @param table - Table holding the column.
|
|
502
|
+
* @param column - The stored reference uid column.
|
|
503
|
+
* @param fromColumn - The column that references the row by key (`ac_id`).
|
|
504
|
+
* @param key - The re-keyed row's local key.
|
|
505
|
+
* @param oldUid - Old referenced uid.
|
|
506
|
+
* @param newUid - New referenced uid.
|
|
507
|
+
* @returns Rows changed.
|
|
508
|
+
* @task T12341
|
|
509
|
+
*/
|
|
510
|
+
export function rewriteStoredRefUidNative(nativeDb, table, column, fromColumn, key, oldUid, newUid) {
|
|
511
|
+
const col = quoteIdent(column);
|
|
512
|
+
return Number(nativeDb
|
|
513
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET ${col} = ? WHERE ${col} = ? AND ${quoteIdent(fromColumn)} = ?`)
|
|
514
|
+
.run(newUid, oldUid, key).changes);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Clear one row's uid so the natural recipe re-derives it (a uid re-key
|
|
518
|
+
* changed an endpoint's uid).
|
|
519
|
+
*
|
|
520
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
521
|
+
* @param table - Declared natural table.
|
|
522
|
+
* @param rowid - The row.
|
|
523
|
+
* @task T12341
|
|
524
|
+
*/
|
|
525
|
+
export function clearRowUidNative(nativeDb, table, rowid) {
|
|
526
|
+
nativeDb.prepare(`UPDATE main.${quoteIdent(table)} SET uid = NULL WHERE rowid = ?`).run(rowid);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Delete a row-identity meta value (a re-mint record that moved with a re-key).
|
|
530
|
+
*
|
|
531
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
532
|
+
* @param key - Meta key.
|
|
533
|
+
* @task T12341
|
|
534
|
+
*/
|
|
535
|
+
export function deleteRowIdentityMetaNative(nativeDb, key) {
|
|
536
|
+
nativeDb.prepare('DELETE FROM tasks_row_identity_meta WHERE key = ?').run(key);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Set a natural row's uid by its current uid (a re-key re-derives the natural
|
|
540
|
+
* uids keyed by the re-keyed row; a receiver applies the published values).
|
|
541
|
+
*
|
|
542
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
543
|
+
* @param table - Declared natural table.
|
|
544
|
+
* @param oldUid - Current uid.
|
|
545
|
+
* @param newUid - New uid.
|
|
546
|
+
* @returns Rows changed.
|
|
547
|
+
* @task T12341
|
|
548
|
+
*/
|
|
549
|
+
export function setNaturalUidNative(nativeDb, table, oldUid, newUid) {
|
|
550
|
+
return Number(nativeDb
|
|
551
|
+
.prepare(`UPDATE main.${quoteIdent(table)} SET uid = ? WHERE uid = ?`)
|
|
552
|
+
.run(newUid, oldUid).changes);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Rewrite a held row (identity quarantine) under a new uid: a uid re-key of a
|
|
556
|
+
* row this replica holds but has not placed.
|
|
557
|
+
*
|
|
558
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
559
|
+
* @param key - The held row's table, uid and birth fingerprint.
|
|
560
|
+
* @param newUid - Its new uid.
|
|
561
|
+
* @param rowJson - Its wire form with the new uid.
|
|
562
|
+
* @task T12341
|
|
563
|
+
*/
|
|
564
|
+
export function rekeyQuarantineNative(nativeDb, key, newUid, rowJson) {
|
|
565
|
+
nativeDb
|
|
566
|
+
.prepare(`UPDATE tasks_identity_quarantine SET uid = ?, row_json = ?
|
|
567
|
+
WHERE entity_table = ? AND uid = ? AND birth_fp = ?`)
|
|
568
|
+
.run(newUid, rowJson, key.entityTable, key.uid, key.birthFp);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Add labels to a task's label junction (idempotent), for writers that
|
|
572
|
+
* insert a task with raw SQL (the sentient proposal ticks).
|
|
573
|
+
*
|
|
574
|
+
* @param nativeDb - The project `cleo.db` handle.
|
|
575
|
+
* @param taskId - The task.
|
|
576
|
+
* @param labels - Labels to add.
|
|
577
|
+
* @task T12341
|
|
578
|
+
*/
|
|
579
|
+
export function addTaskLabelsNative(nativeDb, taskId, labels) {
|
|
580
|
+
const stmt = nativeDb.prepare('INSERT OR IGNORE INTO tasks_task_labels (task_id, label) VALUES (?, ?)');
|
|
581
|
+
for (const label of labels)
|
|
582
|
+
stmt.run(taskId, label);
|
|
583
|
+
}
|
|
135
584
|
// One queue per shared native handle, independent of accessor identity. These
|
|
136
585
|
// hold coordination state only; the ProjectStore remains the owner of handles.
|
|
137
586
|
const taskTransactionQueue = new WeakMap();
|
|
@@ -607,6 +1056,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
607
1056
|
createdAt: schema.taskAcceptanceCriteria.createdAt,
|
|
608
1057
|
updatedAt: schema.taskAcceptanceCriteria.updatedAt,
|
|
609
1058
|
contentHash: schema.taskAcceptanceCriteria.contentHash,
|
|
1059
|
+
uid: schema.taskAcceptanceCriteria.uid,
|
|
1060
|
+
birthFp: schema.taskAcceptanceCriteria.birthFp,
|
|
610
1061
|
})
|
|
611
1062
|
.from(schema.taskAcceptanceCriteria)
|
|
612
1063
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
@@ -624,31 +1075,20 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
624
1075
|
createdAt: r.createdAt,
|
|
625
1076
|
updatedAt: r.updatedAt ?? null,
|
|
626
1077
|
contentHash: r.contentHash ?? null,
|
|
1078
|
+
uid: r.uid ?? null,
|
|
1079
|
+
birthFp: r.birthFp ?? null,
|
|
627
1080
|
}));
|
|
628
1081
|
},
|
|
629
1082
|
// ---- AC bindings (T10509 — AC-coverage gate) ----
|
|
630
1083
|
async getAcBindings(acIds) {
|
|
631
1084
|
if (acIds.length === 0)
|
|
632
1085
|
return [];
|
|
1086
|
+
return selectAcBindings(await getDb(cwd), acIds);
|
|
1087
|
+
},
|
|
1088
|
+
// ---- AC bindings — orphan scan (T12790) ----
|
|
1089
|
+
async findOrphanAcBindings() {
|
|
633
1090
|
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
|
-
}));
|
|
1091
|
+
return selectOrphanAcBindings(db);
|
|
652
1092
|
},
|
|
653
1093
|
async archiveSingleTask(taskId, fields) {
|
|
654
1094
|
return accessor.transaction(async () => {
|
|
@@ -692,6 +1132,9 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
692
1132
|
.delete(schema.taskDependencies)
|
|
693
1133
|
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
694
1134
|
.run();
|
|
1135
|
+
// T12790: the bindings table has no FK to the AC rows that cascade
|
|
1136
|
+
// from tasks_tasks — remove (and audit) them explicitly.
|
|
1137
|
+
await pruneAcBindingsForTask(db, taskId);
|
|
695
1138
|
// Delete the task itself
|
|
696
1139
|
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
697
1140
|
});
|
|
@@ -1242,6 +1685,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1242
1685
|
.delete(schema.taskDependencies)
|
|
1243
1686
|
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
1244
1687
|
.run();
|
|
1688
|
+
// T12790: bindings of the cascading AC rows (no FK).
|
|
1689
|
+
await pruneAcBindingsForTask(db, taskId);
|
|
1245
1690
|
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
1246
1691
|
});
|
|
1247
1692
|
},
|
|
@@ -1321,6 +1766,9 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1321
1766
|
projection: r.projection ?? 'legacy',
|
|
1322
1767
|
text: r.text,
|
|
1323
1768
|
contentHash: r.contentHash ?? null,
|
|
1769
|
+
// A kept criterion keeps its uid (T12341); else one is minted.
|
|
1770
|
+
...(r.uid ? { uid: r.uid } : {}),
|
|
1771
|
+
...(r.uid && r.birthFp ? { birthFp: r.birthFp } : {}),
|
|
1324
1772
|
})))
|
|
1325
1773
|
.run();
|
|
1326
1774
|
});
|
|
@@ -1340,6 +1788,8 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1340
1788
|
createdAt: schema.taskAcceptanceCriteria.createdAt,
|
|
1341
1789
|
updatedAt: schema.taskAcceptanceCriteria.updatedAt,
|
|
1342
1790
|
contentHash: schema.taskAcceptanceCriteria.contentHash,
|
|
1791
|
+
uid: schema.taskAcceptanceCriteria.uid,
|
|
1792
|
+
birthFp: schema.taskAcceptanceCriteria.birthFp,
|
|
1343
1793
|
})
|
|
1344
1794
|
.from(schema.taskAcceptanceCriteria)
|
|
1345
1795
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
@@ -1357,18 +1807,70 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1357
1807
|
createdAt: r.createdAt,
|
|
1358
1808
|
updatedAt: r.updatedAt ?? null,
|
|
1359
1809
|
contentHash: r.contentHash ?? null,
|
|
1810
|
+
uid: r.uid ?? null,
|
|
1811
|
+
birthFp: r.birthFp ?? null,
|
|
1360
1812
|
}));
|
|
1361
1813
|
},
|
|
1362
1814
|
async deleteAcRowsForTask(taskId) {
|
|
1363
1815
|
scope.assertActive();
|
|
1364
1816
|
return accessor.transaction(async () => {
|
|
1365
1817
|
scope.assertActive();
|
|
1818
|
+
// T12790: remove (and audit) the departing rows' bindings.
|
|
1819
|
+
await pruneAcBindingsForTask(db, taskId, 'ac-removed');
|
|
1366
1820
|
await db
|
|
1367
1821
|
.delete(schema.taskAcceptanceCriteria)
|
|
1368
1822
|
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
1369
1823
|
.run();
|
|
1370
1824
|
});
|
|
1371
1825
|
},
|
|
1826
|
+
async deleteAcRowsByIds(taskId, ids, keepBindingsForUids = []) {
|
|
1827
|
+
scope.assertActive();
|
|
1828
|
+
if (ids.length === 0)
|
|
1829
|
+
return;
|
|
1830
|
+
return accessor.transaction(async () => {
|
|
1831
|
+
scope.assertActive();
|
|
1832
|
+
// T12790: only ids this task actually owns leave the set —
|
|
1833
|
+
// prune (and audit) exactly their bindings.
|
|
1834
|
+
const owned = await db
|
|
1835
|
+
.select({ id: schema.taskAcceptanceCriteria.id })
|
|
1836
|
+
.from(schema.taskAcceptanceCriteria)
|
|
1837
|
+
.where(and(eq(schema.taskAcceptanceCriteria.taskId, taskId), inArray(schema.taskAcceptanceCriteria.id, ids)))
|
|
1838
|
+
.all();
|
|
1839
|
+
await pruneAcBindingsForAcIds(db, taskId, owned.map((r) => r.id), 'ac-removed', keepBindingsForUids);
|
|
1840
|
+
await db
|
|
1841
|
+
.delete(schema.taskAcceptanceCriteria)
|
|
1842
|
+
.where(and(eq(schema.taskAcceptanceCriteria.taskId, taskId), inArray(schema.taskAcceptanceCriteria.id, ids)))
|
|
1843
|
+
.run();
|
|
1844
|
+
});
|
|
1845
|
+
},
|
|
1846
|
+
async updateAcRows(rows) {
|
|
1847
|
+
scope.assertActive();
|
|
1848
|
+
if (rows.length === 0)
|
|
1849
|
+
return;
|
|
1850
|
+
return accessor.transaction(async () => {
|
|
1851
|
+
scope.assertActive();
|
|
1852
|
+
const updatedAt = new Date().toISOString();
|
|
1853
|
+
for (const r of rows) {
|
|
1854
|
+
const result = await db
|
|
1855
|
+
.update(schema.taskAcceptanceCriteria)
|
|
1856
|
+
.set({
|
|
1857
|
+
ordinal: r.ordinal,
|
|
1858
|
+
kind: r.kind ?? 'text',
|
|
1859
|
+
sourceKey: r.sourceKey ?? `text:${r.ordinal}`,
|
|
1860
|
+
targetTaskId: r.targetTaskId ?? null,
|
|
1861
|
+
projection: r.projection ?? 'legacy',
|
|
1862
|
+
text: r.text,
|
|
1863
|
+
contentHash: r.contentHash ?? null,
|
|
1864
|
+
updatedAt,
|
|
1865
|
+
})
|
|
1866
|
+
.where(and(eq(schema.taskAcceptanceCriteria.id, r.id), eq(schema.taskAcceptanceCriteria.taskId, r.taskId)))
|
|
1867
|
+
.run();
|
|
1868
|
+
if (Number(result.changes) === 0) {
|
|
1869
|
+
throw new CleoError(ExitCode.VALIDATION_ERROR, `Acceptance criterion ${r.id} does not exist on task ${r.taskId}; refusing to update it`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
},
|
|
1372
1874
|
async appendAcHistory(rows) {
|
|
1373
1875
|
scope.assertActive();
|
|
1374
1876
|
return accessor.transaction(async () => {
|
|
@@ -1381,6 +1883,7 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1381
1883
|
acId: r.acId,
|
|
1382
1884
|
previousText: r.previousText,
|
|
1383
1885
|
reason: r.reason,
|
|
1886
|
+
...(r.acUid ? { acUid: r.acUid } : {}),
|
|
1384
1887
|
})))
|
|
1385
1888
|
.run();
|
|
1386
1889
|
});
|
|
@@ -1388,26 +1891,7 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1388
1891
|
// ---- AC bindings (T10509 — AC-coverage gate) ----
|
|
1389
1892
|
async getAcBindings(acIds) {
|
|
1390
1893
|
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
|
-
}));
|
|
1894
|
+
return selectAcBindings(db, acIds);
|
|
1411
1895
|
},
|
|
1412
1896
|
// ---- AC bindings — writer (T10511, Validator SDK tools) ----
|
|
1413
1897
|
async insertAcBindings(rows) {
|
|
@@ -1436,6 +1920,14 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1436
1920
|
.run();
|
|
1437
1921
|
});
|
|
1438
1922
|
},
|
|
1923
|
+
// ---- AC bindings — orphan repair (T12790) ----
|
|
1924
|
+
async pruneOrphanAcBindings() {
|
|
1925
|
+
scope.assertActive();
|
|
1926
|
+
return accessor.transaction(async () => {
|
|
1927
|
+
scope.assertActive();
|
|
1928
|
+
return pruneOrphanAcBindings(db);
|
|
1929
|
+
});
|
|
1930
|
+
},
|
|
1439
1931
|
};
|
|
1440
1932
|
const result = await fn(bindTaskAccessorScope(tx, accessorScope));
|
|
1441
1933
|
await scope.pending;
|
|
@@ -1624,4 +2116,12 @@ async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
|
1624
2116
|
};
|
|
1625
2117
|
return accessor;
|
|
1626
2118
|
}
|
|
2119
|
+
// T12341: the row-identity open pass writes through these chokepoint helpers.
|
|
2120
|
+
registerRowIdentityWriters({
|
|
2121
|
+
relinkAcUidNative,
|
|
2122
|
+
clearAcUidGraveyardNative,
|
|
2123
|
+
writeRowIdentityMetaNative,
|
|
2124
|
+
fillIdentityColumnNative,
|
|
2125
|
+
clearBirthFpNative,
|
|
2126
|
+
});
|
|
1627
2127
|
//# sourceMappingURL=sqlite-data-accessor.js.map
|