@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.
Files changed (321) hide show
  1. package/dist/bootstrap.d.ts +13 -0
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +21 -32
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/build-provenance.json +2 -2
  6. package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
  7. package/dist/cloud/nexus-cloud-status.js +3 -1
  8. package/dist/cloud/nexus-cloud-status.js.map +1 -1
  9. package/dist/cloud/nexus-device.d.ts +1 -1
  10. package/dist/cloud/nexus-presence-refresh.d.ts.map +1 -1
  11. package/dist/cloud/nexus-presence-refresh.js +20 -13
  12. package/dist/cloud/nexus-presence-refresh.js.map +1 -1
  13. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  14. package/dist/cloud/nexus-vault.js +4 -1
  15. package/dist/cloud/nexus-vault.js.map +1 -1
  16. package/dist/docs/derive-links.d.ts +69 -0
  17. package/dist/docs/derive-links.d.ts.map +1 -0
  18. package/dist/docs/derive-links.js +85 -0
  19. package/dist/docs/derive-links.js.map +1 -0
  20. package/dist/docs/docs-generator.d.ts.map +1 -1
  21. package/dist/docs/docs-generator.js +2 -0
  22. package/dist/docs/docs-generator.js.map +1 -1
  23. package/dist/docs/docs-update.d.ts.map +1 -1
  24. package/dist/docs/docs-update.js +24 -4
  25. package/dist/docs/docs-update.js.map +1 -1
  26. package/dist/docs/fetch-result.d.ts +77 -0
  27. package/dist/docs/fetch-result.d.ts.map +1 -0
  28. package/dist/docs/fetch-result.js +132 -0
  29. package/dist/docs/fetch-result.js.map +1 -0
  30. package/dist/docs/supersede.d.ts.map +1 -1
  31. package/dist/docs/supersede.js +9 -0
  32. package/dist/docs/supersede.js.map +1 -1
  33. package/dist/doctor/db-substrate.d.ts.map +1 -1
  34. package/dist/doctor/db-substrate.js +64 -29
  35. package/dist/doctor/db-substrate.js.map +1 -1
  36. package/dist/doctor/row-identity-refill.d.ts.map +1 -1
  37. package/dist/doctor/row-identity-refill.js +25 -10
  38. package/dist/doctor/row-identity-refill.js.map +1 -1
  39. package/dist/doctor/row-identity.d.ts.map +1 -1
  40. package/dist/doctor/row-identity.js +8 -2
  41. package/dist/doctor/row-identity.js.map +1 -1
  42. package/dist/doctor/sync-repair.d.ts +5 -0
  43. package/dist/doctor/sync-repair.d.ts.map +1 -1
  44. package/dist/doctor/sync-repair.js +11 -0
  45. package/dist/doctor/sync-repair.js.map +1 -1
  46. package/dist/doctor/sync-triggers.d.ts.map +1 -1
  47. package/dist/doctor/sync-triggers.js +3 -17
  48. package/dist/doctor/sync-triggers.js.map +1 -1
  49. package/dist/hygiene/validate-spawn-readiness.d.ts +6 -1
  50. package/dist/hygiene/validate-spawn-readiness.d.ts.map +1 -1
  51. package/dist/hygiene/validate-spawn-readiness.js +11 -6
  52. package/dist/hygiene/validate-spawn-readiness.js.map +1 -1
  53. package/dist/init.d.ts.map +1 -1
  54. package/dist/init.js +10 -1
  55. package/dist/init.js.map +1 -1
  56. package/dist/injection.d.ts +37 -12
  57. package/dist/injection.d.ts.map +1 -1
  58. package/dist/injection.js +152 -35
  59. package/dist/injection.js.map +1 -1
  60. package/dist/memory/brain-search.d.ts.map +1 -1
  61. package/dist/memory/brain-search.js +7 -32
  62. package/dist/memory/brain-search.js.map +1 -1
  63. package/dist/memory/pipeline-manifest-sqlite.d.ts +2 -0
  64. package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
  65. package/dist/memory/pipeline-manifest-sqlite.js +51 -9
  66. package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
  67. package/dist/nexus/assessment-projection.d.ts +96 -1
  68. package/dist/nexus/assessment-projection.d.ts.map +1 -1
  69. package/dist/nexus/assessment-projection.js +135 -0
  70. package/dist/nexus/assessment-projection.js.map +1 -1
  71. package/dist/nexus/assessment-store.d.ts +54 -0
  72. package/dist/nexus/assessment-store.d.ts.map +1 -1
  73. package/dist/nexus/assessment-store.js +161 -2
  74. package/dist/nexus/assessment-store.js.map +1 -1
  75. package/dist/nexus/knowledge.d.ts +19 -0
  76. package/dist/nexus/knowledge.d.ts.map +1 -1
  77. package/dist/nexus/knowledge.js +56 -2
  78. package/dist/nexus/knowledge.js.map +1 -1
  79. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  80. package/dist/orchestrate/spawn-ops.js +12 -1
  81. package/dist/orchestrate/spawn-ops.js.map +1 -1
  82. package/dist/release/commit-presence.d.ts +24 -0
  83. package/dist/release/commit-presence.d.ts.map +1 -1
  84. package/dist/release/commit-presence.js +31 -0
  85. package/dist/release/commit-presence.js.map +1 -1
  86. package/dist/release/plan.d.ts +8 -6
  87. package/dist/release/plan.d.ts.map +1 -1
  88. package/dist/release/plan.js +11 -6
  89. package/dist/release/plan.js.map +1 -1
  90. package/dist/release/reconcile.d.ts.map +1 -1
  91. package/dist/release/reconcile.js +10 -8
  92. package/dist/release/reconcile.js.map +1 -1
  93. package/dist/release/verify-provenance.d.ts.map +1 -1
  94. package/dist/release/verify-provenance.js +6 -8
  95. package/dist/release/verify-provenance.js.map +1 -1
  96. package/dist/resources/admission-ledger.d.ts +66 -0
  97. package/dist/resources/admission-ledger.d.ts.map +1 -1
  98. package/dist/resources/admission-ledger.js +145 -6
  99. package/dist/resources/admission-ledger.js.map +1 -1
  100. package/dist/resources/run-class.d.ts +71 -0
  101. package/dist/resources/run-class.d.ts.map +1 -1
  102. package/dist/resources/run-class.js +203 -0
  103. package/dist/resources/run-class.js.map +1 -1
  104. package/dist/resources/run-governed.d.ts +2 -0
  105. package/dist/resources/run-governed.d.ts.map +1 -1
  106. package/dist/resources/run-governed.js +1 -0
  107. package/dist/resources/run-governed.js.map +1 -1
  108. package/dist/scaffold/ensure-config.d.ts +20 -9
  109. package/dist/scaffold/ensure-config.d.ts.map +1 -1
  110. package/dist/scaffold/ensure-config.js +56 -23
  111. package/dist/scaffold/ensure-config.js.map +1 -1
  112. package/dist/scaffold/ensure-skills.d.ts +10 -0
  113. package/dist/scaffold/ensure-skills.d.ts.map +1 -1
  114. package/dist/scaffold/ensure-skills.js +47 -3
  115. package/dist/scaffold/ensure-skills.js.map +1 -1
  116. package/dist/scaffold/upgrade-file-journal.d.ts +79 -0
  117. package/dist/scaffold/upgrade-file-journal.d.ts.map +1 -0
  118. package/dist/scaffold/upgrade-file-journal.js +111 -0
  119. package/dist/scaffold/upgrade-file-journal.js.map +1 -0
  120. package/dist/session/engine-ops.d.ts.map +1 -1
  121. package/dist/session/engine-ops.js +10 -2
  122. package/dist/session/engine-ops.js.map +1 -1
  123. package/dist/sessions/index.d.ts.map +1 -1
  124. package/dist/sessions/index.js +3 -0
  125. package/dist/sessions/index.js.map +1 -1
  126. package/dist/spawn/worktree-lock-holder.d.ts +59 -1
  127. package/dist/spawn/worktree-lock-holder.d.ts.map +1 -1
  128. package/dist/spawn/worktree-lock-holder.js +114 -0
  129. package/dist/spawn/worktree-lock-holder.js.map +1 -1
  130. package/dist/store/attachment-store.d.ts.map +1 -1
  131. package/dist/store/attachment-store.js +38 -0
  132. package/dist/store/attachment-store.js.map +1 -1
  133. package/dist/store/backup-recover.d.ts +4 -4
  134. package/dist/store/backup-recover.d.ts.map +1 -1
  135. package/dist/store/backup-recover.js +11 -4
  136. package/dist/store/backup-recover.js.map +1 -1
  137. package/dist/store/backup-sidecar.d.ts +19 -1
  138. package/dist/store/backup-sidecar.d.ts.map +1 -1
  139. package/dist/store/backup-sidecar.js +57 -6
  140. package/dist/store/backup-sidecar.js.map +1 -1
  141. package/dist/store/db-helpers.js +1 -1
  142. package/dist/store/display-id-alias.js +1 -1
  143. package/dist/store/display-id-alias.js.map +1 -1
  144. package/dist/store/dual-scope-db.d.ts +2 -0
  145. package/dist/store/dual-scope-db.d.ts.map +1 -1
  146. package/dist/store/dual-scope-db.js +28 -16
  147. package/dist/store/dual-scope-db.js.map +1 -1
  148. package/dist/store/exodus/archive.d.ts +42 -2
  149. package/dist/store/exodus/archive.d.ts.map +1 -1
  150. package/dist/store/exodus/archive.js +81 -4
  151. package/dist/store/exodus/archive.js.map +1 -1
  152. package/dist/store/exodus/bare-family.d.ts +104 -0
  153. package/dist/store/exodus/bare-family.d.ts.map +1 -0
  154. package/dist/store/exodus/bare-family.js +470 -0
  155. package/dist/store/exodus/bare-family.js.map +1 -0
  156. package/dist/store/exodus/column-transforms.d.ts.map +1 -1
  157. package/dist/store/exodus/column-transforms.js +15 -8
  158. package/dist/store/exodus/column-transforms.js.map +1 -1
  159. package/dist/store/exodus/health.d.ts +2 -0
  160. package/dist/store/exodus/health.d.ts.map +1 -1
  161. package/dist/store/exodus/health.js +7 -1
  162. package/dist/store/exodus/health.js.map +1 -1
  163. package/dist/store/exodus/index.d.ts +2 -2
  164. package/dist/store/exodus/index.d.ts.map +1 -1
  165. package/dist/store/exodus/index.js +2 -2
  166. package/dist/store/exodus/index.js.map +1 -1
  167. package/dist/store/exodus/migrate.d.ts.map +1 -1
  168. package/dist/store/exodus/migrate.js +30 -1
  169. package/dist/store/exodus/migrate.js.map +1 -1
  170. package/dist/store/exodus/on-open.d.ts +11 -0
  171. package/dist/store/exodus/on-open.d.ts.map +1 -1
  172. package/dist/store/exodus/on-open.js +26 -1
  173. package/dist/store/exodus/on-open.js.map +1 -1
  174. package/dist/store/exodus/prior-recoveries.d.ts +3 -2
  175. package/dist/store/exodus/prior-recoveries.d.ts.map +1 -1
  176. package/dist/store/exodus/prior-recoveries.js +7 -3
  177. package/dist/store/exodus/prior-recoveries.js.map +1 -1
  178. package/dist/store/exodus/reconcile.d.ts +44 -6
  179. package/dist/store/exodus/reconcile.d.ts.map +1 -1
  180. package/dist/store/exodus/reconcile.js +246 -173
  181. package/dist/store/exodus/reconcile.js.map +1 -1
  182. package/dist/store/exodus/recovery.d.ts +48 -0
  183. package/dist/store/exodus/recovery.d.ts.map +1 -1
  184. package/dist/store/exodus/recovery.js +104 -6
  185. package/dist/store/exodus/recovery.js.map +1 -1
  186. package/dist/store/exodus/seal.d.ts.map +1 -1
  187. package/dist/store/exodus/seal.js +4 -2
  188. package/dist/store/exodus/seal.js.map +1 -1
  189. package/dist/store/exodus/table-order.d.ts +6 -0
  190. package/dist/store/exodus/table-order.d.ts.map +1 -1
  191. package/dist/store/exodus/table-order.js +15 -0
  192. package/dist/store/exodus/table-order.js.map +1 -1
  193. package/dist/store/exodus/types.d.ts +4 -0
  194. package/dist/store/exodus/types.d.ts.map +1 -1
  195. package/dist/store/exodus/types.js +4 -0
  196. package/dist/store/exodus/types.js.map +1 -1
  197. package/dist/store/exodus/verify-migration.d.ts.map +1 -1
  198. package/dist/store/exodus/verify-migration.js +22 -17
  199. package/dist/store/exodus/verify-migration.js.map +1 -1
  200. package/dist/store/identity-share.d.ts +14 -0
  201. package/dist/store/identity-share.d.ts.map +1 -1
  202. package/dist/store/identity-share.js +33 -0
  203. package/dist/store/identity-share.js.map +1 -1
  204. package/dist/store/lock.d.ts +33 -0
  205. package/dist/store/lock.d.ts.map +1 -1
  206. package/dist/store/lock.js +60 -0
  207. package/dist/store/lock.js.map +1 -1
  208. package/dist/store/portable-bundle-import.d.ts.map +1 -1
  209. package/dist/store/portable-bundle-import.js +7 -0
  210. package/dist/store/portable-bundle-import.js.map +1 -1
  211. package/dist/store/portable-bundle-scan.d.ts +3 -2
  212. package/dist/store/portable-bundle-scan.d.ts.map +1 -1
  213. package/dist/store/portable-bundle-scan.js +7 -5
  214. package/dist/store/portable-bundle-scan.js.map +1 -1
  215. package/dist/store/portable-bundle.d.ts +1 -1
  216. package/dist/store/portable-bundle.d.ts.map +1 -1
  217. package/dist/store/portable-bundle.js +9 -1
  218. package/dist/store/portable-bundle.js.map +1 -1
  219. package/dist/store/repair-malformed-dbs.d.ts.map +1 -1
  220. package/dist/store/repair-malformed-dbs.js +36 -6
  221. package/dist/store/repair-malformed-dbs.js.map +1 -1
  222. package/dist/store/restore-marker.d.ts +20 -2
  223. package/dist/store/restore-marker.d.ts.map +1 -1
  224. package/dist/store/restore-marker.js +41 -1
  225. package/dist/store/restore-marker.js.map +1 -1
  226. package/dist/store/row-identity-flag.d.ts +9 -6
  227. package/dist/store/row-identity-flag.d.ts.map +1 -1
  228. package/dist/store/row-identity-flag.js +10 -7
  229. package/dist/store/row-identity-flag.js.map +1 -1
  230. package/dist/store/row-identity-registry.d.ts +4 -3
  231. package/dist/store/row-identity-registry.d.ts.map +1 -1
  232. package/dist/store/row-identity-registry.js +132 -31
  233. package/dist/store/row-identity-registry.js.map +1 -1
  234. package/dist/store/row-identity.d.ts +81 -14
  235. package/dist/store/row-identity.d.ts.map +1 -1
  236. package/dist/store/row-identity.js +238 -32
  237. package/dist/store/row-identity.js.map +1 -1
  238. package/dist/store/schema/cleo-shared/brain.d.ts +368 -0
  239. package/dist/store/schema/cleo-shared/brain.d.ts.map +1 -1
  240. package/dist/store/schema/cleo-shared/brain.js +104 -2
  241. package/dist/store/schema/cleo-shared/brain.js.map +1 -1
  242. package/dist/store/sync/apply/applier.d.ts +5 -0
  243. package/dist/store/sync/apply/applier.d.ts.map +1 -1
  244. package/dist/store/sync/apply/applier.js +5 -1
  245. package/dist/store/sync/apply/applier.js.map +1 -1
  246. package/dist/store/sync/apply/parent-delete.d.ts.map +1 -1
  247. package/dist/store/sync/apply/parent-delete.js +4 -0
  248. package/dist/store/sync/apply/parent-delete.js.map +1 -1
  249. package/dist/store/sync/capture.d.ts +13 -1
  250. package/dist/store/sync/capture.d.ts.map +1 -1
  251. package/dist/store/sync/capture.js +31 -3
  252. package/dist/store/sync/capture.js.map +1 -1
  253. package/dist/store/sync/flags.d.ts +14 -5
  254. package/dist/store/sync/flags.d.ts.map +1 -1
  255. package/dist/store/sync/flags.js +20 -8
  256. package/dist/store/sync/flags.js.map +1 -1
  257. package/dist/store/sync/genesis.d.ts +130 -0
  258. package/dist/store/sync/genesis.d.ts.map +1 -0
  259. package/dist/store/sync/genesis.js +337 -0
  260. package/dist/store/sync/genesis.js.map +1 -0
  261. package/dist/store/sync/repair.d.ts +2 -2
  262. package/dist/store/sync/repair.d.ts.map +1 -1
  263. package/dist/store/sync/repair.js +55 -15
  264. package/dist/store/sync/repair.js.map +1 -1
  265. package/dist/store/sync/sealer.d.ts +14 -0
  266. package/dist/store/sync/sealer.d.ts.map +1 -1
  267. package/dist/store/sync/sealer.js +27 -6
  268. package/dist/store/sync/sealer.js.map +1 -1
  269. package/dist/store/sync/segments.d.ts +124 -0
  270. package/dist/store/sync/segments.d.ts.map +1 -0
  271. package/dist/store/sync/segments.js +267 -0
  272. package/dist/store/sync/segments.js.map +1 -0
  273. package/dist/store/sync/sequencing.d.ts +44 -1
  274. package/dist/store/sync/sequencing.d.ts.map +1 -1
  275. package/dist/store/sync/sequencing.js +59 -2
  276. package/dist/store/sync/sequencing.js.map +1 -1
  277. package/dist/store/sync/timestamp-canon.d.ts +6 -3
  278. package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
  279. package/dist/store/sync/timestamp-canon.js +55 -4
  280. package/dist/store/sync/timestamp-canon.js.map +1 -1
  281. package/dist/store/sync/txn-signing.d.ts +69 -0
  282. package/dist/store/sync/txn-signing.d.ts.map +1 -0
  283. package/dist/store/sync/txn-signing.js +81 -0
  284. package/dist/store/sync/txn-signing.js.map +1 -0
  285. package/dist/store/table-classification.d.ts.map +1 -1
  286. package/dist/store/table-classification.js +30 -0
  287. package/dist/store/table-classification.js.map +1 -1
  288. package/dist/store/twin-collapse.d.ts.map +1 -1
  289. package/dist/store/twin-collapse.js +65 -2
  290. package/dist/store/twin-collapse.js.map +1 -1
  291. package/dist/system/backup.d.ts +3 -2
  292. package/dist/system/backup.d.ts.map +1 -1
  293. package/dist/system/backup.js +43 -17
  294. package/dist/system/backup.js.map +1 -1
  295. package/dist/system/health.d.ts.map +1 -1
  296. package/dist/system/health.js +2 -1
  297. package/dist/system/health.js.map +1 -1
  298. package/dist/tasks/affected-packages.d.ts +26 -0
  299. package/dist/tasks/affected-packages.d.ts.map +1 -1
  300. package/dist/tasks/affected-packages.js +95 -28
  301. package/dist/tasks/affected-packages.js.map +1 -1
  302. package/dist/tasks/change-set.d.ts.map +1 -1
  303. package/dist/tasks/change-set.js +14 -4
  304. package/dist/tasks/change-set.js.map +1 -1
  305. package/dist/tasks/done-plan.d.ts.map +1 -1
  306. package/dist/tasks/done-plan.js +50 -4
  307. package/dist/tasks/done-plan.js.map +1 -1
  308. package/dist/tasks/test-run-binding.d.ts +14 -0
  309. package/dist/tasks/test-run-binding.d.ts.map +1 -1
  310. package/dist/tasks/test-run-binding.js +35 -4
  311. package/dist/tasks/test-run-binding.js.map +1 -1
  312. package/dist/upgrade.d.ts +6 -0
  313. package/dist/upgrade.d.ts.map +1 -1
  314. package/dist/upgrade.js +58 -11
  315. package/dist/upgrade.js.map +1 -1
  316. package/migrations/drizzle-cleo-global/20261010120000_t12894-brain-row-uids/migration.sql +88 -0
  317. package/migrations/drizzle-cleo-project/20261010120000_t12894-brain-row-uids/migration.sql +92 -0
  318. package/migrations/sync-journal/20261007120000_t12343-segment-outbox/migration.sql +38 -0
  319. package/package.json +13 -13
  320. package/templates/CLEO-INJECTION.md +1 -1
  321. 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 OPT-IN: nothing here runs unless
