@cleocode/core 2026.9.25 → 2026.9.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/import-tasks.d.ts.map +1 -1
- package/dist/admin/import-tasks.js +5 -2
- package/dist/admin/import-tasks.js.map +1 -1
- package/dist/admin/import.d.ts.map +1 -1
- package/dist/admin/import.js +7 -2
- package/dist/admin/import.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.d.ts.map +1 -1
- package/dist/cloud/index.js +1 -0
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/nexus-auth.d.ts +26 -1
- package/dist/cloud/nexus-auth.d.ts.map +1 -1
- package/dist/cloud/nexus-auth.js +61 -32
- package/dist/cloud/nexus-auth.js.map +1 -1
- package/dist/cloud/nexus-credentials.js +2 -2
- package/dist/cloud/nexus-credentials.js.map +1 -1
- package/dist/cloud/nexus-device.d.ts +809 -0
- package/dist/cloud/nexus-device.d.ts.map +1 -0
- package/dist/cloud/nexus-device.js +1680 -0
- package/dist/cloud/nexus-device.js.map +1 -0
- package/dist/cloud/nexus-enrol.d.ts +210 -0
- package/dist/cloud/nexus-enrol.d.ts.map +1 -0
- package/dist/cloud/nexus-enrol.js +1023 -0
- package/dist/cloud/nexus-enrol.js.map +1 -0
- package/dist/cloud/nexus-link.d.ts +3 -0
- package/dist/cloud/nexus-link.d.ts.map +1 -1
- package/dist/cloud/nexus-link.js +38 -4
- package/dist/cloud/nexus-link.js.map +1 -1
- package/dist/cloud/signing.d.ts +20 -0
- package/dist/cloud/signing.d.ts.map +1 -1
- package/dist/cloud/signing.js +14 -0
- package/dist/cloud/signing.js.map +1 -1
- package/dist/crypto/credentials.d.ts +61 -0
- package/dist/crypto/credentials.d.ts.map +1 -1
- package/dist/crypto/credentials.js +154 -58
- package/dist/crypto/credentials.js.map +1 -1
- package/dist/docs/export-document.js +830 -190
- package/dist/docs/export-document.js.map +3 -3
- package/dist/doctor/dependency-cycles.d.ts +41 -0
- package/dist/doctor/dependency-cycles.d.ts.map +1 -0
- package/dist/doctor/dependency-cycles.js +39 -0
- package/dist/doctor/dependency-cycles.js.map +1 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/row-identity.d.ts +11 -0
- package/dist/doctor/row-identity.d.ts.map +1 -1
- package/dist/doctor/row-identity.js +67 -9
- package/dist/doctor/row-identity.js.map +1 -1
- package/dist/error-catalog.d.ts.map +1 -1
- package/dist/error-catalog.js +4 -0
- package/dist/error-catalog.js.map +1 -1
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/error-registry.js +8 -0
- package/dist/error-registry.js.map +1 -1
- package/dist/llm/plugin-facade.js +857 -217
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/nexus/workspace.d.ts.map +1 -1
- package/dist/nexus/workspace.js +54 -4
- package/dist/nexus/workspace.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +1 -1
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/pivot.d.ts.map +1 -1
- package/dist/orchestrate/pivot.js +4 -2
- package/dist/orchestrate/pivot.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +3 -3
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.js +1 -1
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +3 -3
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/parallel.js +1 -1
- package/dist/orchestration/parallel.js.map +1 -1
- package/dist/orchestration/wave-topic.d.ts +4 -2
- package/dist/orchestration/wave-topic.d.ts.map +1 -1
- package/dist/orchestration/wave-topic.js +6 -4
- package/dist/orchestration/wave-topic.js.map +1 -1
- package/dist/orchestration/waves.d.ts +2 -1
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +4 -3
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +4 -2
- package/dist/phases/index.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +856 -216
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +77 -49
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/focus-state-store.d.ts +69 -23
- package/dist/sessions/focus-state-store.d.ts.map +1 -1
- package/dist/sessions/focus-state-store.js +113 -27
- package/dist/sessions/focus-state-store.js.map +1 -1
- package/dist/sessions/handoff.js +1 -1
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +24 -13
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/terminal-identity.d.ts +165 -17
- package/dist/sessions/terminal-identity.d.ts.map +1 -1
- package/dist/sessions/terminal-identity.js +341 -32
- package/dist/sessions/terminal-identity.js.map +1 -1
- package/dist/skills/orchestrator/startup.js +2 -2
- package/dist/skills/orchestrator/startup.js.map +1 -1
- package/dist/snapshot/index.d.ts +4 -0
- package/dist/snapshot/index.d.ts.map +1 -1
- package/dist/snapshot/index.js +50 -3
- package/dist/snapshot/index.js.map +1 -1
- package/dist/store/db-helpers.d.ts +15 -2
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +84 -3
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dependency-cycles.d.ts +164 -0
- package/dist/store/dependency-cycles.d.ts.map +1 -0
- package/dist/store/dependency-cycles.js +360 -0
- package/dist/store/dependency-cycles.js.map +1 -0
- package/dist/store/display-id-alias.d.ts +93 -37
- package/dist/store/display-id-alias.d.ts.map +1 -1
- package/dist/store/display-id-alias.js +590 -115
- package/dist/store/display-id-alias.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +34 -7
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +8 -1
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/global-salt.d.ts +35 -0
- package/dist/store/global-salt.d.ts.map +1 -1
- package/dist/store/global-salt.js +136 -16
- package/dist/store/global-salt.js.map +1 -1
- package/dist/store/migration-sqlite.d.ts.map +1 -1
- package/dist/store/migration-sqlite.js +19 -11
- package/dist/store/migration-sqlite.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +6 -0
- package/dist/store/portable-bundle-scan.js.map +1 -1
- package/dist/store/row-identity-registry.d.ts.map +1 -1
- package/dist/store/row-identity-registry.js +11 -3
- package/dist/store/row-identity-registry.js.map +1 -1
- package/dist/store/row-identity.d.ts +91 -1
- package/dist/store/row-identity.d.ts.map +1 -1
- package/dist/store/row-identity.js +276 -7
- package/dist/store/row-identity.js.map +1 -1
- package/dist/store/safety-data-accessor.d.ts +3 -2
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +5 -2
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/session-store.d.ts +63 -3
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +121 -8
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/split-brain-import.d.ts.map +1 -1
- package/dist/store/split-brain-import.js +36 -0
- package/dist/store/split-brain-import.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +50 -3
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +246 -13
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +14 -2
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +3 -2
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +5 -2
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/system/health.d.ts +2 -0
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +54 -0
- package/dist/system/health.js.map +1 -1
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +9 -6
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/analyze.d.ts.map +1 -1
- package/dist/tasks/analyze.js +3 -2
- package/dist/tasks/analyze.js.map +1 -1
- package/dist/tasks/evidence.d.ts +19 -1
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +79 -18
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/infer-add-params.js +1 -1
- package/dist/tasks/infer-add-params.js.map +1 -1
- package/dist/tasks/task-context.d.ts.map +1 -1
- package/dist/tasks/task-context.js +3 -1
- package/dist/tasks/task-context.js.map +1 -1
- package/dist/tasks/task-import.d.ts.map +1 -1
- package/dist/tasks/task-import.js +11 -3
- package/dist/tasks/task-import.js.map +1 -1
- package/dist/tasks/task-next.d.ts +5 -1
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +7 -2
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +3 -2
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
- package/dist/workgraph/scaffold-apply.js +14 -5
- package/dist/workgraph/scaffold-apply.js.map +1 -1
- package/migrations/drizzle-cleo-project/20260930160000_t12886-dependency-cycle-guard/migration.sql +76 -0
- package/migrations/drizzle-cleo-project/20260930160000_t12886-dependency-cycle-guard/revert.sql +7 -0
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +2 -2
- package/templates/CLEO-REFERENCE.md +1 -1
|
@@ -325,7 +325,28 @@ export declare function installRowUidTriggers(db: DatabaseSync, scope: TableScop
|
|
|
325
325
|
* {@link ROW_IDENTITY_RECIPE_KEY} by the first fill; bumped by any change to a
|
|
326
326
|
* recipe, a frozen content or birth-fact list, or the encoding.
|
|
327
327
|
*/
|
|
328
|
-
export declare const ROW_IDENTITY_RECIPE = "cleo/row-identity/
|
|
328
|
+
export declare const ROW_IDENTITY_RECIPE = "cleo/row-identity/v2";
|
|
329
|
+
/**
|
|
330
|
+
* The recipe 9.25 shipped (v1). v2 changed only the birth facts of AC history
|
|
331
|
+
* and evidence bindings (@refFp:ac_uid and ac_text_hash → none / ac_id,
|
|
332
|
+
* T12802); a store filled
|
|
333
|
+
* with v1 has those two tables' fingerprints re-derived (see
|
|
334
|
+
* {@link v1ReleaseBirthFp}).
|
|
335
|
+
*/
|
|
336
|
+
export declare const ROW_IDENTITY_RECIPE_V1 = "cleo/row-identity/v1";
|
|
337
|
+
/**
|
|
338
|
+
* The v1 fingerprint of an AC-history or binding row, to recognise a value
|
|
339
|
+
* the 9.25 recipe derived: v1 hashed the criterion's fingerprint
|
|
340
|
+
* (`@refFp:ac_uid`), or `ref:none` / `ref:missing`. Other tables: `null`
|
|
341
|
+
* (their v1 and v2 recipes are the same).
|
|
342
|
+
*
|
|
343
|
+
* @param db - Connection on the project `cleo.db`.
|
|
344
|
+
* @param table - Declared minted table.
|
|
345
|
+
* @param row - The stored row.
|
|
346
|
+
* @returns The v1 value, or `null`.
|
|
347
|
+
* @task T12802
|
|
348
|
+
*/
|
|
349
|
+
export declare function v1ReleaseBirthFp(db: DatabaseSync, table: string, row: Readonly<Record<string, UidInput>>): string | null;
|
|
329
350
|
/**
|
|
330
351
|
* Local-only key/value table of the row-identity layer. Not
|
|
331
352
|
* `tasks_schema_meta`: the twin collapse drops twin-only keys of that table on
|
|
@@ -370,6 +391,16 @@ export declare function preReleaseBirthFp(db: DatabaseSync, table: string, row:
|
|
|
370
391
|
* @param db - Connection on the project `cleo.db`.
|
|
371
392
|
*/
|
|
372
393
|
export declare function writeRecipeMarker(db: DatabaseSync, writers?: RowIdentityWriters): void;
|
|
394
|
+
/**
|
|
395
|
+
* Whether the store's identity values are shared with other devices
|
|
396
|
+
* ({@link ROW_IDENTITY_SYNCED_KEY} present): from then on no local write may
|
|
397
|
+
* clear or re-point a uid.
|
|
398
|
+
*
|
|
399
|
+
* @param db - Connection on the project `cleo.db`.
|
|
400
|
+
* @returns True once identity has been sent or received.
|
|
401
|
+
* @task T12806
|
|
402
|
+
*/
|
|
403
|
+
export declare function rowIdentityShared(db: DatabaseSync): boolean;
|
|
373
404
|
/**
|
|
374
405
|
* Record that identity values are now SHARED with other devices: the recipe
|
|
375
406
|
* marker, and {@link ROW_IDENTITY_SYNCED_KEY} (first write wins, with the
|
|
@@ -383,6 +414,65 @@ export declare function writeRecipeMarker(db: DatabaseSync, writers?: RowIdentit
|
|
|
383
414
|
* @task T12746
|
|
384
415
|
*/
|
|
385
416
|
export declare function markRowIdentityShared(db: DatabaseSync, direction: 'send' | 'receive', writers?: RowIdentityWriters): void;
|
|
417
|
+
/**
|
|
418
|
+
* Re-create the row-identity SCHEMA a store should have, on every open,
|
|
419
|
+
* whatever the fill flag (T12878). DDL only: the identity tables, the
|
|
420
|
+
* graveyard trigger, the columns an early table lacks, the uid columns and
|
|
421
|
+
* their indexes. No row value is read or written. A store whose uid migration
|
|
422
|
+
* was journaled without running its statements (journal Scenario 3 Case A:
|
|
423
|
+
* live cleocode under 9.25, spec §12.2) gets the full schema here; the fill,
|
|
424
|
+
* refill and TEMP triggers stay opt-in ({@link prepareRowIdentity}).
|
|
425
|
+
*
|
|
426
|
+
* Never throws: a failure is logged and the open continues.
|
|
427
|
+
*
|
|
428
|
+
* @param db - Connection on a `cleo.db`, inside the cold-open lease.
|
|
429
|
+
* @param scope - The store's scope.
|
|
430
|
+
* @returns The DDL statements it ran (empty when the schema was complete).
|
|
431
|
+
* @task T12878
|
|
432
|
+
*/
|
|
433
|
+
export declare function healRowIdentitySchema(db: DatabaseSync, scope: TableScope, writers?: RowIdentityWriters | undefined): string[];
|
|
434
|
+
/**
|
|
435
|
+
* Meta key of the heal receipt: when the open last re-created missing
|
|
436
|
+
* identity schema, and what (T12878). Local-only bookkeeping of the heal
|
|
437
|
+
* itself, in `tasks_row_identity_meta`; no row value of any other table is
|
|
438
|
+
* written. `cleo doctor` (the `row_identity` check) shows it.
|
|
439
|
+
*/
|
|
440
|
+
export declare const ROW_IDENTITY_HEAL_KEY = "row_identity_schema_healed";
|
|
441
|
+
/** The receipt {@link healRowIdentitySchema} leaves when it re-created schema. */
|
|
442
|
+
export interface RowIdentityHealReceipt {
|
|
443
|
+
readonly at: string;
|
|
444
|
+
readonly scope?: TableScope;
|
|
445
|
+
readonly statements: number;
|
|
446
|
+
/** Objects re-created or columns added (table, index, trigger or `table.column`). */
|
|
447
|
+
readonly objects: readonly string[];
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The last heal receipt of a store, read-only (`undefined` when the schema
|
|
451
|
+
* never needed healing).
|
|
452
|
+
*
|
|
453
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
454
|
+
* @returns The receipt.
|
|
455
|
+
* @task T12878
|
|
456
|
+
*/
|
|
457
|
+
export declare function readRowIdentityHealReceipt(db: DatabaseSync): RowIdentityHealReceipt | undefined;
|
|
458
|
+
/**
|
|
459
|
+
* Every heal receipt a store kept, oldest first (read-only).
|
|
460
|
+
*
|
|
461
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
462
|
+
* @returns The receipts (empty when the schema never needed healing).
|
|
463
|
+
* @task T12878
|
|
464
|
+
*/
|
|
465
|
+
export declare function readRowIdentityHealHistory(db: DatabaseSync): RowIdentityHealReceipt[];
|
|
466
|
+
/**
|
|
467
|
+
* Identity schema a project store lacks, read-only: missing identity tables,
|
|
468
|
+
* the graveyard trigger, and the alias / graveyard columns an early table
|
|
469
|
+
* lacks (T12878; the open heals them).
|
|
470
|
+
*
|
|
471
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
472
|
+
* @returns What is missing (empty when complete).
|
|
473
|
+
* @task T12878
|
|
474
|
+
*/
|
|
475
|
+
export declare function missingRowIdentitySchema(db: DatabaseSync): string[];
|
|
386
476
|
export declare function prepareRowIdentity(db: DatabaseSync, scope: TableScope, options?: {
|
|
387
477
|
readonly triggers?: boolean;
|
|
388
478
|
readonly writers?: RowIdentityWriters;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"row-identity.d.ts","sourceRoot":"","sources":["../../src/store/row-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAmC,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAKvF,OAAO,EACL,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,UAAU,EACX,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"row-identity.d.ts","sourceRoot":"","sources":["../../src/store/row-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,6 +67,7 @@ import { getLogger } from '../logger.js';
|
|
|
67
67
|
import { acTextHash } from '../tasks/ac-table.js';
|
|
68
68
|
import { rowUidFillEnabled } from './row-identity-flag.js';
|
|
69
69
|
import { BIRTH_FP_COLUMN, ROW_IDENTITY, rowIdentityColumns, rowIdentitySpec, UID_COLUMN, } from './row-identity-registry.js';
|
|
70
|
+
import { schemaWritesAllowed } from './worktree-build-guard.js';
|
|
70
71
|
export { BIRTH_FP_COLUMN, ROW_IDENTITY, rowIdentityColumns, rowIdentitySpec, UID_COLUMN };
|
|
71
72
|
/** Domain separator hashed into every deterministic uid. Changing it re-keys every row. */
|
|
72
73
|
export const ROW_UID_DOMAIN = 'cleo/row-uid/v1';
|
|
@@ -938,7 +939,49 @@ export function installRowUidTriggers(db, scope) {
|
|
|
938
939
|
* {@link ROW_IDENTITY_RECIPE_KEY} by the first fill; bumped by any change to a
|
|
939
940
|
* recipe, a frozen content or birth-fact list, or the encoding.
|
|
940
941
|
*/
|
|
941
|
-
export const ROW_IDENTITY_RECIPE = 'cleo/row-identity/
|
|
942
|
+
export const ROW_IDENTITY_RECIPE = 'cleo/row-identity/v2';
|
|
943
|
+
/**
|
|
944
|
+
* The recipe 9.25 shipped (v1). v2 changed only the birth facts of AC history
|
|
945
|
+
* and evidence bindings (@refFp:ac_uid and ac_text_hash → none / ac_id,
|
|
946
|
+
* T12802); a store filled
|
|
947
|
+
* with v1 has those two tables' fingerprints re-derived (see
|
|
948
|
+
* {@link v1ReleaseBirthFp}).
|
|
949
|
+
*/
|
|
950
|
+
export const ROW_IDENTITY_RECIPE_V1 = 'cleo/row-identity/v1';
|
|
951
|
+
/**
|
|
952
|
+
* The v1 fingerprint of an AC-history or binding row, to recognise a value
|
|
953
|
+
* the 9.25 recipe derived: v1 hashed the criterion's fingerprint
|
|
954
|
+
* (`@refFp:ac_uid`), or `ref:none` / `ref:missing`. Other tables: `null`
|
|
955
|
+
* (their v1 and v2 recipes are the same).
|
|
956
|
+
*
|
|
957
|
+
* @param db - Connection on the project `cleo.db`.
|
|
958
|
+
* @param table - Declared minted table.
|
|
959
|
+
* @param row - The stored row.
|
|
960
|
+
* @returns The v1 value, or `null`.
|
|
961
|
+
* @task T12802
|
|
962
|
+
*/
|
|
963
|
+
export function v1ReleaseBirthFp(db, table, row) {
|
|
964
|
+
const col = (c) => row[c] ?? null;
|
|
965
|
+
const facts = table === 'tasks_task_acceptance_criteria_history'
|
|
966
|
+
? [col('ac_id'), col('previous_text'), col('reason')]
|
|
967
|
+
: table === 'tasks_evidence_ac_bindings'
|
|
968
|
+
? [col('evidence_atom_id'), col('binding_type'), col('ac_text_hash')]
|
|
969
|
+
: [];
|
|
970
|
+
if (facts.length === 0)
|
|
971
|
+
return null;
|
|
972
|
+
const acUid = col('ac_uid');
|
|
973
|
+
let ref = 'ref:none';
|
|
974
|
+
if (acUid !== null) {
|
|
975
|
+
const ac = db
|
|
976
|
+
.prepare('SELECT birth_fp AS fp FROM main.tasks_task_acceptance_criteria WHERE uid = ?')
|
|
977
|
+
.get(acUid);
|
|
978
|
+
ref = ac === undefined ? 'ref:missing' : ac.fp;
|
|
979
|
+
if (ref === null)
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
const spec = rowIdentitySpec('project', table);
|
|
983
|
+
return birthFingerprint(table, spec?.birth ? col(spec.birth) : null, [...facts, ref]);
|
|
984
|
+
}
|
|
942
985
|
/**
|
|
943
986
|
* Local-only key/value table of the row-identity layer. Not
|
|
944
987
|
* `tasks_schema_meta`: the twin collapse drops twin-only keys of that table on
|
|
@@ -1124,7 +1167,8 @@ export function preReleaseBirthFp(db, table, row) {
|
|
|
1124
1167
|
* Birth fingerprints a pre-release build derived (e.g. a worktree CLI that
|
|
1125
1168
|
* opened live cleocode, 2026-09-28) are cleared, so the fill re-derives them
|
|
1126
1169
|
* with the release recipe. Targeted: a value is cleared ONLY when it equals
|
|
1127
|
-
* {@link preReleaseBirthFp} of its row
|
|
1170
|
+
* {@link preReleaseBirthFp} of its row, or hashes a fingerprint cleared here
|
|
1171
|
+
* (@ownerFp / @refFp; T12801); values from any other source (a
|
|
1128
1172
|
* device that received them by sync, a store whose meta table was lost) are
|
|
1129
1173
|
* kept, and alias rows are never touched. The uid recipe did not change, so
|
|
1130
1174
|
* uids are kept. Runs only while the recipe marker is missing or stale, and
|
|
@@ -1135,26 +1179,64 @@ function resetStaleIdentity(db, scope, writers) {
|
|
|
1135
1179
|
return 'none';
|
|
1136
1180
|
if (readMeta(db, ROW_IDENTITY_RECIPE_KEY) === ROW_IDENTITY_RECIPE)
|
|
1137
1181
|
return 'none';
|
|
1138
|
-
const minted = ROW_IDENTITY.project.filter((spec) => spec.
|
|
1139
|
-
|
|
1140
|
-
|
|
1182
|
+
const minted = fingerprintOrder('project', ROW_IDENTITY.project).filter((spec) => hasTable(db, spec.table) && columnsOf(db, spec.table).has(BIRTH_FP_COLUMN));
|
|
1183
|
+
// A value is stale when it equals the pre-release recipe of its row, OR when
|
|
1184
|
+
// it was derived from a stale value: a fingerprint that hashes its owner's
|
|
1185
|
+
// or its criterion's fingerprint (@ownerFp / @refFp) whose source is being
|
|
1186
|
+
// cleared. That covers a criterion whose uid and fingerprint an edit carried
|
|
1187
|
+
// onto a new row (the pre-release recipe cannot be recomputed from the new
|
|
1188
|
+
// row, T12801). Tables are visited owners first.
|
|
1141
1189
|
const stale = [];
|
|
1190
|
+
const cleared = new Map(); // `${table}.${column}` → cleared values
|
|
1191
|
+
const mark = (table, column, value) => {
|
|
1192
|
+
if (value === null || value === undefined)
|
|
1193
|
+
return;
|
|
1194
|
+
const key = `${table}.${column}`;
|
|
1195
|
+
const set = cleared.get(key) ?? new Set();
|
|
1196
|
+
set.add(String(value));
|
|
1197
|
+
cleared.set(key, set);
|
|
1198
|
+
};
|
|
1142
1199
|
for (const spec of minted) {
|
|
1200
|
+
const deps = (spec.birthFacts ?? [])
|
|
1201
|
+
.map((fact) => fingerprintRef('project', spec, fact))
|
|
1202
|
+
.filter((d) => d !== null);
|
|
1203
|
+
const keyColumn = targetKey('project', spec.table);
|
|
1143
1204
|
const rows = db
|
|
1144
1205
|
.prepare(`SELECT rowid AS _rowid, * FROM main.${q(spec.table)} WHERE ${q(BIRTH_FP_COLUMN)} IS NOT NULL`)
|
|
1145
1206
|
.all();
|
|
1146
1207
|
for (const row of rows) {
|
|
1147
|
-
|
|
1208
|
+
const derivedFromStale = deps.some((d) => cleared.get(`${d.table}.${d.key}`)?.has(String(row[d.column] ?? '')));
|
|
1209
|
+
// Three sources of a stale value, all kept (T12801 x T12802): derived
|
|
1210
|
+
// from a value cleared in this pass, the pre-release recipe, or the
|
|
1211
|
+
// 9.25 (v1) recipe.
|
|
1212
|
+
if (derivedFromStale ||
|
|
1213
|
+
row[BIRTH_FP_COLUMN] === preReleaseBirthFp(db, spec.table, row) ||
|
|
1214
|
+
row[BIRTH_FP_COLUMN] === v1ReleaseBirthFp(db, spec.table, row)) {
|
|
1148
1215
|
stale.push({ table: spec.table, rowid: row._rowid });
|
|
1216
|
+
mark(spec.table, keyColumn, row[keyColumn]);
|
|
1217
|
+
mark(spec.table, UID_COLUMN, row[UID_COLUMN]);
|
|
1149
1218
|
}
|
|
1150
1219
|
}
|
|
1151
1220
|
}
|
|
1152
|
-
|
|
1221
|
+
// The AC uid graveyard records deletions made while the recipe marker was
|
|
1222
|
+
// missing or stale: with the fill off, the schema heal (T12878) restores
|
|
1223
|
+
// its trigger, which then records the stale pre-release uid and birth_fp of
|
|
1224
|
+
// every criterion deleted since. Relinking those would carry a stale
|
|
1225
|
+
// fingerprint into the release recipe and, through the ordinal fallback,
|
|
1226
|
+
// hand a deleted criterion's uid to an unrelated new one. They are not an
|
|
1227
|
+
// older build's delete-and-recreate of one edit, so they are dropped in
|
|
1228
|
+
// this same step, before the fill relinks anything (T12878 review F1).
|
|
1229
|
+
const graveyardRows = scope === 'project' && hasTable(db, AC_UID_GRAVEYARD)
|
|
1230
|
+
? db.prepare(`SELECT count(*) AS n FROM main.${AC_UID_GRAVEYARD}`).get().n
|
|
1231
|
+
: 0;
|
|
1232
|
+
if (stale.length === 0 && graveyardRows === 0)
|
|
1153
1233
|
return 'none';
|
|
1154
1234
|
if (readMeta(db, ROW_IDENTITY_SYNCED_KEY) !== undefined)
|
|
1155
1235
|
return 'refused';
|
|
1156
1236
|
db.exec('SAVEPOINT row_identity_reset');
|
|
1157
1237
|
try {
|
|
1238
|
+
if (graveyardRows > 0)
|
|
1239
|
+
writers.clearAcUidGraveyardNative(db);
|
|
1158
1240
|
for (const { table, rowid } of stale) {
|
|
1159
1241
|
writers.clearBirthFpNative(db, table, rowid);
|
|
1160
1242
|
}
|
|
@@ -1179,6 +1261,18 @@ export function writeRecipeMarker(db, writers = requireWriters()) {
|
|
|
1179
1261
|
return;
|
|
1180
1262
|
writers.writeRowIdentityMetaNative(db, ROW_IDENTITY_RECIPE_KEY, ROW_IDENTITY_RECIPE);
|
|
1181
1263
|
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Whether the store's identity values are shared with other devices
|
|
1266
|
+
* ({@link ROW_IDENTITY_SYNCED_KEY} present): from then on no local write may
|
|
1267
|
+
* clear or re-point a uid.
|
|
1268
|
+
*
|
|
1269
|
+
* @param db - Connection on the project `cleo.db`.
|
|
1270
|
+
* @returns True once identity has been sent or received.
|
|
1271
|
+
* @task T12806
|
|
1272
|
+
*/
|
|
1273
|
+
export function rowIdentityShared(db) {
|
|
1274
|
+
return readMeta(db, ROW_IDENTITY_SYNCED_KEY) !== undefined;
|
|
1275
|
+
}
|
|
1182
1276
|
/**
|
|
1183
1277
|
* Record that identity values are now SHARED with other devices: the recipe
|
|
1184
1278
|
* marker, and {@link ROW_IDENTITY_SYNCED_KEY} (first write wins, with the
|
|
@@ -1200,6 +1294,181 @@ export function markRowIdentityShared(db, direction, writers = requireWriters())
|
|
|
1200
1294
|
return;
|
|
1201
1295
|
writers.writeRowIdentityMetaNative(db, ROW_IDENTITY_SYNCED_KEY, JSON.stringify({ first: direction, at: new Date().toISOString() }));
|
|
1202
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Re-create the row-identity SCHEMA a store should have, on every open,
|
|
1299
|
+
* whatever the fill flag (T12878). DDL only: the identity tables, the
|
|
1300
|
+
* graveyard trigger, the columns an early table lacks, the uid columns and
|
|
1301
|
+
* their indexes. No row value is read or written. A store whose uid migration
|
|
1302
|
+
* was journaled without running its statements (journal Scenario 3 Case A:
|
|
1303
|
+
* live cleocode under 9.25, spec §12.2) gets the full schema here; the fill,
|
|
1304
|
+
* refill and TEMP triggers stay opt-in ({@link prepareRowIdentity}).
|
|
1305
|
+
*
|
|
1306
|
+
* Never throws: a failure is logged and the open continues.
|
|
1307
|
+
*
|
|
1308
|
+
* @param db - Connection on a `cleo.db`, inside the cold-open lease.
|
|
1309
|
+
* @param scope - The store's scope.
|
|
1310
|
+
* @returns The DDL statements it ran (empty when the schema was complete).
|
|
1311
|
+
* @task T12878
|
|
1312
|
+
*/
|
|
1313
|
+
export function healRowIdentitySchema(db, scope, writers = registeredWriters) {
|
|
1314
|
+
const log = getLogger('row-identity');
|
|
1315
|
+
if (ROW_IDENTITY[scope].length === 0) {
|
|
1316
|
+
// Nothing is declared in this scope yet (the global store today): there is
|
|
1317
|
+
// no identity schema to heal, so nothing to record either.
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
// A build that may not change this store's schema (a worktree-built CLI on a
|
|
1321
|
+
// live store, T12687) never heals it: the released build will.
|
|
1322
|
+
if (!schemaWritesAllowed(db)) {
|
|
1323
|
+
log.debug({ scope }, 'row identity schema heal skipped: schema writes refused for this build');
|
|
1324
|
+
return [];
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
db.exec('SAVEPOINT row_identity_heal');
|
|
1328
|
+
try {
|
|
1329
|
+
const healed = [
|
|
1330
|
+
...(scope === 'project' ? ensureIdentityTables(db) : []),
|
|
1331
|
+
...ensureRowIdentitySchema(db, scope),
|
|
1332
|
+
];
|
|
1333
|
+
if (healed.length > 0)
|
|
1334
|
+
recordHealReceipt(db, scope, healed, writers);
|
|
1335
|
+
db.exec('RELEASE SAVEPOINT row_identity_heal');
|
|
1336
|
+
if (healed.length > 0)
|
|
1337
|
+
log.info({ scope, healed: healed.length }, 'row identity schema healed');
|
|
1338
|
+
return healed;
|
|
1339
|
+
}
|
|
1340
|
+
catch (error) {
|
|
1341
|
+
db.exec('ROLLBACK TO SAVEPOINT row_identity_heal');
|
|
1342
|
+
db.exec('RELEASE SAVEPOINT row_identity_heal');
|
|
1343
|
+
throw error;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
catch (error) {
|
|
1347
|
+
log.error({ scope, error }, 'row identity schema heal failed');
|
|
1348
|
+
return [];
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Meta key of the heal receipt: when the open last re-created missing
|
|
1353
|
+
* identity schema, and what (T12878). Local-only bookkeeping of the heal
|
|
1354
|
+
* itself, in `tasks_row_identity_meta`; no row value of any other table is
|
|
1355
|
+
* written. `cleo doctor` (the `row_identity` check) shows it.
|
|
1356
|
+
*/
|
|
1357
|
+
export const ROW_IDENTITY_HEAL_KEY = 'row_identity_schema_healed';
|
|
1358
|
+
/** The table holding each scope's heal receipts (`null`: the scope has none yet). */
|
|
1359
|
+
const HEAL_RECEIPT_TABLE = {
|
|
1360
|
+
project: ROW_IDENTITY_META_TABLE,
|
|
1361
|
+
// The global store declares no identity table yet (ROW_IDENTITY.global is
|
|
1362
|
+
// empty, so its heal never runs); the change that declares one adds its
|
|
1363
|
+
// meta table here.
|
|
1364
|
+
global: null,
|
|
1365
|
+
};
|
|
1366
|
+
/** How many heal receipts a store keeps (newest last). */
|
|
1367
|
+
const HEAL_RECEIPTS_KEPT = 20;
|
|
1368
|
+
function recordHealReceipt(db, scope, healed, writers) {
|
|
1369
|
+
const table = HEAL_RECEIPT_TABLE[scope];
|
|
1370
|
+
const log = getLogger('row-identity');
|
|
1371
|
+
if (table === null || !hasTable(db, table) || !writers) {
|
|
1372
|
+
log.warn({ scope, healed: healed.length }, 'row identity schema healed; no receipt table');
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const objects = healed.map((stmt) => {
|
|
1376
|
+
const col = /ALTER TABLE main\.("?)(\w+)\1 ADD COLUMN ("?)(\w+)\3/.exec(stmt);
|
|
1377
|
+
if (col)
|
|
1378
|
+
return `${col[2]}.${col[4]}`;
|
|
1379
|
+
const obj = /(TABLE|INDEX|TRIGGER) IF NOT EXISTS main\.("?)(\w+)\2/.exec(stmt);
|
|
1380
|
+
return obj ? `${obj[1].toLowerCase()} ${obj[3]}` : stmt.slice(0, 60);
|
|
1381
|
+
});
|
|
1382
|
+
const receipt = {
|
|
1383
|
+
at: new Date().toISOString(),
|
|
1384
|
+
scope,
|
|
1385
|
+
statements: healed.length,
|
|
1386
|
+
objects,
|
|
1387
|
+
};
|
|
1388
|
+
// Appended, never overwritten: a later partial heal keeps the earlier ones.
|
|
1389
|
+
const history = [...readRowIdentityHealHistory(db), receipt].slice(-HEAL_RECEIPTS_KEPT);
|
|
1390
|
+
writers.writeRowIdentityMetaNative(db, ROW_IDENTITY_HEAL_KEY, JSON.stringify(history));
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* The last heal receipt of a store, read-only (`undefined` when the schema
|
|
1394
|
+
* never needed healing).
|
|
1395
|
+
*
|
|
1396
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
1397
|
+
* @returns The receipt.
|
|
1398
|
+
* @task T12878
|
|
1399
|
+
*/
|
|
1400
|
+
export function readRowIdentityHealReceipt(db) {
|
|
1401
|
+
const history = readRowIdentityHealHistory(db);
|
|
1402
|
+
return history[history.length - 1];
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Every heal receipt a store kept, oldest first (read-only).
|
|
1406
|
+
*
|
|
1407
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
1408
|
+
* @returns The receipts (empty when the schema never needed healing).
|
|
1409
|
+
* @task T12878
|
|
1410
|
+
*/
|
|
1411
|
+
export function readRowIdentityHealHistory(db) {
|
|
1412
|
+
const raw = readMeta(db, ROW_IDENTITY_HEAL_KEY);
|
|
1413
|
+
if (raw === undefined)
|
|
1414
|
+
return [];
|
|
1415
|
+
try {
|
|
1416
|
+
const parsed = JSON.parse(raw);
|
|
1417
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
return [];
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Identity schema a project store lacks, read-only: missing identity tables,
|
|
1425
|
+
* the graveyard trigger, and the alias / graveyard columns an early table
|
|
1426
|
+
* lacks (T12878; the open heals them).
|
|
1427
|
+
*
|
|
1428
|
+
* @param db - Connection on a project `cleo.db` (read-only is fine).
|
|
1429
|
+
* @returns What is missing (empty when complete).
|
|
1430
|
+
* @task T12878
|
|
1431
|
+
*/
|
|
1432
|
+
export function missingRowIdentitySchema(db) {
|
|
1433
|
+
if (!hasTable(db, 'tasks_task_acceptance_criteria'))
|
|
1434
|
+
return [];
|
|
1435
|
+
const missing = [];
|
|
1436
|
+
for (const table of Object.keys(IDENTITY_TABLE_DDL)) {
|
|
1437
|
+
if (!hasTable(db, table))
|
|
1438
|
+
missing.push(`table ${table}`);
|
|
1439
|
+
}
|
|
1440
|
+
for (const [table, column] of [
|
|
1441
|
+
['tasks_display_id_aliases', 'displaced_hlc'],
|
|
1442
|
+
['tasks_display_id_aliases', 'entity_birth_fp'],
|
|
1443
|
+
[AC_UID_GRAVEYARD, 'birth_fp'],
|
|
1444
|
+
]) {
|
|
1445
|
+
if (hasTable(db, table) && !columnsOf(db, table).has(column)) {
|
|
1446
|
+
missing.push(`column ${table}.${column}`);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (!hasObject(db, 'trigger', 'trg_tasks_ac_uid_graveyard')) {
|
|
1450
|
+
missing.push('trigger trg_tasks_ac_uid_graveyard');
|
|
1451
|
+
}
|
|
1452
|
+
// The uid columns and indexes ensureRowIdentitySchema heals.
|
|
1453
|
+
for (const spec of ROW_IDENTITY.project) {
|
|
1454
|
+
if (!hasTable(db, spec.table))
|
|
1455
|
+
continue;
|
|
1456
|
+
const cols = columnsOf(db, spec.table);
|
|
1457
|
+
for (const column of rowIdentityColumns('project', spec.table)) {
|
|
1458
|
+
if (!cols.has(column))
|
|
1459
|
+
missing.push(`column ${spec.table}.${column}`);
|
|
1460
|
+
}
|
|
1461
|
+
if (!uidIsPrimaryKey(db, spec.table) && !hasObject(db, 'index', `uq_${spec.table}_uid`)) {
|
|
1462
|
+
missing.push(`index uq_${spec.table}_uid`);
|
|
1463
|
+
}
|
|
1464
|
+
for (const ref of spec.storedRefUids ?? []) {
|
|
1465
|
+
const index = `idx_${spec.table}_${ref.column}`;
|
|
1466
|
+
if (!hasObject(db, 'index', index))
|
|
1467
|
+
missing.push(`index ${index}`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return missing;
|
|
1471
|
+
}
|
|
1203
1472
|
export function prepareRowIdentity(db, scope, options = {}) {
|
|
1204
1473
|
if (ROW_IDENTITY[scope].length === 0)
|
|
1205
1474
|
return null;
|