@cleocode/core 2026.10.5 → 2026.10.6
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/bootstrap.d.ts +13 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +21 -32
- package/dist/bootstrap.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
- package/dist/cloud/nexus-cloud-status.js +3 -1
- package/dist/cloud/nexus-cloud-status.js.map +1 -1
- package/dist/cloud/nexus-device.d.ts +1 -1
- package/dist/cloud/nexus-presence-refresh.d.ts.map +1 -1
- package/dist/cloud/nexus-presence-refresh.js +20 -13
- package/dist/cloud/nexus-presence-refresh.js.map +1 -1
- package/dist/cloud/nexus-vault.d.ts.map +1 -1
- package/dist/cloud/nexus-vault.js +4 -1
- package/dist/cloud/nexus-vault.js.map +1 -1
- package/dist/docs/derive-links.d.ts +69 -0
- package/dist/docs/derive-links.d.ts.map +1 -0
- package/dist/docs/derive-links.js +85 -0
- package/dist/docs/derive-links.js.map +1 -0
- package/dist/docs/docs-generator.d.ts.map +1 -1
- package/dist/docs/docs-generator.js +2 -0
- package/dist/docs/docs-generator.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +24 -4
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/fetch-result.d.ts +77 -0
- package/dist/docs/fetch-result.d.ts.map +1 -0
- package/dist/docs/fetch-result.js +132 -0
- package/dist/docs/fetch-result.js.map +1 -0
- package/dist/docs/supersede.d.ts.map +1 -1
- package/dist/docs/supersede.js +9 -0
- package/dist/docs/supersede.js.map +1 -1
- package/dist/doctor/db-substrate.d.ts.map +1 -1
- package/dist/doctor/db-substrate.js +64 -29
- package/dist/doctor/db-substrate.js.map +1 -1
- package/dist/doctor/row-identity-refill.d.ts.map +1 -1
- package/dist/doctor/row-identity-refill.js +25 -10
- package/dist/doctor/row-identity-refill.js.map +1 -1
- package/dist/doctor/row-identity.d.ts.map +1 -1
- package/dist/doctor/row-identity.js +8 -2
- package/dist/doctor/row-identity.js.map +1 -1
- package/dist/doctor/sync-repair.d.ts +5 -0
- package/dist/doctor/sync-repair.d.ts.map +1 -1
- package/dist/doctor/sync-repair.js +11 -0
- package/dist/doctor/sync-repair.js.map +1 -1
- package/dist/doctor/sync-triggers.d.ts.map +1 -1
- package/dist/doctor/sync-triggers.js +3 -17
- package/dist/doctor/sync-triggers.js.map +1 -1
- package/dist/hygiene/validate-spawn-readiness.d.ts +6 -1
- package/dist/hygiene/validate-spawn-readiness.d.ts.map +1 -1
- package/dist/hygiene/validate-spawn-readiness.js +11 -6
- package/dist/hygiene/validate-spawn-readiness.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +10 -1
- package/dist/init.js.map +1 -1
- package/dist/injection.d.ts +37 -12
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +152 -35
- package/dist/injection.js.map +1 -1
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-search.js +7 -32
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +2 -0
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +51 -9
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/nexus/assessment-projection.d.ts +96 -1
- package/dist/nexus/assessment-projection.d.ts.map +1 -1
- package/dist/nexus/assessment-projection.js +135 -0
- package/dist/nexus/assessment-projection.js.map +1 -1
- package/dist/nexus/assessment-store.d.ts +54 -0
- package/dist/nexus/assessment-store.d.ts.map +1 -1
- package/dist/nexus/assessment-store.js +161 -2
- package/dist/nexus/assessment-store.js.map +1 -1
- package/dist/nexus/knowledge.d.ts +19 -0
- package/dist/nexus/knowledge.d.ts.map +1 -1
- package/dist/nexus/knowledge.js +56 -2
- package/dist/nexus/knowledge.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +12 -1
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/release/commit-presence.d.ts +24 -0
- package/dist/release/commit-presence.d.ts.map +1 -1
- package/dist/release/commit-presence.js +31 -0
- package/dist/release/commit-presence.js.map +1 -1
- package/dist/release/plan.d.ts +8 -6
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +11 -6
- package/dist/release/plan.js.map +1 -1
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +10 -8
- package/dist/release/reconcile.js.map +1 -1
- package/dist/release/verify-provenance.d.ts.map +1 -1
- package/dist/release/verify-provenance.js +6 -8
- package/dist/release/verify-provenance.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +66 -0
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +145 -6
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/run-class.d.ts +71 -0
- package/dist/resources/run-class.d.ts.map +1 -1
- package/dist/resources/run-class.js +203 -0
- package/dist/resources/run-class.js.map +1 -1
- package/dist/resources/run-governed.d.ts +2 -0
- package/dist/resources/run-governed.d.ts.map +1 -1
- package/dist/resources/run-governed.js +1 -0
- package/dist/resources/run-governed.js.map +1 -1
- package/dist/scaffold/ensure-config.d.ts +20 -9
- package/dist/scaffold/ensure-config.d.ts.map +1 -1
- package/dist/scaffold/ensure-config.js +56 -23
- package/dist/scaffold/ensure-config.js.map +1 -1
- package/dist/scaffold/ensure-skills.d.ts +10 -0
- package/dist/scaffold/ensure-skills.d.ts.map +1 -1
- package/dist/scaffold/ensure-skills.js +47 -3
- package/dist/scaffold/ensure-skills.js.map +1 -1
- package/dist/scaffold/upgrade-file-journal.d.ts +79 -0
- package/dist/scaffold/upgrade-file-journal.d.ts.map +1 -0
- package/dist/scaffold/upgrade-file-journal.js +111 -0
- package/dist/scaffold/upgrade-file-journal.js.map +1 -0
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +10 -2
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +3 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/spawn/worktree-lock-holder.d.ts +59 -1
- package/dist/spawn/worktree-lock-holder.d.ts.map +1 -1
- package/dist/spawn/worktree-lock-holder.js +114 -0
- package/dist/spawn/worktree-lock-holder.js.map +1 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +38 -0
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/backup-recover.d.ts +4 -4
- package/dist/store/backup-recover.d.ts.map +1 -1
- package/dist/store/backup-recover.js +11 -4
- package/dist/store/backup-recover.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +19 -1
- package/dist/store/backup-sidecar.d.ts.map +1 -1
- package/dist/store/backup-sidecar.js +57 -6
- package/dist/store/backup-sidecar.js.map +1 -1
- package/dist/store/db-helpers.js +1 -1
- package/dist/store/display-id-alias.js +1 -1
- package/dist/store/display-id-alias.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +2 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +28 -16
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/archive.d.ts +42 -2
- package/dist/store/exodus/archive.d.ts.map +1 -1
- package/dist/store/exodus/archive.js +81 -4
- package/dist/store/exodus/archive.js.map +1 -1
- package/dist/store/exodus/bare-family.d.ts +104 -0
- package/dist/store/exodus/bare-family.d.ts.map +1 -0
- package/dist/store/exodus/bare-family.js +470 -0
- package/dist/store/exodus/bare-family.js.map +1 -0
- package/dist/store/exodus/column-transforms.d.ts.map +1 -1
- package/dist/store/exodus/column-transforms.js +15 -8
- package/dist/store/exodus/column-transforms.js.map +1 -1
- package/dist/store/exodus/health.d.ts +2 -0
- package/dist/store/exodus/health.d.ts.map +1 -1
- package/dist/store/exodus/health.js +7 -1
- package/dist/store/exodus/health.js.map +1 -1
- package/dist/store/exodus/index.d.ts +2 -2
- package/dist/store/exodus/index.d.ts.map +1 -1
- package/dist/store/exodus/index.js +2 -2
- package/dist/store/exodus/index.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +30 -1
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts +11 -0
- package/dist/store/exodus/on-open.d.ts.map +1 -1
- package/dist/store/exodus/on-open.js +26 -1
- package/dist/store/exodus/on-open.js.map +1 -1
- package/dist/store/exodus/prior-recoveries.d.ts +3 -2
- package/dist/store/exodus/prior-recoveries.d.ts.map +1 -1
- package/dist/store/exodus/prior-recoveries.js +7 -3
- package/dist/store/exodus/prior-recoveries.js.map +1 -1
- package/dist/store/exodus/reconcile.d.ts +44 -6
- package/dist/store/exodus/reconcile.d.ts.map +1 -1
- package/dist/store/exodus/reconcile.js +246 -173
- package/dist/store/exodus/reconcile.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts +48 -0
- package/dist/store/exodus/recovery.d.ts.map +1 -1
- package/dist/store/exodus/recovery.js +104 -6
- package/dist/store/exodus/recovery.js.map +1 -1
- package/dist/store/exodus/seal.d.ts.map +1 -1
- package/dist/store/exodus/seal.js +4 -2
- package/dist/store/exodus/seal.js.map +1 -1
- package/dist/store/exodus/table-order.d.ts +6 -0
- package/dist/store/exodus/table-order.d.ts.map +1 -1
- package/dist/store/exodus/table-order.js +15 -0
- package/dist/store/exodus/table-order.js.map +1 -1
- package/dist/store/exodus/types.d.ts +4 -0
- package/dist/store/exodus/types.d.ts.map +1 -1
- package/dist/store/exodus/types.js +4 -0
- package/dist/store/exodus/types.js.map +1 -1
- package/dist/store/exodus/verify-migration.d.ts.map +1 -1
- package/dist/store/exodus/verify-migration.js +22 -17
- package/dist/store/exodus/verify-migration.js.map +1 -1
- package/dist/store/identity-share.d.ts +14 -0
- package/dist/store/identity-share.d.ts.map +1 -1
- package/dist/store/identity-share.js +33 -0
- package/dist/store/identity-share.js.map +1 -1
- package/dist/store/lock.d.ts +33 -0
- package/dist/store/lock.d.ts.map +1 -1
- package/dist/store/lock.js +60 -0
- package/dist/store/lock.js.map +1 -1
- package/dist/store/portable-bundle-import.d.ts.map +1 -1
- package/dist/store/portable-bundle-import.js +7 -0
- package/dist/store/portable-bundle-import.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts +3 -2
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +7 -5
- package/dist/store/portable-bundle-scan.js.map +1 -1
- package/dist/store/portable-bundle.d.ts +1 -1
- package/dist/store/portable-bundle.d.ts.map +1 -1
- package/dist/store/portable-bundle.js +9 -1
- package/dist/store/portable-bundle.js.map +1 -1
- package/dist/store/repair-malformed-dbs.d.ts.map +1 -1
- package/dist/store/repair-malformed-dbs.js +36 -6
- package/dist/store/repair-malformed-dbs.js.map +1 -1
- package/dist/store/restore-marker.d.ts +20 -2
- package/dist/store/restore-marker.d.ts.map +1 -1
- package/dist/store/restore-marker.js +41 -1
- package/dist/store/restore-marker.js.map +1 -1
- package/dist/store/row-identity-flag.d.ts +9 -6
- package/dist/store/row-identity-flag.d.ts.map +1 -1
- package/dist/store/row-identity-flag.js +10 -7
- package/dist/store/row-identity-flag.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts +4 -3
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +132 -31
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/row-identity.d.ts +81 -14
- package/dist/store/row-identity.d.ts.map +1 -1
- package/dist/store/row-identity.js +238 -32
- package/dist/store/row-identity.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +368 -0
- package/dist/store/schema/cleo-shared/brain.d.ts.map +1 -1
- package/dist/store/schema/cleo-shared/brain.js +104 -2
- package/dist/store/schema/cleo-shared/brain.js.map +1 -1
- package/dist/store/sync/apply/applier.d.ts +5 -0
- package/dist/store/sync/apply/applier.d.ts.map +1 -1
- package/dist/store/sync/apply/applier.js +5 -1
- package/dist/store/sync/apply/applier.js.map +1 -1
- package/dist/store/sync/apply/parent-delete.d.ts.map +1 -1
- package/dist/store/sync/apply/parent-delete.js +4 -0
- package/dist/store/sync/apply/parent-delete.js.map +1 -1
- package/dist/store/sync/capture.d.ts +13 -1
- package/dist/store/sync/capture.d.ts.map +1 -1
- package/dist/store/sync/capture.js +31 -3
- package/dist/store/sync/capture.js.map +1 -1
- package/dist/store/sync/flags.d.ts +14 -5
- package/dist/store/sync/flags.d.ts.map +1 -1
- package/dist/store/sync/flags.js +20 -8
- package/dist/store/sync/flags.js.map +1 -1
- package/dist/store/sync/genesis.d.ts +130 -0
- package/dist/store/sync/genesis.d.ts.map +1 -0
- package/dist/store/sync/genesis.js +337 -0
- package/dist/store/sync/genesis.js.map +1 -0
- package/dist/store/sync/repair.d.ts +2 -2
- package/dist/store/sync/repair.d.ts.map +1 -1
- package/dist/store/sync/repair.js +55 -15
- package/dist/store/sync/repair.js.map +1 -1
- package/dist/store/sync/sealer.d.ts +14 -0
- package/dist/store/sync/sealer.d.ts.map +1 -1
- package/dist/store/sync/sealer.js +27 -6
- package/dist/store/sync/sealer.js.map +1 -1
- package/dist/store/sync/segments.d.ts +124 -0
- package/dist/store/sync/segments.d.ts.map +1 -0
- package/dist/store/sync/segments.js +267 -0
- package/dist/store/sync/segments.js.map +1 -0
- package/dist/store/sync/sequencing.d.ts +44 -1
- package/dist/store/sync/sequencing.d.ts.map +1 -1
- package/dist/store/sync/sequencing.js +59 -2
- package/dist/store/sync/sequencing.js.map +1 -1
- package/dist/store/sync/timestamp-canon.d.ts +6 -3
- package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
- package/dist/store/sync/timestamp-canon.js +55 -4
- package/dist/store/sync/timestamp-canon.js.map +1 -1
- package/dist/store/sync/txn-signing.d.ts +69 -0
- package/dist/store/sync/txn-signing.d.ts.map +1 -0
- package/dist/store/sync/txn-signing.js +81 -0
- package/dist/store/sync/txn-signing.js.map +1 -0
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +30 -0
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +65 -2
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/system/backup.d.ts +3 -2
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +43 -17
- package/dist/system/backup.js.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +2 -1
- package/dist/system/health.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +26 -0
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +95 -28
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +14 -4
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +50 -4
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/test-run-binding.d.ts +14 -0
- package/dist/tasks/test-run-binding.d.ts.map +1 -1
- package/dist/tasks/test-run-binding.js +35 -4
- package/dist/tasks/test-run-binding.js.map +1 -1
- package/dist/upgrade.d.ts +6 -0
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +58 -11
- package/dist/upgrade.js.map +1 -1
- package/migrations/drizzle-cleo-global/20261010120000_t12894-brain-row-uids/migration.sql +88 -0
- package/migrations/drizzle-cleo-project/20261010120000_t12894-brain-row-uids/migration.sql +92 -0
- package/migrations/sync-journal/20261007120000_t12343-segment-outbox/migration.sql +38 -0
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +1 -1
- package/templates/CLEO-REFERENCE.md +2 -2
|
@@ -50,8 +50,9 @@
|
|
|
50
50
|
*
|
|
51
51
|
* A row an older build inserts keeps NULL values until the next open by this
|
|
52
52
|
* build: there is no persistent trigger that computes them (see the
|
|
53
|
-
* migration's header). Row uids are
|
|
54
|
-
* `CLEO_ROW_UID_FILL=
|
|
53
|
+
* migration's header). Row uids are on by default (T13305); nothing here
|
|
54
|
+
* runs when the kill switch `CLEO_ROW_UID_FILL=0` is set
|
|
55
|
+
* ({@link rowUidFillEnabled}).
|
|
55
56
|
*
|
|
56
57
|
* A failure never fails the open: it is logged, and the rows keep NULL values
|
|
57
58
|
* until a later open succeeds. A uid's timestamp is ordering sugar only: HLC
|
|
@@ -192,6 +193,20 @@ export function parseStoreTimestamp(value) {
|
|
|
192
193
|
return null;
|
|
193
194
|
return utc - sign * offset;
|
|
194
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* A stored birth as epoch ms: a timestamp text ({@link parseStoreTimestamp}),
|
|
198
|
+
* or an INTEGER epoch-ms birth column as is (`brain_attention.created_at`,
|
|
199
|
+
* T12894). A TEXT-affinity column never hands over a number, so the birth of
|
|
200
|
+
* every table declared before T12894 reads exactly as before.
|
|
201
|
+
*/
|
|
202
|
+
function birthEpochMs(birth) {
|
|
203
|
+
if (typeof birth === 'number' && Number.isSafeInteger(birth))
|
|
204
|
+
return birth;
|
|
205
|
+
if (typeof birth === 'bigint') {
|
|
206
|
+
return birth >= 0n && birth <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(birth) : null;
|
|
207
|
+
}
|
|
208
|
+
return parseStoreTimestamp(birth);
|
|
209
|
+
}
|
|
195
210
|
/** Largest value a UUIDv7 timestamp field holds. */
|
|
196
211
|
const MAX_UUID_MS = 2 ** 48 - 1;
|
|
197
212
|
/** Format 16 bytes as a lowercase canonical UUID string. */
|
|
@@ -225,7 +240,7 @@ export function mintRowUid(birthMs) {
|
|
|
225
240
|
* @returns Canonical lowercase UUID string.
|
|
226
241
|
*/
|
|
227
242
|
export function mintedRowUid(scope, table, key, birth, ownerUids = [], content = []) {
|
|
228
|
-
const birthMs =
|
|
243
|
+
const birthMs = birthEpochMs(birth);
|
|
229
244
|
const canonicalBirth = birthMs ?? birth;
|
|
230
245
|
const h = createHash('sha256')
|
|
231
246
|
.update(encodeUidInputs([
|
|
@@ -299,7 +314,7 @@ export function naturalRowUid(scope, table, parts, recipe = 'natural') {
|
|
|
299
314
|
function birthToken(birth) {
|
|
300
315
|
if (birth === null)
|
|
301
316
|
return 'birth:unknown';
|
|
302
|
-
const ms =
|
|
317
|
+
const ms = birthEpochMs(birth);
|
|
303
318
|
if (ms !== null)
|
|
304
319
|
return `ms:${ms}`;
|
|
305
320
|
return `birth:unparseable:${canonicalText(String(birth))}`;
|
|
@@ -547,6 +562,20 @@ function birthFpCallSql(scope, spec, row) {
|
|
|
547
562
|
const facts = (spec.birthFacts ?? []).map((f) => birthFactSql(scope, spec, f, row));
|
|
548
563
|
return `${ROW_BIRTH_FP_SQL_FUNCTION}(${[`'${spec.table}'`, birth, ...facts].join(', ')})`;
|
|
549
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* SQL that is true exactly when {@link storedRefSql} would be NOT NULL for
|
|
567
|
+
* `row`, without calling a JS function: the AC text hash is non-NULL iff the
|
|
568
|
+
* referenced row's text is a string, which SQLite says as `typeof = 'text'`.
|
|
569
|
+
* The pending probe uses it, so a store with many legitimately dangling refs
|
|
570
|
+
* opens without one JS hash per dangling row (T13261).
|
|
571
|
+
*/
|
|
572
|
+
function storedRefPresentSql(scope, ref, row) {
|
|
573
|
+
if (ref.source === 'text_hash') {
|
|
574
|
+
return (`(SELECT typeof(_r.${q('text')}) FROM main.${q(ref.table)} AS _r ` +
|
|
575
|
+
`WHERE _r.${q(targetKey(scope, ref.table))} = ${row}.${q(ref.from)}) = 'text'`);
|
|
576
|
+
}
|
|
577
|
+
return `${refUidSql(scope, { column: ref.from, table: ref.table }, row)} IS NOT NULL`;
|
|
578
|
+
}
|
|
550
579
|
/** SQL for a stored reference fact (`ac_uid`, `ac_text_hash`) read from `row`. */
|
|
551
580
|
function storedRefSql(scope, ref, row) {
|
|
552
581
|
if (ref.source === 'text_hash') {
|
|
@@ -1097,6 +1126,22 @@ function readMeta(db, key) {
|
|
|
1097
1126
|
.get(key);
|
|
1098
1127
|
return row?.value;
|
|
1099
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Whether the store's identity values follow the current
|
|
1131
|
+
* {@link ROW_IDENTITY_RECIPE} (its marker, which the fill writes): no refill
|
|
1132
|
+
* is due. The recipe applies to project stores only; a global store is
|
|
1133
|
+
* always current. Read-only (a read-only handle is fine).
|
|
1134
|
+
*
|
|
1135
|
+
* @param db - Connection on a `cleo.db`.
|
|
1136
|
+
* @param scope - The store's scope. @defaultValue 'project'
|
|
1137
|
+
* @returns True when no recipe refill is owed.
|
|
1138
|
+
* @task T13231
|
|
1139
|
+
*/
|
|
1140
|
+
export function rowIdentityRecipeCurrent(db, scope = 'project') {
|
|
1141
|
+
if (scope !== 'project')
|
|
1142
|
+
return true;
|
|
1143
|
+
return readMeta(db, ROW_IDENTITY_RECIPE_KEY) === ROW_IDENTITY_RECIPE;
|
|
1144
|
+
}
|
|
1100
1145
|
/** The pre-release (v4/v5 build) parse of a birth: a zoneless SQLite value as UTC, else `Date.parse`. */
|
|
1101
1146
|
function preReleaseParses(value) {
|
|
1102
1147
|
if (typeof value !== 'string' || value.length === 0)
|
|
@@ -1365,18 +1410,6 @@ export function snapshotIfFullRefillDue(db, scope, share = {}) {
|
|
|
1365
1410
|
return null;
|
|
1366
1411
|
return snapshotBeforeRefill(db);
|
|
1367
1412
|
}
|
|
1368
|
-
/**
|
|
1369
|
-
* Whether the store's identity follows the current recipe (its marker is
|
|
1370
|
-
* {@link ROW_IDENTITY_RECIPE}): no refill is due. Read-only.
|
|
1371
|
-
*
|
|
1372
|
-
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
1373
|
-
* @returns `true` when the marker is current.
|
|
1374
|
-
* @task T13231
|
|
1375
|
-
*/
|
|
1376
|
-
export function rowIdentityRecipeCurrent(db) {
|
|
1377
|
-
return (hasTable(db, ROW_IDENTITY_META_TABLE) &&
|
|
1378
|
-
readMeta(db, ROW_IDENTITY_RECIPE_KEY) === ROW_IDENTITY_RECIPE);
|
|
1379
|
-
}
|
|
1380
1413
|
/**
|
|
1381
1414
|
* What a full from-scratch refill clears: per declared table (alias tables
|
|
1382
1415
|
* excluded), the rows carrying any identity value, plus the AC uid graveyard
|
|
@@ -1494,12 +1527,93 @@ function resetStaleIdentity(db, scope, writers, refillSnapshot, shareOptions = {
|
|
|
1494
1527
|
getLogger('row-identity').info({ scope, snapshot }, 'stale identity on an unshared store: every identity value cleared for a from-scratch refill');
|
|
1495
1528
|
return 'cleared';
|
|
1496
1529
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1530
|
+
// T13305: the refusal is recorded, so later opens take the fast path while
|
|
1531
|
+
// nothing changed, and it is said at most once a day (a doctor finding
|
|
1532
|
+
// carries it in between).
|
|
1533
|
+
const key = shareKeyOf(share);
|
|
1534
|
+
const prior = readRowIdentityRefusal(db);
|
|
1535
|
+
const now = Date.now();
|
|
1536
|
+
const sameRefusal = prior?.state === 'refused' && prior.recipe === ROW_IDENTITY_RECIPE && prior.shareKey === key;
|
|
1537
|
+
const warnDue = !sameRefusal || prior === null || now - Date.parse(prior.warnedAt) >= REFUSAL_WARN_INTERVAL_MS;
|
|
1538
|
+
if (warnDue) {
|
|
1539
|
+
getLogger('row-identity').warn({ scope, state: share.state, reasons: share.reasons }, share.state === 'shared'
|
|
1540
|
+
? 'identity values predate the current recipe but may have left this store; kept as they are'
|
|
1541
|
+
: 'identity values predate the current recipe and the store cannot be proven unshared; kept as they are. ' +
|
|
1542
|
+
'Remedy: `cleo doctor row-identity --refill` asks Cleo Nexus and shows the plan');
|
|
1543
|
+
}
|
|
1544
|
+
if (!sameRefusal || warnDue) {
|
|
1545
|
+
const marker = {
|
|
1546
|
+
state: 'refused',
|
|
1547
|
+
recipe: ROW_IDENTITY_RECIPE,
|
|
1548
|
+
shareKey: key,
|
|
1549
|
+
shareState: share.state,
|
|
1550
|
+
reasons: [...share.reasons],
|
|
1551
|
+
at: sameRefusal && prior ? prior.at : new Date(now).toISOString(),
|
|
1552
|
+
warnedAt: warnDue
|
|
1553
|
+
? new Date(now).toISOString()
|
|
1554
|
+
: (prior?.warnedAt ?? new Date(now).toISOString()),
|
|
1555
|
+
};
|
|
1556
|
+
writers.writeRowIdentityMetaNative(db, ROW_IDENTITY_REFUSED_KEY, JSON.stringify(marker));
|
|
1557
|
+
}
|
|
1501
1558
|
return 'refused';
|
|
1502
1559
|
}
|
|
1560
|
+
/** Meta key recording a refused full refill (T13305). */
|
|
1561
|
+
export const ROW_IDENTITY_REFUSED_KEY = 'row_identity_refused';
|
|
1562
|
+
/** How often a standing refusal is logged again (the doctor reports it in between). */
|
|
1563
|
+
export const REFUSAL_WARN_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
1564
|
+
/** Digest of a share verdict: a change of state or of any signal re-evaluates a refusal. */
|
|
1565
|
+
function shareKeyOf(share) {
|
|
1566
|
+
return createHash('sha256')
|
|
1567
|
+
.update(`${share.state}\n${share.signals.map((s) => `${s.code}:${s.detail}`).join('\n')}`)
|
|
1568
|
+
.digest('hex')
|
|
1569
|
+
.slice(0, 32);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* The store's recorded refusal of the full identity refill, if any.
|
|
1573
|
+
*
|
|
1574
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
1575
|
+
* @returns The refusal record, or `null`.
|
|
1576
|
+
* @task T13305
|
|
1577
|
+
*/
|
|
1578
|
+
export function readRowIdentityRefusal(db) {
|
|
1579
|
+
if (!hasTable(db, ROW_IDENTITY_META_TABLE))
|
|
1580
|
+
return null;
|
|
1581
|
+
const raw = readMeta(db, ROW_IDENTITY_REFUSED_KEY);
|
|
1582
|
+
if (raw === undefined)
|
|
1583
|
+
return null;
|
|
1584
|
+
try {
|
|
1585
|
+
return JSON.parse(raw);
|
|
1586
|
+
}
|
|
1587
|
+
catch {
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Whether a recorded refusal still holds: refused for the current recipe, and
|
|
1593
|
+
* the share verdict unchanged. Then the stale marker is not open-time work.
|
|
1594
|
+
*/
|
|
1595
|
+
function refusalHolds(db) {
|
|
1596
|
+
const prior = readRowIdentityRefusal(db);
|
|
1597
|
+
if (prior?.state !== 'refused' || prior.recipe !== ROW_IDENTITY_RECIPE)
|
|
1598
|
+
return false;
|
|
1599
|
+
return prior.shareKey === shareKeyOf(rowIdentityShareState(db));
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Clear a recorded refusal, so the next open (or this caller) re-evaluates
|
|
1603
|
+
* the full refill (`cleo doctor row-identity --refill`, T13305).
|
|
1604
|
+
*
|
|
1605
|
+
* @param db - Connection on a project `cleo.db`.
|
|
1606
|
+
* @param writers - The chokepoint writers.
|
|
1607
|
+
* @returns Whether a standing refusal was cleared.
|
|
1608
|
+
* @task T13305
|
|
1609
|
+
*/
|
|
1610
|
+
export function clearRowIdentityRefusal(db, writers = requireWriters()) {
|
|
1611
|
+
const prior = readRowIdentityRefusal(db);
|
|
1612
|
+
if (prior?.state !== 'refused')
|
|
1613
|
+
return false;
|
|
1614
|
+
writers.writeRowIdentityMetaNative(db, ROW_IDENTITY_REFUSED_KEY, JSON.stringify({ ...prior, state: 'cleared' }));
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1503
1617
|
/**
|
|
1504
1618
|
* Record that the store's identity values follow {@link ROW_IDENTITY_RECIPE}.
|
|
1505
1619
|
* The fill writes it; {@link markRowIdentityShared} writes it too on the
|
|
@@ -1552,7 +1666,7 @@ export function markRowIdentityShared(db, direction, writers = requireWriters())
|
|
|
1552
1666
|
* their indexes. No row value is read or written. A store whose uid migration
|
|
1553
1667
|
* was journaled without running its statements (journal Scenario 3 Case A:
|
|
1554
1668
|
* live cleocode under 9.25, spec §12.2) gets the full schema here; the fill,
|
|
1555
|
-
* refill and TEMP triggers
|
|
1669
|
+
* refill and TEMP triggers follow the fill flag ({@link prepareRowIdentity}).
|
|
1556
1670
|
*
|
|
1557
1671
|
* Never throws: a failure is logged and the open continues.
|
|
1558
1672
|
*
|
|
@@ -1676,11 +1790,18 @@ export function readRowIdentityHealHistory(db) {
|
|
|
1676
1790
|
* the graveyard trigger, and the alias / graveyard columns an early table
|
|
1677
1791
|
* lacks (T12878; the open heals them).
|
|
1678
1792
|
*
|
|
1679
|
-
*
|
|
1793
|
+
* The global store has no identity tables: only its declared uid columns
|
|
1794
|
+
* and indexes are checked (T12894).
|
|
1795
|
+
*
|
|
1796
|
+
* @param db - Connection on a `cleo.db` (read-only is fine).
|
|
1797
|
+
* @param scope - The store's scope (default `project`).
|
|
1680
1798
|
* @returns What is missing (empty when complete).
|
|
1681
1799
|
* @task T12878
|
|
1800
|
+
* @task T12894
|
|
1682
1801
|
*/
|
|
1683
|
-
export function missingRowIdentitySchema(db) {
|
|
1802
|
+
export function missingRowIdentitySchema(db, scope = 'project') {
|
|
1803
|
+
if (scope === 'global')
|
|
1804
|
+
return missingDeclaredSchema(db, scope);
|
|
1684
1805
|
if (!hasTable(db, 'tasks_task_acceptance_criteria'))
|
|
1685
1806
|
return [];
|
|
1686
1807
|
const missing = [];
|
|
@@ -1700,12 +1821,17 @@ export function missingRowIdentitySchema(db) {
|
|
|
1700
1821
|
if (!hasObject(db, 'trigger', 'trg_tasks_ac_uid_graveyard')) {
|
|
1701
1822
|
missing.push('trigger trg_tasks_ac_uid_graveyard');
|
|
1702
1823
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1824
|
+
missing.push(...missingDeclaredSchema(db, scope));
|
|
1825
|
+
return missing;
|
|
1826
|
+
}
|
|
1827
|
+
/** The uid columns and indexes {@link ensureRowIdentitySchema} heals, missing from a store. */
|
|
1828
|
+
function missingDeclaredSchema(db, scope) {
|
|
1829
|
+
const missing = [];
|
|
1830
|
+
for (const spec of ROW_IDENTITY[scope]) {
|
|
1705
1831
|
if (!hasTable(db, spec.table))
|
|
1706
1832
|
continue;
|
|
1707
1833
|
const cols = columnsOf(db, spec.table);
|
|
1708
|
-
for (const column of rowIdentityColumns(
|
|
1834
|
+
for (const column of rowIdentityColumns(scope, spec.table)) {
|
|
1709
1835
|
if (!cols.has(column))
|
|
1710
1836
|
missing.push(`column ${spec.table}.${column}`);
|
|
1711
1837
|
}
|
|
@@ -1788,15 +1914,18 @@ export function rowIdentityFillPending(db, scope) {
|
|
|
1788
1914
|
return [];
|
|
1789
1915
|
// The probes name identity columns: a store missing any of its identity
|
|
1790
1916
|
// schema is pending the full pass, which heals it first.
|
|
1791
|
-
if (scope
|
|
1792
|
-
(missingRowIdentitySchema(db).length > 0 || !fillIndexesPresent(db, scope))) {
|
|
1917
|
+
if (missingRowIdentitySchema(db, scope).length > 0 || !fillIndexesPresent(db, scope)) {
|
|
1793
1918
|
return ['schema'];
|
|
1794
1919
|
}
|
|
1795
1920
|
const pending = [];
|
|
1796
1921
|
// A stale marker is work even when the identity has synced: the pass then
|
|
1797
|
-
// refuses the refill and
|
|
1798
|
-
//
|
|
1799
|
-
|
|
1922
|
+
// refuses the refill and records it; the record keeps later opens fast and
|
|
1923
|
+
// quiet until the recipe or the share verdict changes (T13305).
|
|
1924
|
+
// T13305: a refusal recorded for this recipe, with the share verdict
|
|
1925
|
+
// unchanged, is settled: the pass would only refuse again.
|
|
1926
|
+
if (scope === 'project' &&
|
|
1927
|
+
readMeta(db, ROW_IDENTITY_RECIPE_KEY) !== ROW_IDENTITY_RECIPE &&
|
|
1928
|
+
!refusalHolds(db)) {
|
|
1800
1929
|
pending.push('recipe');
|
|
1801
1930
|
}
|
|
1802
1931
|
if (scope === 'project' &&
|
|
@@ -1815,7 +1944,7 @@ export function rowIdentityFillPending(db, scope) {
|
|
|
1815
1944
|
}
|
|
1816
1945
|
const row = `main.${q(spec.table)}`;
|
|
1817
1946
|
for (const ref of spec.storedRefUids ?? []) {
|
|
1818
|
-
if (any(spec.table, `${q(ref.column)} IS NULL AND
|
|
1947
|
+
if (any(spec.table, `${q(ref.column)} IS NULL AND ${storedRefPresentSql(scope, ref, row)}`)) {
|
|
1819
1948
|
pending.push(`ref:${spec.table}.${ref.column}`);
|
|
1820
1949
|
}
|
|
1821
1950
|
}
|
|
@@ -1879,4 +2008,81 @@ export function prepareRowIdentity(db, scope, options = {}) {
|
|
|
1879
2008
|
return null;
|
|
1880
2009
|
}
|
|
1881
2010
|
}
|
|
2011
|
+
// ---------------------------------------------------------------------------
|
|
2012
|
+
// Brain FTS5 update triggers vs the identity fill (T12894)
|
|
2013
|
+
// ---------------------------------------------------------------------------
|
|
2014
|
+
//
|
|
2015
|
+
// The brain FTS5 tables are external-content (`content=brain_<x>`), so their
|
|
2016
|
+
// AFTER UPDATE trigger replays a `'delete'` of the old row before indexing the
|
|
2017
|
+
// new one. A delete of a row the index does not hold corrupts the index
|
|
2018
|
+
// ("database disk image is malformed"). The uid fill is a per-connection TEMP
|
|
2019
|
+
// AFTER INSERT trigger that UPDATEs the row, and TEMP triggers fire before
|
|
2020
|
+
// main ones, so an unscoped update trigger deleted the new row from the index
|
|
2021
|
+
// before the insert trigger had added it; the open-time fill hits the same
|
|
2022
|
+
// path for any row the index is missing. Scoping each trigger to its indexed
|
|
2023
|
+
// columns (`AFTER UPDATE OF …`) keeps every write that leaves the indexed text
|
|
2024
|
+
// alone, the fill included, off the index. Kept here, not in its own module,
|
|
2025
|
+
// so the CLI's store-opening graph loads no extra module (gate 39).
|
|
2026
|
+
/** Each brain FTS5 table's content table and its indexed columns (in FTS order). */
|
|
2027
|
+
const BRAIN_FTS = [
|
|
2028
|
+
{ table: 'brain_decisions', columns: ['id', 'decision', 'rationale'] },
|
|
2029
|
+
{ table: 'brain_patterns', columns: ['id', 'pattern', 'context'] },
|
|
2030
|
+
{ table: 'brain_learnings', columns: ['id', 'insight', 'source'] },
|
|
2031
|
+
{ table: 'brain_observations', columns: ['id', 'title', 'narrative'] },
|
|
2032
|
+
];
|
|
2033
|
+
/**
|
|
2034
|
+
* The AFTER UPDATE content-sync trigger for one brain FTS5 table, fired only
|
|
2035
|
+
* when an indexed column changes.
|
|
2036
|
+
*
|
|
2037
|
+
* @param table - The content table, e.g. `brain_observations`.
|
|
2038
|
+
* @returns The `CREATE TRIGGER IF NOT EXISTS` statement.
|
|
2039
|
+
*/
|
|
2040
|
+
export function brainFtsUpdateTriggerSql(table) {
|
|
2041
|
+
const spec = BRAIN_FTS.find((f) => f.table === table);
|
|
2042
|
+
// @sync-invariant none:input-shape programming error: a caller named a table with no brain FTS index
|
|
2043
|
+
if (!spec)
|
|
2044
|
+
throw new Error(`no brain FTS table for ${table}`);
|
|
2045
|
+
const cols = spec.columns.join(', ');
|
|
2046
|
+
const oldCols = spec.columns.map((c) => `old.${c}`).join(', ');
|
|
2047
|
+
const newCols = spec.columns.map((c) => `new.${c}`).join(', ');
|
|
2048
|
+
return `CREATE TRIGGER IF NOT EXISTS ${table}_au AFTER UPDATE OF ${cols} ON ${table} BEGIN
|
|
2049
|
+
INSERT INTO ${table}_fts(${table}_fts, rowid, ${cols})
|
|
2050
|
+
VALUES('delete', old.rowid, ${oldCols});
|
|
2051
|
+
INSERT INTO ${table}_fts(rowid, ${cols})
|
|
2052
|
+
VALUES (new.rowid, ${newCols});
|
|
2053
|
+
END`;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Rewrite every existing unscoped brain FTS update trigger to its scoped form.
|
|
2057
|
+
* A store without the trigger is left alone (the FTS tables are created
|
|
2058
|
+
* lazily by `ensureFts5Tables`). Idempotent; one `sqlite_master` read when
|
|
2059
|
+
* there is nothing to do.
|
|
2060
|
+
*
|
|
2061
|
+
* @param db - The store handle.
|
|
2062
|
+
* @returns The triggers rewritten.
|
|
2063
|
+
*/
|
|
2064
|
+
export function upgradeBrainFtsUpdateTriggers(db) {
|
|
2065
|
+
const live = db
|
|
2066
|
+
.prepare("SELECT name, sql FROM main.sqlite_master WHERE type = 'trigger' AND name IN (?, ?, ?, ?)")
|
|
2067
|
+
.all(...BRAIN_FTS.map((f) => `${f.table}_au`));
|
|
2068
|
+
const rewritten = [];
|
|
2069
|
+
for (const { name, sql } of live) {
|
|
2070
|
+
if (/\bAFTER\s+UPDATE\s+OF\b/i.test(sql))
|
|
2071
|
+
continue;
|
|
2072
|
+
const table = name.slice(0, -'_au'.length);
|
|
2073
|
+
db.exec('SAVEPOINT brain_fts_au');
|
|
2074
|
+
try {
|
|
2075
|
+
db.exec(`DROP TRIGGER ${name}`);
|
|
2076
|
+
db.exec(brainFtsUpdateTriggerSql(table));
|
|
2077
|
+
db.exec('RELEASE brain_fts_au');
|
|
2078
|
+
}
|
|
2079
|
+
catch (err) {
|
|
2080
|
+
db.exec('ROLLBACK TO brain_fts_au');
|
|
2081
|
+
db.exec('RELEASE brain_fts_au');
|
|
2082
|
+
throw err;
|
|
2083
|
+
}
|
|
2084
|
+
rewritten.push(name);
|
|
2085
|
+
}
|
|
2086
|
+
return rewritten;
|
|
2087
|
+
}
|
|
1882
2088
|
//# sourceMappingURL=row-identity.js.map
|