@cleocode/core 2026.10.8 → 2026.10.9

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 (266) hide show
  1. package/dist/build-provenance.json +2 -2
  2. package/dist/cloud/auto-sync-command-entry.d.ts +13 -0
  3. package/dist/cloud/auto-sync-command-entry.d.ts.map +1 -0
  4. package/dist/cloud/auto-sync-command-entry.js +18 -0
  5. package/dist/cloud/auto-sync-command-entry.js.map +1 -0
  6. package/dist/cloud/auto-sync-command.d.ts +64 -0
  7. package/dist/cloud/auto-sync-command.d.ts.map +1 -0
  8. package/dist/cloud/auto-sync-command.js +109 -0
  9. package/dist/cloud/auto-sync-command.js.map +1 -0
  10. package/dist/cloud/auto-sync.d.ts +111 -0
  11. package/dist/cloud/auto-sync.d.ts.map +1 -0
  12. package/dist/cloud/auto-sync.js +275 -0
  13. package/dist/cloud/auto-sync.js.map +1 -0
  14. package/dist/cloud/nexus-cloud-device-overview.d.ts +52 -0
  15. package/dist/cloud/nexus-cloud-device-overview.d.ts.map +1 -0
  16. package/dist/cloud/nexus-cloud-device-overview.js +105 -0
  17. package/dist/cloud/nexus-cloud-device-overview.js.map +1 -0
  18. package/dist/cloud/nexus-cloud-journal-activity.d.ts +56 -0
  19. package/dist/cloud/nexus-cloud-journal-activity.d.ts.map +1 -0
  20. package/dist/cloud/nexus-cloud-journal-activity.js +98 -0
  21. package/dist/cloud/nexus-cloud-journal-activity.js.map +1 -0
  22. package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
  23. package/dist/cloud/nexus-cloud-status.js +88 -5
  24. package/dist/cloud/nexus-cloud-status.js.map +1 -1
  25. package/dist/cloud/nexus-vault.d.ts +19 -5
  26. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  27. package/dist/cloud/nexus-vault.js +376 -71
  28. package/dist/cloud/nexus-vault.js.map +1 -1
  29. package/dist/cloud/replica-rebind.d.ts +14 -0
  30. package/dist/cloud/replica-rebind.d.ts.map +1 -1
  31. package/dist/cloud/replica-rebind.js +20 -1
  32. package/dist/cloud/replica-rebind.js.map +1 -1
  33. package/dist/config.d.ts +10 -0
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +19 -0
  36. package/dist/config.js.map +1 -1
  37. package/dist/docs/docs-ops.d.ts +4 -2
  38. package/dist/docs/docs-ops.d.ts.map +1 -1
  39. package/dist/docs/docs-ops.js +107 -15
  40. package/dist/docs/docs-ops.js.map +1 -1
  41. package/dist/doctor/system-health.d.ts +76 -2
  42. package/dist/doctor/system-health.d.ts.map +1 -1
  43. package/dist/doctor/system-health.js +264 -41
  44. package/dist/doctor/system-health.js.map +1 -1
  45. package/dist/hooks/project-runner.d.ts +10 -0
  46. package/dist/hooks/project-runner.d.ts.map +1 -0
  47. package/dist/hooks/project-runner.js +328 -0
  48. package/dist/hooks/project-runner.js.map +1 -0
  49. package/dist/hooks/project-state.d.ts +41 -0
  50. package/dist/hooks/project-state.d.ts.map +1 -0
  51. package/dist/hooks/project-state.js +331 -0
  52. package/dist/hooks/project-state.js.map +1 -0
  53. package/dist/hooks/release-gate.d.ts +61 -0
  54. package/dist/hooks/release-gate.d.ts.map +1 -0
  55. package/dist/hooks/release-gate.js +70 -0
  56. package/dist/hooks/release-gate.js.map +1 -0
  57. package/dist/nexus/analyze-orchestrator.d.ts +2 -0
  58. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  59. package/dist/nexus/analyze-orchestrator.js +1 -0
  60. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  61. package/dist/nexus/freshness.d.ts +67 -3
  62. package/dist/nexus/freshness.d.ts.map +1 -1
  63. package/dist/nexus/freshness.js +130 -9
  64. package/dist/nexus/freshness.js.map +1 -1
  65. package/dist/nexus/refresh-child.d.ts +14 -0
  66. package/dist/nexus/refresh-child.d.ts.map +1 -0
  67. package/dist/nexus/refresh-child.js +24 -0
  68. package/dist/nexus/refresh-child.js.map +1 -0
  69. package/dist/orchestrate/spawn-ops.d.ts +8 -1
  70. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  71. package/dist/orchestrate/spawn-ops.js +42 -12
  72. package/dist/orchestrate/spawn-ops.js.map +1 -1
  73. package/dist/orchestration/spawn.d.ts +5 -0
  74. package/dist/orchestration/spawn.d.ts.map +1 -1
  75. package/dist/orchestration/spawn.js +4 -1
  76. package/dist/orchestration/spawn.js.map +1 -1
  77. package/dist/release/channel.d.ts.map +1 -1
  78. package/dist/release/channel.js +10 -0
  79. package/dist/release/channel.js.map +1 -1
  80. package/dist/release/github-pr.d.ts.map +1 -1
  81. package/dist/release/github-pr.js +1 -0
  82. package/dist/release/github-pr.js.map +1 -1
  83. package/dist/release/plan.d.ts +8 -3
  84. package/dist/release/plan.d.ts.map +1 -1
  85. package/dist/release/plan.js +72 -15
  86. package/dist/release/plan.js.map +1 -1
  87. package/dist/release/reconcile.d.ts.map +1 -1
  88. package/dist/release/reconcile.js +6 -15
  89. package/dist/release/reconcile.js.map +1 -1
  90. package/dist/sentient/daemon-entry.d.ts +6 -0
  91. package/dist/sentient/daemon-entry.d.ts.map +1 -1
  92. package/dist/sentient/daemon-entry.js +12 -0
  93. package/dist/sentient/daemon-entry.js.map +1 -1
  94. package/dist/sentient/worktree-dispatch.d.ts +7 -0
  95. package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
  96. package/dist/sentient/worktree-dispatch.js +37 -1
  97. package/dist/sentient/worktree-dispatch.js.map +1 -1
  98. package/dist/sessions/session-end-snapshot-entry.d.ts +4 -1
  99. package/dist/sessions/session-end-snapshot-entry.d.ts.map +1 -1
  100. package/dist/sessions/session-end-snapshot-entry.js +9 -1
  101. package/dist/sessions/session-end-snapshot-entry.js.map +1 -1
  102. package/dist/spawn/agent-identity.d.ts.map +1 -1
  103. package/dist/spawn/agent-identity.js +96 -1
  104. package/dist/spawn/agent-identity.js.map +1 -1
  105. package/dist/spawn/branch-lock.d.ts +27 -8
  106. package/dist/spawn/branch-lock.d.ts.map +1 -1
  107. package/dist/spawn/branch-lock.js +57 -73
  108. package/dist/spawn/branch-lock.js.map +1 -1
  109. package/dist/store/agent-resolver.d.ts +6 -0
  110. package/dist/store/agent-resolver.d.ts.map +1 -1
  111. package/dist/store/agent-resolver.js +2 -0
  112. package/dist/store/agent-resolver.js.map +1 -1
  113. package/dist/store/dual-scope-db.d.ts +20 -0
  114. package/dist/store/dual-scope-db.d.ts.map +1 -1
  115. package/dist/store/dual-scope-db.js +81 -8
  116. package/dist/store/dual-scope-db.js.map +1 -1
  117. package/dist/store/exodus/bare-family.d.ts.map +1 -1
  118. package/dist/store/exodus/bare-family.js +4 -1
  119. package/dist/store/exodus/bare-family.js.map +1 -1
  120. package/dist/store/exodus/migrate.d.ts.map +1 -1
  121. package/dist/store/exodus/migrate.js +2 -0
  122. package/dist/store/exodus/migrate.js.map +1 -1
  123. package/dist/store/exodus/on-open.d.ts.map +1 -1
  124. package/dist/store/exodus/on-open.js +16 -4
  125. package/dist/store/exodus/on-open.js.map +1 -1
  126. package/dist/store/exodus/reconcile.d.ts.map +1 -1
  127. package/dist/store/exodus/reconcile.js +2 -0
  128. package/dist/store/exodus/reconcile.js.map +1 -1
  129. package/dist/store/portable-bundle-import.d.ts +17 -0
  130. package/dist/store/portable-bundle-import.d.ts.map +1 -1
  131. package/dist/store/portable-bundle-import.js +46 -31
  132. package/dist/store/portable-bundle-import.js.map +1 -1
  133. package/dist/store/portable-bundle-scan.d.ts.map +1 -1
  134. package/dist/store/portable-bundle-scan.js +5 -0
  135. package/dist/store/portable-bundle-scan.js.map +1 -1
  136. package/dist/store/row-identity-registry.d.ts +4 -3
  137. package/dist/store/row-identity-registry.d.ts.map +1 -1
  138. package/dist/store/row-identity-registry.js +37 -35
  139. package/dist/store/row-identity-registry.js.map +1 -1
  140. package/dist/store/row-identity.d.ts.map +1 -1
  141. package/dist/store/row-identity.js +6 -1
  142. package/dist/store/row-identity.js.map +1 -1
  143. package/dist/store/schema/cleo-global/agent-registry.d.ts +16 -0
  144. package/dist/store/schema/cleo-global/agent-registry.d.ts.map +1 -1
  145. package/dist/store/schema/cleo-global/agent-registry.js +4 -1
  146. package/dist/store/schema/cleo-global/agent-registry.js.map +1 -1
  147. package/dist/store/schema/cleo-project/provenance-orphans.d.ts +126 -1
  148. package/dist/store/schema/cleo-project/provenance-orphans.d.ts.map +1 -1
  149. package/dist/store/schema/cleo-project/provenance-orphans.js +41 -1
  150. package/dist/store/schema/cleo-project/provenance-orphans.js.map +1 -1
  151. package/dist/store/sqlite-data-accessor.d.ts +40 -0
  152. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  153. package/dist/store/sqlite-data-accessor.js +122 -0
  154. package/dist/store/sqlite-data-accessor.js.map +1 -1
  155. package/dist/store/sync/activity.d.ts +64 -0
  156. package/dist/store/sync/activity.d.ts.map +1 -0
  157. package/dist/store/sync/activity.js +220 -0
  158. package/dist/store/sync/activity.js.map +1 -0
  159. package/dist/store/sync/apply/applier.d.ts.map +1 -1
  160. package/dist/store/sync/apply/applier.js +58 -2
  161. package/dist/store/sync/apply/applier.js.map +1 -1
  162. package/dist/store/sync/apply/write-api.d.ts.map +1 -1
  163. package/dist/store/sync/apply/write-api.js +45 -3
  164. package/dist/store/sync/apply/write-api.js.map +1 -1
  165. package/dist/store/sync/capture.d.ts +6 -4
  166. package/dist/store/sync/capture.d.ts.map +1 -1
  167. package/dist/store/sync/capture.js +16 -4
  168. package/dist/store/sync/capture.js.map +1 -1
  169. package/dist/store/sync/collision-settle.d.ts +120 -0
  170. package/dist/store/sync/collision-settle.d.ts.map +1 -1
  171. package/dist/store/sync/collision-settle.js +342 -5
  172. package/dist/store/sync/collision-settle.js.map +1 -1
  173. package/dist/store/sync/flags.d.ts +34 -0
  174. package/dist/store/sync/flags.d.ts.map +1 -1
  175. package/dist/store/sync/flags.js +46 -0
  176. package/dist/store/sync/flags.js.map +1 -1
  177. package/dist/store/sync/genesis.d.ts +8 -0
  178. package/dist/store/sync/genesis.d.ts.map +1 -1
  179. package/dist/store/sync/genesis.js +18 -5
  180. package/dist/store/sync/genesis.js.map +1 -1
  181. package/dist/store/sync/push.d.ts +4 -2
  182. package/dist/store/sync/push.d.ts.map +1 -1
  183. package/dist/store/sync/push.js +10 -1
  184. package/dist/store/sync/push.js.map +1 -1
  185. package/dist/store/sync/rebind.d.ts +20 -0
  186. package/dist/store/sync/rebind.d.ts.map +1 -1
  187. package/dist/store/sync/rebind.js +86 -4
  188. package/dist/store/sync/rebind.js.map +1 -1
  189. package/dist/store/sync/reconcile-copy.d.ts +152 -0
  190. package/dist/store/sync/reconcile-copy.d.ts.map +1 -0
  191. package/dist/store/sync/reconcile-copy.js +428 -0
  192. package/dist/store/sync/reconcile-copy.js.map +1 -0
  193. package/dist/store/sync/reconcile.d.ts +80 -0
  194. package/dist/store/sync/reconcile.d.ts.map +1 -1
  195. package/dist/store/sync/reconcile.js +97 -5
  196. package/dist/store/sync/reconcile.js.map +1 -1
  197. package/dist/store/sync/repair.d.ts +11 -0
  198. package/dist/store/sync/repair.d.ts.map +1 -1
  199. package/dist/store/sync/repair.js +14 -3
  200. package/dist/store/sync/repair.js.map +1 -1
  201. package/dist/store/sync/replica.d.ts +74 -1
  202. package/dist/store/sync/replica.d.ts.map +1 -1
  203. package/dist/store/sync/replica.js +104 -6
  204. package/dist/store/sync/replica.js.map +1 -1
  205. package/dist/store/sync/row-meta.d.ts +4 -2
  206. package/dist/store/sync/row-meta.d.ts.map +1 -1
  207. package/dist/store/sync/row-meta.js +6 -4
  208. package/dist/store/sync/row-meta.js.map +1 -1
  209. package/dist/store/sync/sealer.d.ts +2 -0
  210. package/dist/store/sync/sealer.d.ts.map +1 -1
  211. package/dist/store/sync/sealer.js +33 -4
  212. package/dist/store/sync/sealer.js.map +1 -1
  213. package/dist/store/sync/structural.d.ts.map +1 -1
  214. package/dist/store/sync/structural.js +3 -1
  215. package/dist/store/sync/structural.js.map +1 -1
  216. package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
  217. package/dist/store/sync/timestamp-canon.js +2 -0
  218. package/dist/store/sync/timestamp-canon.js.map +1 -1
  219. package/dist/store/table-classification.d.ts +29 -0
  220. package/dist/store/table-classification.d.ts.map +1 -1
  221. package/dist/store/table-classification.js +85 -19
  222. package/dist/store/table-classification.js.map +1 -1
  223. package/dist/store/twin-collapse.d.ts.map +1 -1
  224. package/dist/store/twin-collapse.js +28 -10
  225. package/dist/store/twin-collapse.js.map +1 -1
  226. package/dist/store/vault-manifest.d.ts +5 -0
  227. package/dist/store/vault-manifest.d.ts.map +1 -1
  228. package/dist/store/vault-manifest.js +14 -1
  229. package/dist/store/vault-manifest.js.map +1 -1
  230. package/dist/tasks/affected-packages.d.ts +15 -5
  231. package/dist/tasks/affected-packages.d.ts.map +1 -1
  232. package/dist/tasks/affected-packages.js +20 -8
  233. package/dist/tasks/affected-packages.js.map +1 -1
  234. package/dist/tasks/done-record.d.ts +14 -4
  235. package/dist/tasks/done-record.d.ts.map +1 -1
  236. package/dist/tasks/done-record.js +21 -9
  237. package/dist/tasks/done-record.js.map +1 -1
  238. package/dist/tasks/evidence.d.ts.map +1 -1
  239. package/dist/tasks/evidence.js +45 -4
  240. package/dist/tasks/evidence.js.map +1 -1
  241. package/dist/tasks/gate-runner.d.ts +5 -4
  242. package/dist/tasks/gate-runner.d.ts.map +1 -1
  243. package/dist/tasks/gate-runner.js +18 -11
  244. package/dist/tasks/gate-runner.js.map +1 -1
  245. package/dist/tasks/tool-cache.d.ts +27 -1
  246. package/dist/tasks/tool-cache.d.ts.map +1 -1
  247. package/dist/tasks/tool-cache.js +110 -6
  248. package/dist/tasks/tool-cache.js.map +1 -1
  249. package/dist/tools/fs.d.ts +3 -1
  250. package/dist/tools/fs.d.ts.map +1 -1
  251. package/dist/tools/fs.js +35 -2
  252. package/dist/tools/fs.js.map +1 -1
  253. package/dist/validation/engine-ops.d.ts +10 -3
  254. package/dist/validation/engine-ops.d.ts.map +1 -1
  255. package/dist/validation/engine-ops.js +17 -8
  256. package/dist/validation/engine-ops.js.map +1 -1
  257. package/migrations/drizzle-cleo-global/20261010180000_t12915-agent-registry-uids/migration.sql +18 -0
  258. package/migrations/drizzle-cleo-project/20261010170000_t12896b-brain-task-observations/migration.sql +37 -0
  259. package/migrations/sync-journal/20261009150000_t13335-capture-pin/migration.sql +13 -0
  260. package/package.json +18 -13
  261. package/schemas/config.schema.json +12 -0
  262. package/schemas/project-context.schema.json +5 -0
  263. package/templates/CLEO-INJECTION.md +2 -2
  264. package/templates/CLEO-REFERENCE.md +6 -4
  265. package/templates/git-hooks/pre-push +29 -1
  266. package/templates/workflows/release-prepare.yml.tmpl +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"nexus-vault.d.ts","sourceRoot":"","sources":["../../src/cloud/nexus-vault.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nexus-vault.d.ts","sourceRoot":"","sources":["../../src/cloud/nexus-vault.ts"],"names":[],"mappings":"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"}