54
- * `CLEO_ROW_UID_FILL=1` ({@link rowUidFillEnabled}).
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 = parseStoreTimestamp(birth);
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 = parseStoreTimestamp(birth);
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
- getLogger('row-identity').error({ scope, state: share.state, reasons: share.reasons }, share.state === 'shared'
1498
- ? 'identity values predate the current recipe but may have left this store; kept as they are'
1499
- : 'identity values predate the current recipe and the store cannot be proven unshared; kept as they are. ' +
1500
- 'Remedy: `cleo doctor row-identity --refill` asks Cleo Nexus and shows the plan');
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 stay opt-in ({@link prepareRowIdentity}).
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
- * @param db - Connection on a project `cleo.db` (read-only is fine).
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
- // The uid columns and indexes ensureRowIdentitySchema heals.
1704
- for (const spec of ROW_IDENTITY.project) {
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('project', spec.table)) {
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 === 'project' &&
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 says so (refill 'refused'), every open, until the
1798
- // store is repaired — that anomaly must stay loud, not become a fast path.
1799
- if (scope === 'project' && readMeta(db, ROW_IDENTITY_RECIPE_KEY) !== ROW_IDENTITY_RECIPE) {
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 (${storedRefSql(scope, ref, row)}) IS NOT NULL`)) {
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