@cleocode/core 2026.10.1 → 2026.10.3
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/build-provenance.json +2 -2
- package/dist/cloud/http.d.ts +2 -0
- package/dist/cloud/http.d.ts.map +1 -1
- package/dist/cloud/http.js +3 -0
- package/dist/cloud/http.js.map +1 -1
- package/dist/cloud/journal.d.ts +9 -5
- package/dist/cloud/journal.d.ts.map +1 -1
- package/dist/cloud/journal.js +29 -8
- package/dist/cloud/journal.js.map +1 -1
- package/dist/cloud/manifest-check.d.ts +140 -4
- package/dist/cloud/manifest-check.d.ts.map +1 -1
- package/dist/cloud/manifest-check.js +322 -10
- package/dist/cloud/manifest-check.js.map +1 -1
- package/dist/cloud/nexus-attach.d.ts +25 -0
- package/dist/cloud/nexus-attach.d.ts.map +1 -1
- package/dist/cloud/nexus-attach.js +81 -6
- package/dist/cloud/nexus-attach.js.map +1 -1
- package/dist/cloud/nexus-cloud-activity.d.ts +37 -0
- package/dist/cloud/nexus-cloud-activity.d.ts.map +1 -0
- package/dist/cloud/nexus-cloud-activity.js +79 -0
- package/dist/cloud/nexus-cloud-activity.js.map +1 -0
- package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
- package/dist/cloud/nexus-cloud-status.js +36 -0
- package/dist/cloud/nexus-cloud-status.js.map +1 -1
- package/dist/cloud/nexus-device.d.ts +1 -1
- package/dist/cloud/nexus-enrol.d.ts.map +1 -1
- package/dist/cloud/nexus-enrol.js +5 -0
- package/dist/cloud/nexus-enrol.js.map +1 -1
- package/dist/cloud/nexus-home.d.ts +74 -0
- package/dist/cloud/nexus-home.d.ts.map +1 -0
- package/dist/cloud/nexus-home.js +97 -0
- package/dist/cloud/nexus-home.js.map +1 -0
- package/dist/cloud/nexus-vault-keys.d.ts +130 -0
- package/dist/cloud/nexus-vault-keys.d.ts.map +1 -0
- package/dist/cloud/nexus-vault-keys.js +347 -0
- package/dist/cloud/nexus-vault-keys.js.map +1 -0
- package/dist/cloud/nexus-vault-state.d.ts +121 -0
- package/dist/cloud/nexus-vault-state.d.ts.map +1 -0
- package/dist/cloud/nexus-vault-state.js +274 -0
- package/dist/cloud/nexus-vault-state.js.map +1 -0
- package/dist/cloud/nexus-vault.d.ts +160 -0
- package/dist/cloud/nexus-vault.d.ts.map +1 -0
- package/dist/cloud/nexus-vault.js +1745 -0
- package/dist/cloud/nexus-vault.js.map +1 -0
- package/dist/cloud/signing.d.ts +24 -8
- package/dist/cloud/signing.d.ts.map +1 -1
- package/dist/cloud/signing.js +61 -15
- package/dist/cloud/signing.js.map +1 -1
- package/dist/docs/export-document.js +8137 -3318
- package/dist/docs/export-document.js.map +3 -3
- package/dist/doctor/migrations.d.ts +75 -0
- package/dist/doctor/migrations.d.ts.map +1 -0
- package/dist/doctor/migrations.js +136 -0
- package/dist/doctor/migrations.js.map +1 -0
- package/dist/doctor/sync-sealer.d.ts +48 -0
- package/dist/doctor/sync-sealer.d.ts.map +1 -0
- package/dist/doctor/sync-sealer.js +109 -0
- package/dist/doctor/sync-sealer.js.map +1 -0
- package/dist/doctor/sync-triggers.d.ts +53 -0
- package/dist/doctor/sync-triggers.d.ts.map +1 -0
- package/dist/doctor/sync-triggers.js +157 -0
- package/dist/doctor/sync-triggers.js.map +1 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +5 -1
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/llm/plugin-facade.js +8442 -3623
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/nexus/projects-clean.d.ts +3 -0
- package/dist/nexus/projects-clean.d.ts.map +1 -1
- package/dist/nexus/projects-clean.js +9 -3
- package/dist/nexus/projects-clean.js.map +1 -1
- package/dist/nexus/registry-roots.d.ts +3 -1
- package/dist/nexus/registry-roots.d.ts.map +1 -1
- package/dist/nexus/registry-roots.js +5 -2
- package/dist/nexus/registry-roots.js.map +1 -1
- package/dist/nexus/registry.d.ts +8 -1
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +24 -10
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/workspace.d.ts +4 -1
- package/dist/nexus/workspace.d.ts.map +1 -1
- package/dist/nexus/workspace.js +10 -4
- package/dist/nexus/workspace.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +7 -2
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestrate/worker-verify.d.ts +121 -12
- package/dist/orchestrate/worker-verify.d.ts.map +1 -1
- package/dist/orchestrate/worker-verify.js +172 -21
- package/dist/orchestrate/worker-verify.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +15 -9
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +9 -0
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +9 -2
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/resources/backend.d.ts +15 -3
- package/dist/resources/backend.d.ts.map +1 -1
- package/dist/resources/backend.js +7 -3
- package/dist/resources/backend.js.map +1 -1
- package/dist/resources/darwin-backend.d.ts +142 -0
- package/dist/resources/darwin-backend.d.ts.map +1 -0
- package/dist/resources/darwin-backend.js +288 -0
- package/dist/resources/darwin-backend.js.map +1 -0
- package/dist/resources/governor.d.ts +44 -1
- package/dist/resources/governor.d.ts.map +1 -1
- package/dist/resources/governor.js +100 -36
- package/dist/resources/governor.js.map +1 -1
- package/dist/resources/heavy-command.d.ts +132 -0
- package/dist/resources/heavy-command.d.ts.map +1 -0
- package/dist/resources/heavy-command.js +768 -0
- package/dist/resources/heavy-command.js.map +1 -0
- package/dist/resources/monitor.d.ts +50 -1
- package/dist/resources/monitor.d.ts.map +1 -1
- package/dist/resources/monitor.js +83 -1
- package/dist/resources/monitor.js.map +1 -1
- package/dist/resources/run-admission.d.ts +303 -0
- package/dist/resources/run-admission.d.ts.map +1 -0
- package/dist/resources/run-admission.js +648 -0
- package/dist/resources/run-admission.js.map +1 -0
- package/dist/resources/run-class.d.ts +111 -0
- package/dist/resources/run-class.d.ts.map +1 -0
- package/dist/resources/run-class.js +658 -0
- package/dist/resources/run-class.js.map +1 -0
- package/dist/resources/run-governed.d.ts +158 -0
- package/dist/resources/run-governed.d.ts.map +1 -0
- package/dist/resources/run-governed.js +406 -0
- package/dist/resources/run-governed.js.map +1 -0
- package/dist/resources/slot-holder.d.ts +267 -0
- package/dist/resources/slot-holder.d.ts.map +1 -0
- package/dist/resources/slot-holder.js +505 -0
- package/dist/resources/slot-holder.js.map +1 -0
- package/dist/resources/tool-groups.d.ts +74 -0
- package/dist/resources/tool-groups.d.ts.map +1 -0
- package/dist/resources/tool-groups.js +118 -0
- package/dist/resources/tool-groups.js.map +1 -0
- package/dist/routing/build-command-groups.d.ts.map +1 -1
- package/dist/routing/build-command-groups.js +1 -0
- package/dist/routing/build-command-groups.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +8446 -3627
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/cross-project-hygiene.d.ts +3 -2
- package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
- package/dist/sentient/cross-project-hygiene.js +5 -2
- package/dist/sentient/cross-project-hygiene.js.map +1 -1
- package/dist/sentient/state.d.ts +7 -0
- package/dist/sentient/state.d.ts.map +1 -1
- package/dist/sentient/state.js.map +1 -1
- package/dist/sentient/tick.d.ts +14 -2
- package/dist/sentient/tick.d.ts.map +1 -1
- package/dist/sentient/tick.js +63 -4
- package/dist/sentient/tick.js.map +1 -1
- package/dist/store/backup-recover.d.ts.map +1 -1
- package/dist/store/backup-recover.js +5 -0
- package/dist/store/backup-recover.js.map +1 -1
- package/dist/store/data-accessor.d.ts +1 -0
- package/dist/store/data-accessor.d.ts.map +1 -1
- package/dist/store/data-accessor.js +7 -0
- package/dist/store/data-accessor.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +23 -1
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +126 -36
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts.map +1 -1
- package/dist/store/exodus/recovery.js +15 -0
- 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 +5 -0
- package/dist/store/legacy-tasks-lineage.js.map +1 -1
- package/dist/store/lock.d.ts +8 -1
- package/dist/store/lock.d.ts.map +1 -1
- package/dist/store/lock.js +10 -2
- package/dist/store/lock.js.map +1 -1
- package/dist/store/migration-manager.d.ts +5 -0
- package/dist/store/migration-manager.d.ts.map +1 -1
- package/dist/store/migration-manager.js +11 -0
- package/dist/store/migration-manager.js.map +1 -1
- package/dist/store/migration-runner.d.ts +158 -0
- package/dist/store/migration-runner.d.ts.map +1 -0
- package/dist/store/migration-runner.js +493 -0
- package/dist/store/migration-runner.js.map +1 -0
- package/dist/store/portable-bundle-import.d.ts +15 -1
- package/dist/store/portable-bundle-import.d.ts.map +1 -1
- package/dist/store/portable-bundle-import.js +24 -4
- package/dist/store/portable-bundle-import.js.map +1 -1
- package/dist/store/portable-bundle-relocate.d.ts +43 -0
- package/dist/store/portable-bundle-relocate.d.ts.map +1 -1
- package/dist/store/portable-bundle-relocate.js +79 -5
- package/dist/store/portable-bundle-relocate.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts +42 -0
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +132 -25
- package/dist/store/portable-bundle-scan.js.map +1 -1
- package/dist/store/portable-bundle.d.ts +40 -0
- package/dist/store/portable-bundle.d.ts.map +1 -1
- package/dist/store/portable-bundle.js +43 -7
- package/dist/store/portable-bundle.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts +137 -7
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +364 -9
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/row-identity.d.ts.map +1 -1
- package/dist/store/row-identity.js +9 -10
- package/dist/store/row-identity.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +10 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +31 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +5 -1
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sync/capture.d.ts +203 -0
- package/dist/store/sync/capture.d.ts.map +1 -0
- package/dist/store/sync/capture.js +562 -0
- package/dist/store/sync/capture.js.map +1 -0
- package/dist/store/sync/clock-store.d.ts.map +1 -1
- package/dist/store/sync/clock-store.js +2 -0
- package/dist/store/sync/clock-store.js.map +1 -1
- package/dist/store/sync/fk-orphans.d.ts +133 -0
- package/dist/store/sync/fk-orphans.d.ts.map +1 -0
- package/dist/store/sync/fk-orphans.js +265 -0
- package/dist/store/sync/fk-orphans.js.map +1 -0
- package/dist/store/sync/flags.d.ts +7 -0
- package/dist/store/sync/flags.d.ts.map +1 -1
- package/dist/store/sync/flags.js +16 -0
- package/dist/store/sync/flags.js.map +1 -1
- package/dist/store/sync/hlc.d.ts.map +1 -1
- package/dist/store/sync/hlc.js +4 -0
- package/dist/store/sync/hlc.js.map +1 -1
- package/dist/store/sync/identity-fill.d.ts +45 -0
- package/dist/store/sync/identity-fill.d.ts.map +1 -0
- package/dist/store/sync/identity-fill.js +85 -0
- package/dist/store/sync/identity-fill.js.map +1 -0
- package/dist/store/sync/machinery.d.ts +27 -0
- package/dist/store/sync/machinery.d.ts.map +1 -0
- package/dist/store/sync/machinery.js +44 -0
- package/dist/store/sync/machinery.js.map +1 -0
- package/dist/store/sync/netting.d.ts +120 -0
- package/dist/store/sync/netting.d.ts.map +1 -0
- package/dist/store/sync/netting.js +336 -0
- package/dist/store/sync/netting.js.map +1 -0
- package/dist/store/sync/replica.d.ts +27 -2
- package/dist/store/sync/replica.d.ts.map +1 -1
- package/dist/store/sync/replica.js +47 -3
- package/dist/store/sync/replica.js.map +1 -1
- package/dist/store/sync/schema.d.ts +12 -0
- package/dist/store/sync/schema.d.ts.map +1 -1
- package/dist/store/sync/schema.js +21 -0
- package/dist/store/sync/schema.js.map +1 -1
- package/dist/store/sync/seal-backlog.d.ts +21 -0
- package/dist/store/sync/seal-backlog.d.ts.map +1 -0
- package/dist/store/sync/seal-backlog.js +24 -0
- package/dist/store/sync/seal-backlog.js.map +1 -0
- package/dist/store/sync/sealer-values.d.ts +34 -0
- package/dist/store/sync/sealer-values.d.ts.map +1 -0
- package/dist/store/sync/sealer-values.js +53 -0
- package/dist/store/sync/sealer-values.js.map +1 -0
- package/dist/store/sync/sealer.d.ts +145 -0
- package/dist/store/sync/sealer.d.ts.map +1 -0
- package/dist/store/sync/sealer.js +906 -0
- package/dist/store/sync/sealer.js.map +1 -0
- package/dist/store/sync/structural.d.ts +63 -0
- package/dist/store/sync/structural.d.ts.map +1 -0
- package/dist/store/sync/structural.js +180 -0
- package/dist/store/sync/structural.js.map +1 -0
- package/dist/store/sync/timestamps.d.ts +92 -0
- package/dist/store/sync/timestamps.d.ts.map +1 -0
- package/dist/store/sync/timestamps.js +228 -0
- package/dist/store/sync/timestamps.js.map +1 -0
- package/dist/store/sync/tombstones.d.ts +75 -0
- package/dist/store/sync/tombstones.d.ts.map +1 -0
- package/dist/store/sync/tombstones.js +103 -0
- package/dist/store/sync/tombstones.js.map +1 -0
- package/dist/store/sync/trigger-classes.d.ts +221 -0
- package/dist/store/sync/trigger-classes.d.ts.map +1 -0
- package/dist/store/sync/trigger-classes.js +441 -0
- package/dist/store/sync/trigger-classes.js.map +1 -0
- package/dist/store/sync/trigger-suspend-at-path.d.ts +23 -0
- package/dist/store/sync/trigger-suspend-at-path.d.ts.map +1 -0
- package/dist/store/sync/trigger-suspend-at-path.js +44 -0
- package/dist/store/sync/trigger-suspend-at-path.js.map +1 -0
- package/dist/store/sync/writer-version.d.ts +43 -0
- package/dist/store/sync/writer-version.d.ts.map +1 -0
- package/dist/store/sync/writer-version.js +90 -0
- package/dist/store/sync/writer-version.js.map +1 -0
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +102 -0
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/trigger-suspend-schema.d.ts +44 -0
- package/dist/store/trigger-suspend-schema.d.ts.map +1 -0
- package/dist/store/trigger-suspend-schema.js +25 -0
- package/dist/store/trigger-suspend-schema.js.map +1 -0
- package/dist/store/vault-manifest.d.ts +365 -0
- package/dist/store/vault-manifest.d.ts.map +1 -0
- package/dist/store/vault-manifest.js +812 -0
- package/dist/store/vault-manifest.js.map +1 -0
- package/dist/store/writer-lease.d.ts +31 -0
- package/dist/store/writer-lease.d.ts.map +1 -1
- package/dist/store/writer-lease.js +65 -2
- package/dist/store/writer-lease.js.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +21 -0
- package/dist/system/health.js.map +1 -1
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +3 -1
- package/dist/system/project-health.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +58 -0
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +75 -6
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +150 -23
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +320 -32
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/change-set.d.ts +96 -0
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +169 -4
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete-ci.d.ts +115 -0
- package/dist/tasks/complete-ci.d.ts.map +1 -0
- package/dist/tasks/complete-ci.js +171 -0
- package/dist/tasks/complete-ci.js.map +1 -0
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +50 -17
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/compute-task-view.d.ts.map +1 -1
- package/dist/tasks/compute-task-view.js +7 -0
- package/dist/tasks/compute-task-view.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +51 -30
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/done-record.d.ts +6 -0
- package/dist/tasks/done-record.d.ts.map +1 -1
- package/dist/tasks/done-record.js +9 -3
- package/dist/tasks/done-record.js.map +1 -1
- package/dist/tasks/evidence.d.ts +3 -2
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +143 -12
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-notes.d.ts +22 -0
- package/dist/tasks/gate-notes.d.ts.map +1 -0
- package/dist/tasks/gate-notes.js +33 -0
- package/dist/tasks/gate-notes.js.map +1 -0
- package/dist/tasks/index.d.ts +2 -1
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +2 -1
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/test-run-binding.d.ts +117 -0
- package/dist/tasks/test-run-binding.d.ts.map +1 -0
- package/dist/tasks/test-run-binding.js +365 -0
- package/dist/tasks/test-run-binding.js.map +1 -0
- package/dist/tasks/tool-cache-env.d.ts +123 -0
- package/dist/tasks/tool-cache-env.d.ts.map +1 -0
- package/dist/tasks/tool-cache-env.js +287 -0
- package/dist/tasks/tool-cache-env.js.map +1 -0
- package/dist/tasks/tool-cache-failed-first.d.ts +155 -0
- package/dist/tasks/tool-cache-failed-first.d.ts.map +1 -0
- package/dist/tasks/tool-cache-failed-first.js +285 -0
- package/dist/tasks/tool-cache-failed-first.js.map +1 -0
- package/dist/tasks/tool-cache.d.ts +375 -55
- package/dist/tasks/tool-cache.d.ts.map +1 -1
- package/dist/tasks/tool-cache.js +912 -303
- package/dist/tasks/tool-cache.js.map +1 -1
- package/dist/tasks/tool-resolver.d.ts +4 -3
- package/dist/tasks/tool-resolver.d.ts.map +1 -1
- package/dist/tasks/tool-resolver.js +15 -4
- package/dist/tasks/tool-resolver.js.map +1 -1
- package/dist/tasks/tool-semaphore.d.ts +67 -7
- package/dist/tasks/tool-semaphore.d.ts.map +1 -1
- package/dist/tasks/tool-semaphore.js +134 -56
- package/dist/tasks/tool-semaphore.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +4 -0
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +6 -0
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +8 -0
- package/dist/validation/engine-ops.js.map +1 -1
- package/migrations/drizzle-cleo-project/20260930170000_t12819-trigger-suspend-clause/migration.sql +367 -0
- package/migrations/sync-journal/20260930120000_t12343-capture/migration.sql +47 -0
- package/migrations/sync-journal/20261001170000_t12984-sealer/migration.sql +101 -0
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +5 -5
- package/templates/CLEO-REFERENCE.md +8 -4
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniform timestamp format (journal spec §1.8, R3, M4; T12986, S3c).
|
|
3
|
+
*
|
|
4
|
+
* - {@link canonicalStoreTimestamp}: the strict, pure canonicalizer. Accepts
|
|
5
|
+
* ISO-8601 with `Z` or an explicit `±hh:mm` offset (any fractional
|
|
6
|
+
* precision) and SQLite's `YYYY-MM-DD HH:MM:SS[.fff]` UTC form; refuses
|
|
7
|
+
* zoneless `T` forms, date-only values and anything else.
|
|
8
|
+
* - {@link SYNC_TIMESTAMP_COLUMNS}: the captured timestamp columns per table.
|
|
9
|
+
* - {@link canonicalizeStoreTimestamps}: the one-time rewrite under the
|
|
10
|
+
* `timestamp_canon_v1` marker, run before the capture triggers go in. It
|
|
11
|
+
* never touches `tasks_tasks.updated_at` (the #1698 CAS token) and is
|
|
12
|
+
* refused once `row_identity_synced` exists.
|
|
13
|
+
*
|
|
14
|
+
* After genesis the sealer canonicalizes on the wire only; local rows are
|
|
15
|
+
* never rewritten there.
|
|
16
|
+
*
|
|
17
|
+
* @module store/sync/timestamps
|
|
18
|
+
* @task T12986
|
|
19
|
+
* @epic T12323
|
|
20
|
+
*/
|
|
21
|
+
import { ROW_IDENTITY_META_TABLE, ROW_IDENTITY_SYNCED_KEY } from '../row-identity.js';
|
|
22
|
+
import { withImmediateTransaction } from './clock-store.js';
|
|
23
|
+
import { hasTable } from './schema.js';
|
|
24
|
+
import { hasTriggerSuspendTable, withTriggersSuspended } from './trigger-classes.js';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// The canonicalizer
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const ISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})$/;
|
|
29
|
+
const SQLITE = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/;
|
|
30
|
+
function daysIn(year, month) {
|
|
31
|
+
if (month === 2)
|
|
32
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 29 : 28;
|
|
33
|
+
return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The canonical wire form of a stored timestamp,
|
|
37
|
+
* `YYYY-MM-DDTHH:MM:SS.sssZ`, or `null` when the value is refused.
|
|
38
|
+
*
|
|
39
|
+
* Fractions beyond milliseconds are truncated, never rounded, so a carry can
|
|
40
|
+
* never change the second. Pure: two replicas always agree.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* canonicalStoreTimestamp('2026-09-14 19:56:01'); // '2026-09-14T19:56:01.000Z'
|
|
45
|
+
* canonicalStoreTimestamp('2026-09-14T21:56:01.5+02:00'); // '2026-09-14T19:56:01.500Z'
|
|
46
|
+
* canonicalStoreTimestamp('2026-09-14T19:56:01'); // null (zoneless)
|
|
47
|
+
* canonicalStoreTimestamp('2026-09-14'); // null (date only)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function canonicalStoreTimestamp(value) {
|
|
51
|
+
const m = ISO.exec(value) ?? SQLITE.exec(value);
|
|
52
|
+
if (!m)
|
|
53
|
+
return null;
|
|
54
|
+
const [y, mo, d, h, mi, s] = m.slice(1, 7).map(Number);
|
|
55
|
+
if (mo < 1 || mo > 12 || d < 1 || d > daysIn(y, mo))
|
|
56
|
+
return null;
|
|
57
|
+
if (h > 23 || mi > 59 || s > 59)
|
|
58
|
+
return null;
|
|
59
|
+
const ms = Number((m[7] ?? '').slice(0, 3).padEnd(3, '0'));
|
|
60
|
+
let offsetMin = 0;
|
|
61
|
+
const zone = m[8];
|
|
62
|
+
if (zone !== undefined && zone !== 'Z') {
|
|
63
|
+
const oh = Number(zone.slice(1, 3));
|
|
64
|
+
const om = Number(zone.slice(4, 6));
|
|
65
|
+
if (oh > 23 || om > 59)
|
|
66
|
+
return null;
|
|
67
|
+
offsetMin = (zone.startsWith('-') ? -1 : 1) * (oh * 60 + om);
|
|
68
|
+
}
|
|
69
|
+
// setUTCFullYear keeps years below 100 as written (Date.UTC would add 1900).
|
|
70
|
+
const t = new Date(0);
|
|
71
|
+
t.setUTCFullYear(y, mo - 1, d);
|
|
72
|
+
t.setUTCHours(h, mi, s, ms);
|
|
73
|
+
const at = t.getTime() - offsetMin * 60_000;
|
|
74
|
+
const out = new Date(at);
|
|
75
|
+
const year = out.getUTCFullYear();
|
|
76
|
+
if (year < 0 || year > 9999)
|
|
77
|
+
return null;
|
|
78
|
+
return out.toISOString();
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// The columns
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Captured timestamp columns per sync-set table. The claim-lease columns
|
|
85
|
+
* (`claimed_at`, `lease_expires_at`) are local-only and are not captured, so
|
|
86
|
+
* they are not here. A test pins this map against every captured `*_at`
|
|
87
|
+
* column of the sync set.
|
|
88
|
+
*/
|
|
89
|
+
export const SYNC_TIMESTAMP_COLUMNS = {
|
|
90
|
+
project: {
|
|
91
|
+
tasks_tasks: ['created_at', 'updated_at', 'completed_at', 'cancelled_at', 'archived_at'],
|
|
92
|
+
tasks_sessions: ['started_at', 'ended_at', 'task_started_at', 'handoff_consumed_at'],
|
|
93
|
+
tasks_task_acceptance_criteria: ['created_at', 'updated_at'],
|
|
94
|
+
tasks_task_acceptance_criteria_history: ['recorded_at'],
|
|
95
|
+
tasks_evidence_ac_bindings: ['created_at'],
|
|
96
|
+
tasks_display_id_aliases: ['created_at'],
|
|
97
|
+
tasks_uid_aliases: ['created_at'],
|
|
98
|
+
},
|
|
99
|
+
global: {},
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Columns the one-time rewrite never touches: `tasks_tasks.updated_at` is the
|
|
103
|
+
* #1698 CAS token, and rewriting it would invalidate a caller's `--if-match`.
|
|
104
|
+
*/
|
|
105
|
+
export const CANON_EXCLUDED_COLUMNS = {
|
|
106
|
+
project: { tasks_tasks: ['updated_at'] },
|
|
107
|
+
global: {},
|
|
108
|
+
};
|
|
109
|
+
/** The captured timestamp columns of `table` (empty when it has none). */
|
|
110
|
+
export function timestampColumns(scope, table) {
|
|
111
|
+
const cols = SYNC_TIMESTAMP_COLUMNS[scope];
|
|
112
|
+
return new Set(Object.hasOwn(cols, table) ? cols[table] : []);
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// The one-time rewrite
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/** Marker key in `tasks_row_identity_meta` once the rewrite has run. */
|
|
118
|
+
export const TIMESTAMP_CANON_MARKER = 'timestamp_canon_v1';
|
|
119
|
+
const q = (id) => `"${id.replaceAll('"', '""')}"`;
|
|
120
|
+
function metaValue(db, key) {
|
|
121
|
+
const row = db
|
|
122
|
+
.prepare(`SELECT value FROM ${q(ROW_IDENTITY_META_TABLE)} WHERE key = ?`)
|
|
123
|
+
.get(key);
|
|
124
|
+
return row?.value;
|
|
125
|
+
}
|
|
126
|
+
function columnsOf(db, table) {
|
|
127
|
+
const info = db.prepare(`PRAGMA main.table_info(${q(table)})`).all();
|
|
128
|
+
return new Set(info.map((c) => c.name));
|
|
129
|
+
}
|
|
130
|
+
/** Distinct text values of one column, read once. */
|
|
131
|
+
function distinctText(db, table, column) {
|
|
132
|
+
return db
|
|
133
|
+
.prepare(`SELECT DISTINCT ${q(column)} AS v FROM ${q(table)} WHERE typeof(${q(column)}) = 'text'`)
|
|
134
|
+
.all().map((r) => r.v);
|
|
135
|
+
}
|
|
136
|
+
function countValue(db, table, column, value) {
|
|
137
|
+
return db.prepare(`SELECT count(*) AS n FROM ${q(table)} WHERE ${q(column)} = ?`).get(value).n;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Rows whose timestamp the canonicalizer refuses, per `table.column`: the
|
|
141
|
+
* doctor's `timestamp_ambiguous` finding. Read-only.
|
|
142
|
+
*/
|
|
143
|
+
export function ambiguousTimestamps(db, scope) {
|
|
144
|
+
const out = {};
|
|
145
|
+
for (const [table, cols] of Object.entries(SYNC_TIMESTAMP_COLUMNS[scope])) {
|
|
146
|
+
if (!hasTable(db, table))
|
|
147
|
+
continue;
|
|
148
|
+
const present = columnsOf(db, table);
|
|
149
|
+
for (const col of cols) {
|
|
150
|
+
if (!present.has(col))
|
|
151
|
+
continue;
|
|
152
|
+
let n = 0;
|
|
153
|
+
for (const v of distinctText(db, table, col)) {
|
|
154
|
+
if (canonicalStoreTimestamp(v) === null)
|
|
155
|
+
n += countValue(db, table, col, v);
|
|
156
|
+
}
|
|
157
|
+
if (n > 0)
|
|
158
|
+
out[`${table}.${col}`] = n;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The one-time rewrite (§1.8): every accepted legacy value in
|
|
165
|
+
* {@link SYNC_TIMESTAMP_COLUMNS}, except {@link CANON_EXCLUDED_COLUMNS},
|
|
166
|
+
* becomes canonical. Refused values stay as they are and are counted.
|
|
167
|
+
*
|
|
168
|
+
* - Runs once per store: the {@link TIMESTAMP_CANON_MARKER} row records it.
|
|
169
|
+
* - Refused once `row_identity_synced` exists: values have left the device.
|
|
170
|
+
* - Runs in one `BEGIN IMMEDIATE` (or the caller's transaction) with the
|
|
171
|
+
* capture and side-effect triggers suspended, so nothing is captured and
|
|
172
|
+
* no trigger reacts. Guards stay on; canonical values satisfy them.
|
|
173
|
+
* - Deterministic: each distinct value maps through the pure canonicalizer,
|
|
174
|
+
* so two copies of a store agree.
|
|
175
|
+
*/
|
|
176
|
+
export function canonicalizeStoreTimestamps(db, scope) {
|
|
177
|
+
const tables = Object.entries(SYNC_TIMESTAMP_COLUMNS[scope]);
|
|
178
|
+
if (tables.length === 0)
|
|
179
|
+
return { status: 'skipped', reason: `no timestamp columns in ${scope}` };
|
|
180
|
+
if (!hasTable(db, ROW_IDENTITY_META_TABLE)) {
|
|
181
|
+
return { status: 'skipped', reason: `${ROW_IDENTITY_META_TABLE} is absent` };
|
|
182
|
+
}
|
|
183
|
+
// Without the C2 flag table no trigger can be suspended, and a capture
|
|
184
|
+
// trigger (which reads it) cannot exist either.
|
|
185
|
+
if (!hasTriggerSuspendTable(db)) {
|
|
186
|
+
return { status: 'skipped', reason: 'cleo_trigger_suspend is absent' };
|
|
187
|
+
}
|
|
188
|
+
const run = () => {
|
|
189
|
+
if (metaValue(db, TIMESTAMP_CANON_MARKER) !== undefined)
|
|
190
|
+
return { status: 'already' };
|
|
191
|
+
if (metaValue(db, ROW_IDENTITY_SYNCED_KEY) !== undefined) {
|
|
192
|
+
return {
|
|
193
|
+
status: 'refused',
|
|
194
|
+
reason: `${ROW_IDENTITY_SYNCED_KEY} exists: timestamps are canonical on the wire only`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const rewritten = {};
|
|
198
|
+
const ambiguous = {};
|
|
199
|
+
withTriggersSuspended(db, ['capture', 'side-effect'], 'forward', () => {
|
|
200
|
+
for (const [table, cols] of tables) {
|
|
201
|
+
if (!hasTable(db, table))
|
|
202
|
+
continue;
|
|
203
|
+
const present = columnsOf(db, table);
|
|
204
|
+
const excluded = new Set(CANON_EXCLUDED_COLUMNS[scope][table] ?? []);
|
|
205
|
+
for (const col of cols) {
|
|
206
|
+
if (excluded.has(col) || !present.has(col))
|
|
207
|
+
continue;
|
|
208
|
+
const update = db.prepare(`UPDATE ${q(table)} SET ${q(col)} = ? WHERE ${q(col)} = ?`);
|
|
209
|
+
for (const raw of distinctText(db, table, col)) {
|
|
210
|
+
const canon = canonicalStoreTimestamp(raw);
|
|
211
|
+
const key = `${table}.${col}`;
|
|
212
|
+
if (canon === null) {
|
|
213
|
+
ambiguous[key] = (ambiguous[key] ?? 0) + countValue(db, table, col, raw);
|
|
214
|
+
}
|
|
215
|
+
else if (canon !== raw) {
|
|
216
|
+
const n = Number(update.run(canon, raw).changes);
|
|
217
|
+
rewritten[key] = (rewritten[key] ?? 0) + n;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
db.prepare(`INSERT INTO ${q(ROW_IDENTITY_META_TABLE)} (key, value) VALUES (?, ?)`).run(TIMESTAMP_CANON_MARKER, 'done');
|
|
224
|
+
return { status: 'done', rewritten, ambiguous };
|
|
225
|
+
};
|
|
226
|
+
return db.isTransaction ? run() : withImmediateTransaction(db, run);
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=timestamps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamps.js","sourceRoot":"","sources":["../../../src/store/sync/timestamps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal tombstones (journal spec §1.7, NEW-2, R5-5; T12986, S3c).
|
|
3
|
+
*
|
|
4
|
+
* The sealer writes a FULL tombstone on D: the row meta with `deleted = 1`,
|
|
5
|
+
* the delete HLC, the natural key and the before-image hash. GC never forgets
|
|
6
|
+
* that a uid was deleted; once the stream is safely past a tombstone,
|
|
7
|
+
* {@link compactTombstones} shrinks it to the minimal form
|
|
8
|
+
* (`tbl`, `uid`, `hlc`, `deleted = 1`), so a receiver can still tell a
|
|
9
|
+
* deleted uid from a never-seen one and a late op never resurrects the row.
|
|
10
|
+
* It is found by its WITHOUT ROWID key and has no entry in the tombstone
|
|
11
|
+
* index (which holds only full tombstones): about 155 B per row as measured,
|
|
12
|
+
* within the spec's ~190 B.
|
|
13
|
+
*
|
|
14
|
+
* Append-only tables get no per-row tombstone at all (the sealer removes
|
|
15
|
+
* their meta on D); the horizon fold into `_sync_tomb_fold` needs checkpoint
|
|
16
|
+
* receive watermarks and is S4.
|
|
17
|
+
*
|
|
18
|
+
* What makes a tombstone compactable is stream state this slice does not own
|
|
19
|
+
* (the latest verified checkpoint's `ReplicaHeads`, T12338; the inbox's
|
|
20
|
+
* pending and void transactions, T12344), so the caller supplies it as a
|
|
21
|
+
* {@link TombstoneGcPolicy}.
|
|
22
|
+
*
|
|
23
|
+
* @module store/sync/tombstones
|
|
24
|
+
* @task T12986
|
|
25
|
+
* @epic T12323
|
|
26
|
+
*/
|
|
27
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
28
|
+
/** Grace after a tombstone's physical time before it may be compacted (§1.7). */
|
|
29
|
+
export declare const TOMBSTONE_GRACE_MS: number;
|
|
30
|
+
/** When a full tombstone may shrink to its minimal form. */
|
|
31
|
+
export interface TombstoneGcPolicy {
|
|
32
|
+
/**
|
|
33
|
+
* The highest HLC every live replica has covered: in the latest verified
|
|
34
|
+
* checkpoint's `ReplicaHeads`, each live replica's `lastReplicaSeq` covers a
|
|
35
|
+
* segment whose `hlcMax` is at least this. Retired replicas are left out by
|
|
36
|
+
* the caller (§1.5). Only tombstones at or below it are compacted.
|
|
37
|
+
*/
|
|
38
|
+
readonly coveredHlc: string;
|
|
39
|
+
/** Wall clock, ms. */
|
|
40
|
+
readonly now: number;
|
|
41
|
+
/** @defaultValue {@link TOMBSTONE_GRACE_MS} */
|
|
42
|
+
readonly graceMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a `pending` or `void` inbox transaction references the uid
|
|
45
|
+
* (T12344). Such a full tombstone is kept: the void may yet be revived.
|
|
46
|
+
*/
|
|
47
|
+
readonly referenced: (tbl: string, uid: string) => boolean;
|
|
48
|
+
/** Most tombstones examined per call. @defaultValue 5000 */
|
|
49
|
+
readonly budget?: number;
|
|
50
|
+
}
|
|
51
|
+
/** What {@link compactTombstones} did. */
|
|
52
|
+
export interface TombstoneCompactReport {
|
|
53
|
+
/** Full tombstones shrunk to the minimal form. */
|
|
54
|
+
readonly compacted: number;
|
|
55
|
+
/** Eligible by HLC and age, kept because an inbox transaction references the uid. */
|
|
56
|
+
readonly referenced: number;
|
|
57
|
+
/** Covered, but younger than the grace period. */
|
|
58
|
+
readonly young: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Shrink full tombstones the stream no longer needs to their minimal form,
|
|
62
|
+
* in one `BEGIN IMMEDIATE`. Never deletes a tombstone: a minimal one is kept
|
|
63
|
+
* for good (NEW-2).
|
|
64
|
+
*
|
|
65
|
+
* The minimal form keeps `tbl`, `uid`, `hlc` and `deleted = 1`; `origin` and
|
|
66
|
+
* `version` take their empty values (`''`, `0`) because the columns are
|
|
67
|
+
* NOT NULL, and `sent`, `held` and `fk_excluded` are left as they are.
|
|
68
|
+
*/
|
|
69
|
+
export declare function compactTombstones(db: DatabaseSync, policy: TombstoneGcPolicy): TombstoneCompactReport;
|
|
70
|
+
/** Tombstone counts for the doctor and the growth measurement (§1.7 R5-5). */
|
|
71
|
+
export declare function tombstoneCounts(db: DatabaseSync): {
|
|
72
|
+
full: number;
|
|
73
|
+
minimal: number;
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=tombstones.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tombstones.d.ts","sourceRoot":"","sources":["../../../src/store/sync/tombstones.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,QAA2B,CAAC;AAE3D,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sBAAsB;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3D,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,0CAA0C;AAC1C,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,iBAAiB,GACxB,sBAAsB,CAmCxB;AAYD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAUnF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal tombstones (journal spec §1.7, NEW-2, R5-5; T12986, S3c).
|
|
3
|
+
*
|
|
4
|
+
* The sealer writes a FULL tombstone on D: the row meta with `deleted = 1`,
|
|
5
|
+
* the delete HLC, the natural key and the before-image hash. GC never forgets
|
|
6
|
+
* that a uid was deleted; once the stream is safely past a tombstone,
|
|
7
|
+
* {@link compactTombstones} shrinks it to the minimal form
|
|
8
|
+
* (`tbl`, `uid`, `hlc`, `deleted = 1`), so a receiver can still tell a
|
|
9
|
+
* deleted uid from a never-seen one and a late op never resurrects the row.
|
|
10
|
+
* It is found by its WITHOUT ROWID key and has no entry in the tombstone
|
|
11
|
+
* index (which holds only full tombstones): about 155 B per row as measured,
|
|
12
|
+
* within the spec's ~190 B.
|
|
13
|
+
*
|
|
14
|
+
* Append-only tables get no per-row tombstone at all (the sealer removes
|
|
15
|
+
* their meta on D); the horizon fold into `_sync_tomb_fold` needs checkpoint
|
|
16
|
+
* receive watermarks and is S4.
|
|
17
|
+
*
|
|
18
|
+
* What makes a tombstone compactable is stream state this slice does not own
|
|
19
|
+
* (the latest verified checkpoint's `ReplicaHeads`, T12338; the inbox's
|
|
20
|
+
* pending and void transactions, T12344), so the caller supplies it as a
|
|
21
|
+
* {@link TombstoneGcPolicy}.
|
|
22
|
+
*
|
|
23
|
+
* @module store/sync/tombstones
|
|
24
|
+
* @task T12986
|
|
25
|
+
* @epic T12323
|
|
26
|
+
*/
|
|
27
|
+
import { withImmediateTransaction } from './clock-store.js';
|
|
28
|
+
import { HlcError, parseHlc } from './hlc.js';
|
|
29
|
+
import { hasTable } from './schema.js';
|
|
30
|
+
/** Grace after a tombstone's physical time before it may be compacted (§1.7). */
|
|
31
|
+
export const TOMBSTONE_GRACE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
32
|
+
/**
|
|
33
|
+
* A full tombstone: the sealer always writes `version >= 1`, compaction sets
|
|
34
|
+
* it to 0. The predicate matches the partial index `_sync_row_meta_tomb`
|
|
35
|
+
* (`(hlc) WHERE deleted = 1 AND version > 0`), so GC scans only full ones and
|
|
36
|
+
* a minimal tombstone carries no index entry.
|
|
37
|
+
*/
|
|
38
|
+
const FULL = 'version > 0';
|
|
39
|
+
/**
|
|
40
|
+
* Shrink full tombstones the stream no longer needs to their minimal form,
|
|
41
|
+
* in one `BEGIN IMMEDIATE`. Never deletes a tombstone: a minimal one is kept
|
|
42
|
+
* for good (NEW-2).
|
|
43
|
+
*
|
|
44
|
+
* The minimal form keeps `tbl`, `uid`, `hlc` and `deleted = 1`; `origin` and
|
|
45
|
+
* `version` take their empty values (`''`, `0`) because the columns are
|
|
46
|
+
* NOT NULL, and `sent`, `held` and `fk_excluded` are left as they are.
|
|
47
|
+
*/
|
|
48
|
+
export function compactTombstones(db, policy) {
|
|
49
|
+
if (!hasTable(db, '_sync_row_meta'))
|
|
50
|
+
return { compacted: 0, referenced: 0, young: 0 };
|
|
51
|
+
const grace = policy.graceMs ?? TOMBSTONE_GRACE_MS;
|
|
52
|
+
const budget = Math.max(1, policy.budget ?? 5000);
|
|
53
|
+
return withImmediateTransaction(db, () => {
|
|
54
|
+
const rows = db
|
|
55
|
+
.prepare(`SELECT tbl, uid, hlc FROM _sync_row_meta
|
|
56
|
+
WHERE deleted = 1 AND ${FULL} AND hlc <= ?
|
|
57
|
+
ORDER BY hlc LIMIT ?`)
|
|
58
|
+
.all(policy.coveredHlc, budget);
|
|
59
|
+
const shrink = db.prepare(`UPDATE _sync_row_meta
|
|
60
|
+
SET fhlc = NULL, origin = '', actor = NULL, version = 0,
|
|
61
|
+
key_json = NULL, chash = NULL, shash = NULL, bfp = NULL
|
|
62
|
+
WHERE tbl = ? AND uid = ? AND deleted = 1`);
|
|
63
|
+
let compacted = 0;
|
|
64
|
+
let referenced = 0;
|
|
65
|
+
let young = 0;
|
|
66
|
+
for (const r of rows) {
|
|
67
|
+
if (policy.now - physicalMs(r.hlc) < grace) {
|
|
68
|
+
young += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (policy.referenced(r.tbl, r.uid)) {
|
|
72
|
+
referenced += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
shrink.run(r.tbl, r.uid);
|
|
76
|
+
compacted += 1;
|
|
77
|
+
}
|
|
78
|
+
return { compacted, referenced, young };
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** The physical ms of an HLC; an unparseable one counts as brand new (kept). */
|
|
82
|
+
function physicalMs(hlc) {
|
|
83
|
+
try {
|
|
84
|
+
return parseHlc(hlc).phys;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err instanceof HlcError)
|
|
88
|
+
return Number.POSITIVE_INFINITY;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Tombstone counts for the doctor and the growth measurement (§1.7 R5-5). */
|
|
93
|
+
export function tombstoneCounts(db) {
|
|
94
|
+
if (!hasTable(db, '_sync_row_meta'))
|
|
95
|
+
return { full: 0, minimal: 0 };
|
|
96
|
+
const row = db
|
|
97
|
+
.prepare(`SELECT coalesce(sum(CASE WHEN ${FULL} THEN 1 ELSE 0 END), 0) AS full,
|
|
98
|
+
coalesce(sum(CASE WHEN ${FULL} THEN 0 ELSE 1 END), 0) AS minimal
|
|
99
|
+
FROM _sync_row_meta WHERE deleted = 1`)
|
|
100
|
+
.get();
|
|
101
|
+
return { full: row.full, minimal: row.minimal };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=tombstones.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tombstones.js","sourceRoot":"","sources":["../../../src/store/sync/tombstones.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger classes and the suspension clause (journal spec §3.5 Rule 4, C2,
|
|
3
|
+
* D4; T12813, T12819, T12827).
|
|
4
|
+
*
|
|
5
|
+
* Every trigger in a store has exactly one class. The class decides whether
|
|
6
|
+
* the trigger carries the suspension clause, which a rebase frame uses to
|
|
7
|
+
* turn a class off inside its own transaction:
|
|
8
|
+
*
|
|
9
|
+
* | Class | Carries the clause | Example |
|
|
10
|
+
* |---|---|---|
|
|
11
|
+
* | `capture` | yes, scope `capture` | `_sync_cap_*` (S2 capture triggers) |
|
|
12
|
+
* | `guard` | yes, scope `guard` | the parent-cycle and status-pipeline guards |
|
|
13
|
+
* | `side-effect` | yes, scope `side-effect` | claim release, the handoff mirror |
|
|
14
|
+
* | `derived-maintenance` | no, always active | FTS `_ai`/`_ad`/`_au` |
|
|
15
|
+
* | `frozen-guard` | no, never modified | triggers on frozen-legacy bare twins |
|
|
16
|
+
*
|
|
17
|
+
* `guard` and `side-effect` triggers are OWNED: {@link OWNED_TRIGGERS} names
|
|
18
|
+
* each one, and its DDL is the last `CREATE TRIGGER` for that name in the
|
|
19
|
+
* migration lineage from the C2 migration on, or the code-held DDL of
|
|
20
|
+
* {@link CODE_OWNED_TRIGGER_DDL} ({@link ownedTriggerDdl}). The open pass and
|
|
21
|
+
* the doctor compare each owned trigger's LIVE text with that DDL and re-run
|
|
22
|
+
* the DDL when it differs or is missing (C2(b)), but only where every table
|
|
23
|
+
* the trigger needs exists ({@link OWNED_TRIGGER_REQUIRES}).
|
|
24
|
+
*
|
|
25
|
+
* A trigger is `frozen-guard` ONLY because of the table it fires ON: a
|
|
26
|
+
* `frozen-legacy` bare twin. A rebase never touches a frozen twin, and giving
|
|
27
|
+
* such a trigger a clause would make 9.24's replace-if-text-differs
|
|
28
|
+
* (`twin-collapse.ts`) and this build's repair flip its DDL on every open
|
|
29
|
+
* that alternates builds (D4). Its name never decides: a trigger on a live
|
|
30
|
+
* table (even one named `t12535_*`) is owned, or unclassified.
|
|
31
|
+
*
|
|
32
|
+
* @task T12819
|
|
33
|
+
* @task T12827
|
|
34
|
+
* @module store/sync/trigger-classes
|
|
35
|
+
*/
|
|
36
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
37
|
+
import type { TableScope } from '@cleocode/contracts';
|
|
38
|
+
/** A trigger class. */
|
|
39
|
+
export type TriggerClass = 'capture' | 'guard' | 'side-effect' | 'derived-maintenance' | 'frozen-guard';
|
|
40
|
+
/** The classes that carry the suspension clause. */
|
|
41
|
+
export type SuspendableClass = 'capture' | 'guard' | 'side-effect';
|
|
42
|
+
/** The schema-owned flag table the suspension clause reads. Never dropped. */
|
|
43
|
+
export declare const TRIGGER_SUSPEND_TABLE = "cleo_trigger_suspend";
|
|
44
|
+
/**
|
|
45
|
+
* DDL of the flag table, byte-identical to the C2 migration's first
|
|
46
|
+
* statement. The open pass runs it (step 0) before migrations.
|
|
47
|
+
*/
|
|
48
|
+
export declare const TRIGGER_SUSPEND_TABLE_DDL = "CREATE TABLE IF NOT EXISTS `cleo_trigger_suspend` (\n `scope` TEXT PRIMARY KEY NOT NULL CHECK (`scope` IN ('capture', 'guard', 'side-effect', 'all'))\n)";
|
|
49
|
+
/** The C2 migration folder (drizzle-cleo-project). Owned DDL is read from it on. */
|
|
50
|
+
export declare const TRIGGER_CLAUSE_MIGRATION = "20260930170000_t12819-trigger-suspend-clause";
|
|
51
|
+
/** The lineage the owned triggers live in. */
|
|
52
|
+
export declare const OWNED_TRIGGER_LINEAGE = "drizzle-cleo-project";
|
|
53
|
+
/**
|
|
54
|
+
* The suspension clause a trigger of `cls` carries, exactly as written in
|
|
55
|
+
* migration SQL. Verification compares it whitespace-normalized.
|
|
56
|
+
*
|
|
57
|
+
* The table name is unqualified on purpose. A persistent trigger resolves an
|
|
58
|
+
* unqualified name in its own schema (a TEMP table cannot shadow it), so the
|
|
59
|
+
* trigger keeps working when the store is ATTACHed under another name; a
|
|
60
|
+
* `main.` qualifier makes an attached schema unreadable ("cannot reference
|
|
61
|
+
* objects in database main").
|
|
62
|
+
*/
|
|
63
|
+
export declare function suspendClause(cls: SuspendableClass): string;
|
|
64
|
+
/**
|
|
65
|
+
* The owned guard and side-effect triggers (project store), by name. Each
|
|
66
|
+
* one's DDL comes from the migration lineage or from
|
|
67
|
+
* {@link CODE_OWNED_TRIGGER_DDL} ({@link ownedTriggerDdl}).
|
|
68
|
+
*/
|
|
69
|
+
export declare const OWNED_TRIGGERS: Readonly<Record<string, 'guard' | 'side-effect'>>;
|
|
70
|
+
/**
|
|
71
|
+
* Owned triggers whose DDL is held in code, not in the C2 migration.
|
|
72
|
+
*
|
|
73
|
+
* The AC uid graveyard trigger writes `tasks_ac_uid_graveyard`. A store whose
|
|
74
|
+
* t12341 migration was probe-stamped after only its ADD COLUMNs ran (the
|
|
75
|
+
* 9.25 live-cleocode shape) has no graveyard table, and the table is created
|
|
76
|
+
* only by the flag-gated identity heal. A migration that created the trigger
|
|
77
|
+
* unconditionally would make every acceptance-criterion delete fail there
|
|
78
|
+
* ("no such table: main.tasks_ac_uid_graveyard"), so the trigger is installed
|
|
79
|
+
* by the open pass, and only where its table exists.
|
|
80
|
+
*/
|
|
81
|
+
export declare const CODE_OWNED_TRIGGER_DDL: Readonly<Record<string, string>>;
|
|
82
|
+
/**
|
|
83
|
+
* Tables an owned trigger needs besides the one it fires ON. Where one is
|
|
84
|
+
* missing the trigger is not verified or installed, and a live copy is a
|
|
85
|
+
* finding (`dangling`) that repair drops: it would fail every write that
|
|
86
|
+
* fires it.
|
|
87
|
+
*/
|
|
88
|
+
export declare const OWNED_TRIGGER_REQUIRES: Readonly<Record<string, readonly string[]>>;
|
|
89
|
+
/** Prefix of every capture trigger (S2, §2.3). */
|
|
90
|
+
export declare const CAPTURE_TRIGGER_PREFIX = "_sync_cap_";
|
|
91
|
+
/** A classified trigger. */
|
|
92
|
+
export interface TriggerClassification {
|
|
93
|
+
readonly name: string;
|
|
94
|
+
readonly table: string;
|
|
95
|
+
readonly class: TriggerClass;
|
|
96
|
+
/** Why it has this class. */
|
|
97
|
+
readonly reason: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The tables a trigger body writes: `INSERT INTO t`, `UPDATE t`,
|
|
101
|
+
* `DELETE FROM t`, `REPLACE INTO t`. Lower-cased; quotes stripped.
|
|
102
|
+
*/
|
|
103
|
+
export declare function triggerWriteTargets(sql: string): string[];
|
|
104
|
+
/**
|
|
105
|
+
* Classify one trigger. Returns `undefined` for a trigger no rule covers,
|
|
106
|
+
* which the Gate test reports.
|
|
107
|
+
*
|
|
108
|
+
* @param scope - The store the trigger lives in.
|
|
109
|
+
* @param name - `sqlite_master.name`.
|
|
110
|
+
* @param table - `sqlite_master.tbl_name`.
|
|
111
|
+
* @param sql - `sqlite_master.sql`.
|
|
112
|
+
*/
|
|
113
|
+
export declare function classifyTrigger(scope: TableScope, name: string, table: string, sql: string): TriggerClassification | undefined;
|
|
114
|
+
/** Collapse whitespace so text comparisons ignore layout. */
|
|
115
|
+
export declare function normalizeSql(sql: string): string;
|
|
116
|
+
/**
|
|
117
|
+
* Split migration text into `CREATE TRIGGER` statements, by name. Later
|
|
118
|
+
* definitions of the same name replace earlier ones. `IF NOT EXISTS` is
|
|
119
|
+
* removed, as SQLite removes it from `sqlite_master.sql`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function createTriggerStatements(migrationSql: string): Map<string, string>;
|
|
122
|
+
/**
|
|
123
|
+
* The owned trigger DDL: {@link CODE_OWNED_TRIGGER_DDL}, then the last
|
|
124
|
+
* `CREATE TRIGGER` for each owned name in the lineage's migration files from
|
|
125
|
+
* {@link TRIGGER_CLAUSE_MIGRATION} on, in folder order (a later migration
|
|
126
|
+
* replaces a code-held text).
|
|
127
|
+
*
|
|
128
|
+
* @param folder - The lineage folder (tests pass one; default: installed).
|
|
129
|
+
* @throws {Error} When an owned trigger has no DDL from C2 on.
|
|
130
|
+
*/
|
|
131
|
+
export declare function ownedTriggerDdl(folder?: string): Map<string, string>;
|
|
132
|
+
/** One finding of {@link verifyOwnedTriggers}. */
|
|
133
|
+
export interface OwnedTriggerFinding {
|
|
134
|
+
readonly name: string;
|
|
135
|
+
/** `dangling`: live, but a table it needs is missing (repair drops it). */
|
|
136
|
+
readonly problem: 'missing' | 'text-differs' | 'no-clause' | 'dangling';
|
|
137
|
+
readonly repaired: boolean;
|
|
138
|
+
}
|
|
139
|
+
/** Whether the flag table exists. Read-only. */
|
|
140
|
+
export declare function hasTriggerSuspendTable(db: DatabaseSync): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Compare every owned trigger's live `sqlite_master.sql` with its owned DDL,
|
|
143
|
+
* and optionally repair (drop, then re-run the owned DDL) each one that is
|
|
144
|
+
* missing or differs.
|
|
145
|
+
*
|
|
146
|
+
* Only meaningful on a store whose journal has the C2 migration: before it,
|
|
147
|
+
* the owned DDL references a table the store may not have. Callers check
|
|
148
|
+
* {@link hasTriggerSuspendTable} and that the owned tables exist.
|
|
149
|
+
*
|
|
150
|
+
* @param db - A project store handle.
|
|
151
|
+
* @param options - `repair` rewrites; `ddl` overrides the owned DDL (tests).
|
|
152
|
+
* @returns One finding per trigger that was not identical.
|
|
153
|
+
*/
|
|
154
|
+
export declare function verifyOwnedTriggers(db: DatabaseSync, options?: {
|
|
155
|
+
repair?: boolean;
|
|
156
|
+
ddl?: Map<string, string>;
|
|
157
|
+
}): OwnedTriggerFinding[];
|
|
158
|
+
/**
|
|
159
|
+
* Run DDL as one unit (T13024 MED-2): `BEGIN IMMEDIATE` … `COMMIT` on an idle
|
|
160
|
+
* connection, a SAVEPOINT inside a caller's transaction. A failure rolls the
|
|
161
|
+
* whole unit back, so a DROP never commits without its CREATE.
|
|
162
|
+
*/
|
|
163
|
+
export declare function atomicDdl<T>(db: DatabaseSync, fn: () => T): T;
|
|
164
|
+
/**
|
|
165
|
+
* Classify every trigger of a store. Read-only.
|
|
166
|
+
*
|
|
167
|
+
* @returns The classified triggers, and the names no rule covers.
|
|
168
|
+
*/
|
|
169
|
+
export declare function classifyStoreTriggers(db: DatabaseSync, scope: TableScope): {
|
|
170
|
+
classified: TriggerClassification[];
|
|
171
|
+
unclassified: string[];
|
|
172
|
+
};
|
|
173
|
+
/** What the open pass's step 0 did to the flag table. */
|
|
174
|
+
export interface TriggerSuspendStepZero {
|
|
175
|
+
/** The table was missing and was created. */
|
|
176
|
+
readonly created: boolean;
|
|
177
|
+
/** Committed suspension rows found and cleared (should never happen). */
|
|
178
|
+
readonly cleared: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Open-pass step 0 (C2, round 9): make sure `cleo_trigger_suspend` exists,
|
|
182
|
+
* BEFORE migrations, and that it is empty.
|
|
183
|
+
*
|
|
184
|
+
* Every write to tasks, sessions and acceptance criteria fires an owned
|
|
185
|
+
* trigger that reads the table, and any `ALTER … RENAME` re-validates every
|
|
186
|
+
* trigger, so a store missing it cannot be written. A committed row is
|
|
187
|
+
* impossible through the rebase frame (it deletes and asserts before
|
|
188
|
+
* COMMIT); if one is found anyway it is cleared, because a committed
|
|
189
|
+
* suspension would silently disable guards for everyone.
|
|
190
|
+
*
|
|
191
|
+
* Writes nothing when the table exists and is empty.
|
|
192
|
+
*/
|
|
193
|
+
export declare function ensureTriggerSuspendTable(db: DatabaseSync): TriggerSuspendStepZero;
|
|
194
|
+
/**
|
|
195
|
+
* Throw unless `cleo_trigger_suspend` is empty. A frame calls this right
|
|
196
|
+
* before COMMIT, so a suspension can never be committed (C4).
|
|
197
|
+
*
|
|
198
|
+
* @throws {Error} `E_TRIGGER_SUSPEND_NOT_EMPTY` when a row remains.
|
|
199
|
+
*/
|
|
200
|
+
export declare function assertTriggerSuspendEmpty(db: DatabaseSync): void;
|
|
201
|
+
/**
|
|
202
|
+
* Why a caller suspends triggers. `rewind` and `undo` restore a state the
|
|
203
|
+
* store already held, so its guards held for it; `forward` is anything else
|
|
204
|
+
* (apply, replay, import, a derived rewrite).
|
|
205
|
+
*/
|
|
206
|
+
export type SuspendPurpose = 'rewind' | 'undo' | 'forward';
|
|
207
|
+
/**
|
|
208
|
+
* Run `fn` with the given trigger classes suspended, inside the caller's
|
|
209
|
+
* transaction: insert the scope rows, run, delete them, and assert the table
|
|
210
|
+
* is empty before returning. Other connections never see the rows (WAL), so
|
|
211
|
+
* their triggers stay active; a crash rolls the rows back.
|
|
212
|
+
*
|
|
213
|
+
* Suspending `guard` (or `all`) is allowed only to rewind or undo (T12344
|
|
214
|
+
* AC5): a forward apply or replay with the guards off would commit a merged
|
|
215
|
+
* state they refuse (a dependency or parent cycle) without a word.
|
|
216
|
+
*
|
|
217
|
+
* @throws {Error} When called outside a transaction, or
|
|
218
|
+
* `E_GUARD_SUSPEND_FORWARD` when a forward purpose suspends `guard`.
|
|
219
|
+
*/
|
|
220
|
+
export declare function withTriggersSuspended<T>(db: DatabaseSync, scopes: ReadonlyArray<SuspendableClass | 'all'>, purpose: SuspendPurpose, fn: () => T): T;
|
|
221
|
+
//# sourceMappingURL=trigger-classes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-classes.d.ts","sourceRoot":"","sources":["../../../src/store/sync/trigger-classes.ts"],"names":[],"mappings":"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"}
|