@@ -52,12 +52,12 @@
52
52
  * @epic T12322
53
53
  */
54
54
  import { execFileSync } from 'node:child_process';
55
- import { createHash } from 'node:crypto';
55
+ import { createHash, randomUUID } from 'node:crypto';
56
56
  import fs from 'node:fs';
57
57
  import os from 'node:os';
58
58
  import path from 'node:path';
59
59
  import { ListCheckpointsResult, ListHomeReplicasResult, ListLeasesResult, ReplayPin, } from '@cleocode/contracts/cloud';
60
- import { nexusCloudDevicePageSchema, nexusCloudStatusSchema, } from '@cleocode/contracts/nexus-cloud.js';
60
+ import { NEXUS_CLOUD_MAX_PAGES, NEXUS_PAGE_LIMIT_MAX, nexusCloudDevicePageSchema, nexusCloudReplicaPageSchema, nexusCloudStatusSchema, } from '@cleocode/contracts/nexus-cloud.js';
61
61
  import { NEXUS_VAULT_LEASE_ROLE, nexusLeaseAcquireSchema, nexusStreamHeadSchema, } from '@cleocode/contracts/nexus-vault.js';
62
62
  import { SYNC_SCHEMA_VERSION } from '@cleocode/contracts/sync-schema.js';
63
63
  import { readDeclaredProjectIdentity } from '@cleocode/paths';
@@ -66,16 +66,17 @@ import { z } from 'zod';
66
66
  import { getCleoHome, resolveOrCwd } from '../paths.js';
67
67
  import { withLock } from '../store/lock.js';
68
68
  import { exportPortableBundle, globalHomeRules } from '../store/portable-bundle.js';
69
- import { importPortableBundle } from '../store/portable-bundle-import.js';
69
+ import { extractPortableBundle, importPortableBundle } from '../store/portable-bundle-import.js';
70
70
  import { integrityCheck, PROJECT_SECTION_RULES, scanSection, sha256File, } from '../store/portable-bundle-scan.js';
71
71
  import { writeRestoreMarker } from '../store/restore-marker.js';
72
72
  import { FIRST_OPEN_LOCK_SUFFIX } from '../store/sqlite.js';
73
- import { isSyncFlagOn, UNRELEASED_FLAGS } from '../store/sync/flags.js';
73
+ import { isSyncFlagOn, setSyncFlag, UNRELEASED_FLAGS } from '../store/sync/flags.js';
74
74
  import { completeGenesis, cutGenesisWithSnapshot, GenesisRacedError, GenesisTakenOverError, genesisCutOf, genesisPending, joinStream, readGenesisCut, } from '../store/sync/genesis.js';
75
- import { pullStream, readStreamCursor } from '../store/sync/pull.js';
75
+ import { pullStream, readStreamCursor, } from '../store/sync/pull.js';
76
76
  import { pushStream } from '../store/sync/push.js';
77
+ import { MergeKeyCollisionError, mergeKeyCollisions, reconcileCopy, } from '../store/sync/reconcile-copy.js';
77
78
  import { replayPinOf } from '../store/sync/replay-pin.js';
78
- import { activeReplica, fileIdentity, readActiveReplicaId, rebindAfterVaultRestore, } from '../store/sync/replica.js';
79
+ import { activeReplica, fileIdentity, readActiveReplicaId, rebindAfterVaultRestore, reconcileDue, } from '../store/sync/replica.js';
79
80
  import { ReplicaRegistry } from '../store/sync/replica-registry.js';
80
81
  import { sealPending } from '../store/sync/sealer.js';
81
82
  import { firstBadTxnSignature, signTxn } from '../store/sync/txn-signing.js';
@@ -92,7 +93,7 @@ import { nexusQueryPath } from './nexus-cloud.js';
92
93
  import { nexusApiErrorToAccountError } from './nexus-enrol.js';
93
94
  import { readNexusProjectLink } from './nexus-link.js';
94
95
  import { connectNexusVault, nexusHomeDataKey, nexusProjectDataKey, unlockNexusAccountKey, } from './nexus-vault-keys.js';
95
- import { completeServerRebind } from './replica-rebind.js';
96
+ import { completeOwedRebinds } from './replica-rebind.js';
96
97
  import { homeStream, projectStream } from './streams.js';
97
98
  /** Default lease length of a push: long enough for a large upload, short enough to hand off. */
98
99
  export const NEXUS_VAULT_LEASE_TTL_SECONDS = 900;
@@ -363,7 +364,7 @@ async function replaySegments(journal, cursor, signers) {
363
364
  * What works on a stream the change journal writes (its head snapshot is
364
365
  * checkpoint/v3), for every message that would otherwise say "push" (T13034).
365
366
  */
366
- const JOURNAL_STREAM_REMEDY = 'on this stream local changes travel through the change journal (`sync.push`), not vault snapshots (vault snapshots of journal streams are tracked in T12999); `cleo cloud pull`, `cleo cloud restore` and `cleo cloud verify` still work';
367
+ const JOURNAL_STREAM_REMEDY = 'on this stream local changes travel through the change journal (`sync.push`), not vault snapshots (vault snapshots of journal streams are tracked in T12999); a store that has not joined it joins with `cleo sync enable push`, which keeps its own rows and merges them into the stream (T13466); `cleo cloud pull`, `cleo cloud restore` and `cleo cloud verify` still work';
367
368
  /** Whether a snapshot is checkpoint/v3: the change journal writes its stream (T13034). */
368
369
  function isJournalSnapshot(cp) {
369
370
  return cp !== null && cp !== undefined && manifestVersion(cp.manifest) === 3;
@@ -1192,6 +1193,109 @@ async function enableSyncPushImpl(opts = {}) {
1192
1193
  // genesis. A store that holds exactly that journal checkpoint JOINS it
1193
1194
  // (T13312); any other store restores it first.
1194
1195
  if (parent !== null && isJournalSnapshot(parent)) {
1196
+ // A store that never synced with this stream keeps its own rows (T13466):
1197
+ // it joins, then reconciles against the checkpoint pulled to head, which
1198
+ // adopts the stream's rows and emits its own as its first transactions.
1199
+ if (synced === null) {
1200
+ let safetyBackup = '';
1201
+ let cut = 0;
1202
+ // Assigned inside the callback: typed so the checks below are not narrowed to null.
1203
+ let merged = null;
1204
+ // `warnings` copied conn.warnings at the start; the scratch pull adds more (T13551).
1205
+ const connWarned = conn.warnings.length;
1206
+ await withMergedScratch({ conn, key, t }, async (scratch, mergedCursor, baseline) => {
1207
+ // A stream row whose key this store's own row holds cannot be
1208
+ // settled yet (T13504): refuse before anything changes.
1209
+ const collisions = mergeKeyCollisions(db, scratch, tableScopeOf(t));
1210
+ if (collisions.length > 0) {
1211
+ throw vaultError('E_NEXUS_SYNC_REFUSED', new MergeKeyCollisionError(collisions).message, "nothing was joined or changed; give this store's row a key the stream does not hold, then run `cleo sync enable push` again");
1212
+ }
1213
+ // The merge decides field by field which side's value stays: keep the
1214
+ // store as it is now first, so a value it replaces can be recovered.
1215
+ safetyBackup = await exportSafetyBundle(t, 'pre-merge', 'cloud-sync-pre-merge');
1216
+ const joined = joinStream(db, {
1217
+ scope: tableScopeOf(t),
1218
+ stream: t.streamId,
1219
+ cursor: cursorFromCheckpoint(parent),
1220
+ merge: true,
1221
+ ...(opts.allowUnreleased ? { allowUnreleased: true } : {}),
1222
+ });
1223
+ if (joined.refused !== null) {
1224
+ throw vaultError('E_NEXUS_SYNC_REFUSED', `the join was refused: ${joined.refused}`);
1225
+ }
1226
+ cut = joined.cut ?? 0;
1227
+ // A failure leaves the reconcile due and push paused; `cleo cloud sync` finishes it.
1228
+ try {
1229
+ merged = reconcileCopy(db, scratch, {
1230
+ scope: tableScopeOf(t),
1231
+ stream: t.streamId,
1232
+ mergedCursor,
1233
+ baseline,
1234
+ });
1235
+ }
1236
+ catch (err) {
1237
+ warnings.push({
1238
+ code: 'W_SYNC_MERGE_PENDING',
1239
+ message: `joined ${t.streamId}, but merging this store's rows did not finish (${err instanceof Error ? err.message : String(err)}); push stays paused until \`cleo cloud sync\` finishes it`,
1240
+ });
1241
+ }
1242
+ });
1243
+ warnings.push(...conn.warnings.slice(connWarned));
1244
+ if (merged !== null && merged.overwritten.length > 0) {
1245
+ warnings.push({
1246
+ code: 'W_SYNC_MERGE_OVERWROTE',
1247
+ message: `${merged.overwritten.length} field(s) of this store's own rows lost to the stream's value (older here, or written on the stream after its checkpoint): ${merged.overwritten
1248
+ .map((f) => `${f.table} ${f.uid} ${f.column}`)
1249
+ .join(', ')}; the store as it was is kept at ${safetyBackup}`,
1250
+ });
1251
+ }
1252
+ if (merged !== null && merged.replaced.length > 0) {
1253
+ warnings.push({
1254
+ code: 'W_SYNC_MERGE_REPLACED',
1255
+ message: `${merged.replaced.length} field(s) of the stream took this store's newer value on every replica: ${merged.replaced
1256
+ .map((f) => `${f.table} ${f.uid} ${f.column}`)
1257
+ .join(', ')}`,
1258
+ });
1259
+ }
1260
+ if (merged !== null && merged.deleted.length > 0) {
1261
+ warnings.push({
1262
+ code: 'W_SYNC_MERGE_DELETED',
1263
+ message: `${merged.deleted.length} of this store's own row(s) were removed by a newer delete on the stream: ${merged.deleted
1264
+ .map((r) => `${r.table} ${r.uid}`)
1265
+ .join(', ')}; the store as it was is kept at ${safetyBackup}`,
1266
+ });
1267
+ }
1268
+ if (merged !== null && merged.unresolved > 0) {
1269
+ warnings.push({
1270
+ code: 'W_SYNC_MERGE_UNRESOLVED',
1271
+ message: `${merged.unresolved} stream row(s) were not placed here (a key this store's own row already holds, or a reference that does not resolve); this store kept its own`,
1272
+ });
1273
+ }
1274
+ warnings.push(...conn.state.drainWarnings());
1275
+ return {
1276
+ ...base,
1277
+ status: 'joined',
1278
+ cut,
1279
+ sealed: 0,
1280
+ folded: 0,
1281
+ baselined: {},
1282
+ snapshot: snapshotOf(parent, await deviceNames(conn)),
1283
+ deltaSegmentSeq: null,
1284
+ replicaSeqFloor: null,
1285
+ merged: merged === null
1286
+ ? null
1287
+ : {
1288
+ adopted: merged.adoptedInserts,
1289
+ emitted: merged.frame.inserts,
1290
+ unresolved: merged.unresolved,
1291
+ overwritten: [...merged.overwritten],
1292
+ replaced: [...merged.replaced],
1293
+ deleted: [...merged.deleted],
1294
+ safetyBackup,
1295
+ },
1296
+ warnings,
1297
+ };
1298
+ }
1195
1299
  if (synced?.lastCheckpointId !== parent.checkpointId) {
1196
1300
  throw vaultError('E_NEXUS_SYNC_STREAM_JOURNALED', `${t.streamId} already carries the change journal: its head snapshot ${parent.checkpointId} is a journal checkpoint this store has not restored`, 'nothing was cut; restore the journal checkpoint (`cleo cloud restore`), then join the stream with `cleo sync enable push`');
1197
1301
  }
@@ -1400,7 +1504,8 @@ async function openStreamSession(opts) {
1400
1504
  }
1401
1505
  /**
1402
1506
  * Complete a rebind's server half the store still owes (T13278): a crash, or
1403
- * a refused call, after the store rebound at a pull's head. Until it is done
1507
+ * a refused call, after the store rebound at a pull's head, or the retire a
1508
+ * rollback or move rebind at open recorded (T13337). Until it is done
1404
1509
  * the stream does not know the store's replica, so nothing is pushed or
1405
1510
  * pulled first. Returns the session with the replica the store is bound to.
1406
1511
  */
@@ -1412,9 +1517,180 @@ async function withCompletedRebind(session) {
1412
1517
  const db = getDualScopeNativeDb(t.scope === 'global'
1413
1518
  ? await openDualScopeDbAtPath('global', t.dbPath)
1414
1519
  : await openDualScopeDbAtPath('project', t.dbPath));
1415
- const done = await completeServerRebind(conn, t, db);
1520
+ const done = await completeOwedRebinds(conn, t, db);
1521
+ // §1.5 N7 (T13335): a store the open pass rebound reconciles before it
1522
+ // pushes or pulls; push stays refused until this commits.
1523
+ await reconcileOwedCopy(session, db);
1416
1524
  return done === null ? session : { ...session, t: { ...t, replicaId: done.to } };
1417
1525
  }
1526
+ /** Pages the stream's verified segments for {@link pullStream}. */
1527
+ function journalPuller(journal, key) {
1528
+ return async (cursor) => {
1529
+ const page = await journal.pull({
1530
+ after: cursor.after,
1531
+ knowsAllReplicas: cursor.knowsAllReplicas,
1532
+ replicas: { ...cursor.replicas },
1533
+ }, key.signers);
1534
+ return {
1535
+ segments: page.segments.map((s) => ({
1536
+ seq: s.seq,
1537
+ replicaId: s.replicaId,
1538
+ replicaSeq: s.replicaSeq,
1539
+ deviceId: s.deviceId,
1540
+ plaintext: s.plaintext,
1541
+ schemaVersion: s.meta.schemaVersion,
1542
+ })),
1543
+ cursor: page.cursor,
1544
+ head: page.head,
1545
+ };
1546
+ };
1547
+ }
1548
+ /** Checks each pulled transaction's device signature for {@link pullStream}. */
1549
+ function txnVerifier(key, streamId) {
1550
+ return (deviceId, txns) => {
1551
+ const keys = signerKeys(key.signers, deviceId);
1552
+ const first = keys[0];
1553
+ if (first === undefined)
1554
+ return 0;
1555
+ for (const k of keys) {
1556
+ if (firstBadTxnSignature(k.publicKey, streamId, txns) === null)
1557
+ return null;
1558
+ }
1559
+ return firstBadTxnSignature(first.publicKey, streamId, txns);
1560
+ };
1561
+ }
1562
+ /**
1563
+ * The copy reconcile a store the open pass rebound owes (§1.5 N7, T13335):
1564
+ * restore the stream's latest verified journal checkpoint into a scratch
1565
+ * store (`sync: 'off'`, a dedicated handle), pull the stream to head there
1566
+ * with the merge engine, and reconcile the store against that merged state
1567
+ * ({@link reconcileCopy}). The scratch is removed whatever happens. Nothing
1568
+ * is done when no reconcile is due.
1569
+ *
1570
+ * @throws {NexusAccountError} `E_NEXUS_SYNC_REFUSED` when the stream has no
1571
+ * verified journal checkpoint, or the scratch pull stopped short; the
1572
+ * reconcile stays due and push stays refused.
1573
+ */
1574
+ async function reconcileOwedCopy(session, db) {
1575
+ if (reconcileDue(db) === null)
1576
+ return null;
1577
+ const { t } = session;
1578
+ return withMergedScratch(session, (scratch, mergedCursor, baseline) => {
1579
+ try {
1580
+ return reconcileCopy(db, scratch, {
1581
+ scope: tableScopeOf(t),
1582
+ stream: t.streamId,
1583
+ mergedCursor,
1584
+ baseline,
1585
+ });
1586
+ }
1587
+ catch (err) {
1588
+ if (!(err instanceof MergeKeyCollisionError))
1589
+ throw err;
1590
+ throw vaultError('E_NEXUS_SYNC_REFUSED', err.message, "nothing was merged or pushed, and push stays paused; give this store's row a key the stream does not hold, then run `cleo cloud sync`");
1591
+ }
1592
+ });
1593
+ }
1594
+ /**
1595
+ * Run `fn` against the stream's merged state: its latest verified journal
1596
+ * checkpoint restored into a scratch store (`sync: 'off'`, a dedicated
1597
+ * handle) and pulled to head there with the merge engine (§1.5 N7). The
1598
+ * scratch is removed whatever happens.
1599
+ *
1600
+ * @param session - The stream session.
1601
+ * @param fn - Gets the scratch (read it only) and its pull position at head.
1602
+ * @returns What `fn` returns.
1603
+ * @throws {NexusAccountError} `E_NEXUS_SYNC_REFUSED` when the stream has no
1604
+ * verified journal checkpoint, or the scratch pull stopped short.
1605
+ */
1606
+ async function withMergedScratch(session, fn) {
1607
+ const { conn, key, t } = session;
1608
+ const journal = journalFor(conn, t);
1609
+ const journalCheckpoints = trustedCheckpoints(journal, await listCheckpoints(conn, t.streamId), key.signers, null)
1610
+ .filter(isJournalSnapshot)
1611
+ .sort((a, b) => b.coversSeq - a.coversSeq);
1612
+ const latest = journalCheckpoints[0];
1613
+ // The merge join's baseline is the cut (the oldest journal checkpoint, T13508 LOW): a
1614
+ // later checkpoint would hide stream edits made between the cut and it.
1615
+ const cutCheckpoint = journalCheckpoints[journalCheckpoints.length - 1];
1616
+ if (latest === undefined) {
1617
+ throw vaultError('E_NEXUS_SYNC_REFUSED', `${t.streamId} has no verified journal checkpoint to reconcile this store against`, 'nothing was pushed or pulled; restore the stream (`cleo cloud restore`) instead');
1618
+ }
1619
+ const restored = await journal.restoreCheckpoint(latest.checkpointId, key.signers);
1620
+ const work = tempDir('cleo-reconcile-');
1621
+ try {
1622
+ const bundlePath = path.join(work, 'checkpoint.cleobundle.tar.gz');
1623
+ fs.writeFileSync(bundlePath, restored.bundle);
1624
+ const staging = path.join(work, 'staging');
1625
+ fs.mkdirSync(staging);
1626
+ const { extractDir, manifest } = await extractPortableBundle(bundlePath, staging);
1627
+ const section = t.scope === 'global' ? manifest.global?.home : manifest.projects[0];
1628
+ const entry = section?.databases.find((d) => d.role === 'primary' && d.relPath === 'cleo.db');
1629
+ if (!entry) {
1630
+ throw vaultError('E_NEXUS_VAULT_VERIFY_FAILED', 'the checkpoint holds no primary cleo.db');
1631
+ }
1632
+ const scratchPath = path.join(work, 'scratch', 'cleo.db');
1633
+ fs.mkdirSync(path.dirname(scratchPath));
1634
+ fs.copyFileSync(path.join(extractDir, entry.bundlePath), scratchPath);
1635
+ // The checkpoint as restored, never pulled: a merge join's baseline (T13507).
1636
+ const baselinePath = path.join(work, 'baseline', 'cleo.db');
1637
+ fs.mkdirSync(path.dirname(baselinePath));
1638
+ if (cutCheckpoint === undefined || cutCheckpoint.checkpointId === latest.checkpointId) {
1639
+ fs.copyFileSync(path.join(extractDir, entry.bundlePath), baselinePath);
1640
+ }
1641
+ else {
1642
+ const cutRestored = await journal.restoreCheckpoint(cutCheckpoint.checkpointId, key.signers);
1643
+ const cutBundle = path.join(work, 'cut.cleobundle.tar.gz');
1644
+ fs.writeFileSync(cutBundle, cutRestored.bundle);
1645
+ const cutStaging = path.join(work, 'cut-staging');
1646
+ fs.mkdirSync(cutStaging);
1647
+ const cut = await extractPortableBundle(cutBundle, cutStaging);
1648
+ const cutSection = t.scope === 'global' ? cut.manifest.global?.home : cut.manifest.projects[0];
1649
+ const cutEntry = cutSection?.databases.find((d) => d.role === 'primary' && d.relPath === 'cleo.db');
1650
+ if (!cutEntry) {
1651
+ throw vaultError('E_NEXUS_VAULT_VERIFY_FAILED', 'the cut checkpoint holds no primary cleo.db');
1652
+ }
1653
+ fs.copyFileSync(path.join(cut.extractDir, cutEntry.bundlePath), baselinePath);
1654
+ }
1655
+ const { openDualScopeDbAtPath, getDualScopeNativeDb } = await import('../store/dual-scope-db.js');
1656
+ // The reconcile scratch is never bound and runs no open pass (§1.5).
1657
+ const openScratch = (p) => t.scope === 'global'
1658
+ ? openDualScopeDbAtPath('global', p, undefined, { dedicated: true, syncMode: 'off' })
1659
+ : openDualScopeDbAtPath('project', p, undefined, { dedicated: true, syncMode: 'off' });
1660
+ const handle = await openScratch(scratchPath);
1661
+ const baselineHandle = await openScratch(baselinePath);
1662
+ try {
1663
+ const scratch = getDualScopeNativeDb(handle);
1664
+ // The scratch replays the stream; it never seals or pushes. The store
1665
+ // it reconciles already has its sync flags, so the release gate holds.
1666
+ setSyncFlag(scratch, 'sync.pull', true, { allowUnreleased: true });
1667
+ // Soft and project-aware like the live pull (T13551): a failed listing confirms none, never aborts.
1668
+ const serverRetirements = await confirmableRetirements(conn, t.scope, t.projectId);
1669
+ const merged = await pullStream(scratch, {
1670
+ scope: tableScopeOf(t),
1671
+ stream: t.streamId,
1672
+ // No own echoes: the scratch is no replica.
1673
+ replica: randomUUID(),
1674
+ initialCursor: cursorFromCheckpoint(latest),
1675
+ pull: journalPuller(journal, key),
1676
+ verify: txnVerifier(key, t.streamId),
1677
+ seal: () => { },
1678
+ ...(serverRetirements !== undefined ? { serverRetirements } : {}),
1679
+ });
1680
+ if (merged.refused !== null) {
1681
+ throw vaultError('E_NEXUS_SYNC_REFUSED', `the merged state this store reconciles against stopped short of ${t.streamId}'s head: ${merged.refused}`, 'nothing was pushed or pulled; run `cleo cloud sync` again');
1682
+ }
1683
+ return await fn(scratch, readStreamCursor(scratch, t.streamId) ?? cursorFromCheckpoint(latest), getDualScopeNativeDb(baselineHandle));
1684
+ }
1685
+ finally {
1686
+ handle.close();
1687
+ baselineHandle.close();
1688
+ }
1689
+ }
1690
+ finally {
1691
+ fs.rmSync(work, { recursive: true, force: true });
1692
+ }
1693
+ }
1418
1694
  async function pushSyncStreamImpl(opts = {}) {
1419
1695
  return pushWithSession(await openStreamSession(opts), opts);
1420
1696
  }
@@ -1473,23 +1749,64 @@ async function pullSyncStreamImpl(opts = {}) {
1473
1749
  /**
1474
1750
  * The server's retirements a receiver of `scope`'s stream confirms a `retire`
1475
1751
  * by (T13366). The home stream: `retiredAt` and `successor` on
1476
- * `GET /v1/account/home/replicas` (none from a server without the listing).
1477
- * A project stream: undefined, as the server has no read of them yet, so its
1478
- * receivers keep every retire unconfirmed and record its late conflicts.
1752
+ * `GET /v1/account/home/replicas`. A project stream: the same fields on E15
1753
+ * `GET /v1/projects/:projectId/replicas`, every page (T13391). A server
1754
+ * without the listing (404), or one older than retirement, confirms none.
1479
1755
  *
1480
1756
  * @param conn - The vault connection.
1481
1757
  * @param scope - The store's scope.
1482
- * @returns The retirements, or undefined for a project stream.
1758
+ * @param projectId - The server project of a project stream.
1759
+ * @returns The retirements, or undefined for a project stream with no server project.
1483
1760
  */
1484
- export async function serverRetirementsFor(conn, scope) {
1485
- if (scope !== 'global')
1486
- return undefined;
1487
- const list = await conn.find('/v1/account/home/replicas', ListHomeReplicasResult);
1488
- if (list === null)
1489
- return [];
1490
- return list.replicas.flatMap((r) => r.retiredAt
1761
+ export async function serverRetirementsFor(conn, scope, projectId = null) {
1762
+ const retired = (rows) => rows.flatMap((r) => r.retiredAt
1491
1763
  ? [{ replicaId: r.replicaId, successor: r.successor ?? null, retiredAt: r.retiredAt }]
1492
1764
  : []);
1765
+ if (scope === 'global') {
1766
+ const list = await conn.find('/v1/account/home/replicas', ListHomeReplicasResult);
1767
+ return list === null ? [] : retired(list.replicas);
1768
+ }
1769
+ if (projectId === null)
1770
+ return undefined;
1771
+ const out = [];
1772
+ let cursor = null;
1773
+ for (let page = 0; page < NEXUS_CLOUD_MAX_PAGES; page++) {
1774
+ const res = await conn.find(nexusQueryPath(`/v1/projects/${encodeURIComponent(projectId)}/replicas`, {
1775
+ limit: NEXUS_PAGE_LIMIT_MAX,
1776
+ cursor: cursor ?? undefined,
1777
+ }), nexusCloudReplicaPageSchema);
1778
+ if (res === null)
1779
+ return out;
1780
+ out.push(...retired(res.replicas));
1781
+ cursor = res.nextCursor;
1782
+ if (cursor === null)
1783
+ return out;
1784
+ }
1785
+ return out;
1786
+ }
1787
+ /**
1788
+ * {@link serverRetirementsFor}, soft (T13395): a listing that fails (network,
1789
+ * 5xx, a 404) confirms nothing this round instead of failing the pull. The
1790
+ * pull still stages and applies; retires it records stay unconfirmed until a
1791
+ * later pull reads the listing. The failure is a `W_NEXUS_RETIREMENTS_UNAVAILABLE`
1792
+ * warning on the connection.
1793
+ *
1794
+ * @param conn - The vault connection (its warnings collect the failure).
1795
+ * @param scope - The store's scope.
1796
+ * @param projectId - The server project of a project stream.
1797
+ * @returns The retirements, `[]` when the listing failed, or undefined for a project stream with no server project.
1798
+ */
1799
+ export async function confirmableRetirements(conn, scope, projectId = null) {
1800
+ try {
1801
+ return await serverRetirementsFor(conn, scope, projectId);
1802
+ }
1803
+ catch (err) {
1804
+ conn.warnings.push({
1805
+ code: 'W_NEXUS_RETIREMENTS_UNAVAILABLE',
1806
+ message: `the replica listing could not be read (${err instanceof Error ? err.message : String(err)}); no replica retire is confirmed this round`,
1807
+ });
1808
+ return [];
1809
+ }
1493
1810
  }
1494
1811
  async function pullWithSession(opened) {
1495
1812
  const session = await withCompletedRebind(opened);
@@ -1553,43 +1870,15 @@ async function pullWithSession(opened) {
1553
1870
  // server-held old segments pushed and inherits the rest (never uploaded under
1554
1871
  // the new id; unpushedSegments filters by replica), its re-emits are new ids,
1555
1872
  // and a retired replica's late segment carries ids never stored before.
1556
- const serverRetirements = await serverRetirementsFor(conn, t.scope);
1873
+ const serverRetirements = await confirmableRetirements(conn, t.scope, t.projectId);
1557
1874
  const report = await pullStream(db, {
1558
1875
  scope: tableScopeOf(t),
1559
1876
  stream: t.streamId,
1560
1877
  replica: replicaId,
1561
1878
  initialCursor,
1562
1879
  pruneSeen: true,
1563
- pull: async (cursor) => {
1564
- const page = await journal.pull({
1565
- after: cursor.after,
1566
- knowsAllReplicas: cursor.knowsAllReplicas,
1567
- replicas: { ...cursor.replicas },
1568
- }, key.signers);
1569
- return {
1570
- segments: page.segments.map((s) => ({
1571
- seq: s.seq,
1572
- replicaId: s.replicaId,
1573
- replicaSeq: s.replicaSeq,
1574
- deviceId: s.deviceId,
1575
- plaintext: s.plaintext,
1576
- schemaVersion: s.meta.schemaVersion,
1577
- })),
1578
- cursor: page.cursor,
1579
- head: page.head,
1580
- };
1581
- },
1582
- verify: (deviceId, txns) => {
1583
- const keys = signerKeys(key.signers, deviceId);
1584
- const first = keys[0];
1585
- if (first === undefined)
1586
- return 0;
1587
- for (const k of keys) {
1588
- if (firstBadTxnSignature(k.publicKey, t.streamId, txns) === null)
1589
- return null;
1590
- }
1591
- return firstBadTxnSignature(first.publicKey, t.streamId, txns);
1592
- },
1880
+ pull: journalPuller(journal, key),
1881
+ verify: txnVerifier(key, t.streamId),
1593
1882
  seal: () => {
1594
1883
  sealPending(db, { scope: tableScopeOf(t), replica: replicaId });
1595
1884
  },
@@ -1601,7 +1890,7 @@ async function pullWithSession(opened) {
1601
1890
  });
1602
1891
  // Its server half at once, so this run's next push announces the retire.
1603
1892
  if (report.rebind !== null)
1604
- await completeServerRebind(conn, t, db);
1893
+ await completeOwedRebinds(conn, t, db);
1605
1894
  return report;
1606
1895
  }
1607
1896
  /** A `cleo cloud sync` stream result with nothing done yet. */
@@ -1821,12 +2110,41 @@ async function assertStoreQuiescent(t) {
1821
2110
  }
1822
2111
  /** Safety bundles kept per store under `backups/vault`; older ones are removed (T13007). */
1823
2112
  export const NEXUS_VAULT_SAFETY_BUNDLES_KEPT = 10;
1824
- /** Keep the newest {@link NEXUS_VAULT_SAFETY_BUNDLES_KEPT} safety bundles in `dir`. */
1825
- function rotateSafetyBundles(dir) {
2113
+ /** A safety bundle's file name: its kind, then its timestamp. */
2114
+ const SAFETY_BUNDLE = /^pre-(restore|merge)-(.+)\.cleobundle\.tar\.gz$/;
2115
+ /**
2116
+ * Export the store as it is now to a safety bundle under `backups/vault`,
2117
+ * keeping the newest {@link NEXUS_VAULT_SAFETY_BUNDLES_KEPT}.
2118
+ *
2119
+ * @returns The bundle's path.
2120
+ */
2121
+ async function exportSafetyBundle(t, kind, label) {
2122
+ const dir = t.scope === 'global'
2123
+ ? path.join(t.storeRoot, 'backups', 'vault')
2124
+ : path.join(t.storeRoot, '.cleo', 'backups', 'vault');
2125
+ fs.mkdirSync(dir, { recursive: true });
2126
+ const out = path.join(dir, `${kind}-${new Date().toISOString().replace(/[:.]/g, '-')}.cleobundle.tar.gz`);
2127
+ await exportPortableBundle({
2128
+ scope: t.scope === 'global' ? 'global' : 'project',
2129
+ ...(t.scope === 'project' ? { projectRoot: t.storeRoot } : {}),
2130
+ outputPath: out,
2131
+ label,
2132
+ // A local safety copy: it shares nothing (T13250).
2133
+ sharesIdentity: false,
2134
+ });
2135
+ rotateSafetyBundles(dir, kind);
2136
+ return out;
2137
+ }
2138
+ /**
2139
+ * Keep the newest {@link NEXUS_VAULT_SAFETY_BUNDLES_KEPT} safety bundles of `kind` in `dir`: each kind
2140
+ * rotates on its own, so repeated merges never evict the last pre-restore bundle.
2141
+ */
2142
+ function rotateSafetyBundles(dir, kind) {
1826
2143
  const bundles = fs
1827
2144
  .readdirSync(dir)
1828
- .filter((name) => /^pre-restore-.+\.cleobundle\.tar\.gz$/.test(name))
1829
- .sort();
2145
+ .filter((name) => SAFETY_BUNDLE.test(name) && name.startsWith(`${kind}-`))
2146
+ // Oldest first by the timestamp.
2147
+ .sort((a, b) => a.replace(SAFETY_BUNDLE, '$2').localeCompare(b.replace(SAFETY_BUNDLE, '$2')));
1830
2148
  for (const name of bundles.slice(0, Math.max(0, bundles.length - NEXUS_VAULT_SAFETY_BUNDLES_KEPT))) {
1831
2149
  fs.rmSync(path.join(dir, name), { force: true });
1832
2150
  }
@@ -2074,21 +2392,8 @@ async function restoreNexusVaultImpl(opts) {
2074
2392
  let safetyBackup = null;
2075
2393
  let safety = new Map();
2076
2394
  if (hasLocal) {
2077
- const dir = t.scope === 'global'
2078
- ? path.join(t.storeRoot, 'backups', 'vault')
2079
- : path.join(t.storeRoot, '.cleo', 'backups', 'vault');
2080
- fs.mkdirSync(dir, { recursive: true });
2081
- safetyBackup = path.join(dir, `pre-restore-${new Date().toISOString().replace(/[:.]/g, '-')}.cleobundle.tar.gz`);
2082
- await exportPortableBundle({
2083
- scope: t.scope === 'global' ? 'global' : 'project',
2084
- ...(t.scope === 'project' ? { projectRoot: t.storeRoot } : {}),
2085
- outputPath: safetyBackup,
2086
- label: 'cloud-vault-pre-restore',
2087
- // A local safety copy: it shares nothing (T13250).
2088
- sharesIdentity: false,
2089
- });
2395
+ safetyBackup = await exportSafetyBundle(t, 'pre-restore', 'cloud-vault-pre-restore');
2090
2396
  safety = await safetyInventory(safetyBackup, t);
2091
- rotateSafetyBundles(dir);
2092
2397
  }
2093
2398
  let tables = 0;
2094
2399
  // Every path the snapshot lists in its section; the rest of the inventory is removed.