@cleocode/contracts 2026.10.3 → 2026.10.5-rc.ci.672

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 (103) hide show
  1. package/dist/__tests__/cloud-replica-retirement.test.d.ts +10 -0
  2. package/dist/__tests__/cloud-replica-retirement.test.d.ts.map +1 -0
  3. package/dist/__tests__/cloud-replica-retirement.test.js +111 -0
  4. package/dist/__tests__/cloud-replica-retirement.test.js.map +1 -0
  5. package/dist/__tests__/cloud-status-checkpoint-reads.test.d.ts +10 -0
  6. package/dist/__tests__/cloud-status-checkpoint-reads.test.d.ts.map +1 -0
  7. package/dist/__tests__/cloud-status-checkpoint-reads.test.js +49 -0
  8. package/dist/__tests__/cloud-status-checkpoint-reads.test.js.map +1 -0
  9. package/dist/agent-registry.d.ts +5 -1
  10. package/dist/agent-registry.d.ts.map +1 -1
  11. package/dist/cloud/api.d.ts +156 -0
  12. package/dist/cloud/api.d.ts.map +1 -1
  13. package/dist/cloud/api.js +99 -1
  14. package/dist/cloud/api.js.map +1 -1
  15. package/dist/cloud/envelope.d.ts +3 -3
  16. package/dist/dispatch/operations-registry.js +1 -1
  17. package/dist/dispatch/operations-registry.js.map +1 -1
  18. package/dist/doctor.d.ts +79 -7
  19. package/dist/doctor.d.ts.map +1 -1
  20. package/dist/heavy-command-hook.d.ts +134 -5
  21. package/dist/heavy-command-hook.d.ts.map +1 -1
  22. package/dist/index.d.ts +10 -8
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +4 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/invariants/sync-write-invariants.d.ts.map +1 -1
  27. package/dist/invariants/sync-write-invariants.js +31 -6
  28. package/dist/invariants/sync-write-invariants.js.map +1 -1
  29. package/dist/ledger/index.d.ts +211 -0
  30. package/dist/ledger/index.d.ts.map +1 -0
  31. package/dist/ledger/index.js +122 -0
  32. package/dist/ledger/index.js.map +1 -0
  33. package/dist/nexus-account.d.ts +72 -1
  34. package/dist/nexus-account.d.ts.map +1 -1
  35. package/dist/nexus-account.js +5 -0
  36. package/dist/nexus-account.js.map +1 -1
  37. package/dist/nexus-cloud.d.ts +175 -0
  38. package/dist/nexus-cloud.d.ts.map +1 -1
  39. package/dist/nexus-cloud.js +17 -0
  40. package/dist/nexus-cloud.js.map +1 -1
  41. package/dist/nexus-first-run.d.ts +145 -0
  42. package/dist/nexus-first-run.d.ts.map +1 -0
  43. package/dist/nexus-first-run.js +54 -0
  44. package/dist/nexus-first-run.js.map +1 -0
  45. package/dist/nexus-vault.d.ts +113 -1
  46. package/dist/nexus-vault.d.ts.map +1 -1
  47. package/dist/nexus-vault.js +27 -0
  48. package/dist/nexus-vault.js.map +1 -1
  49. package/dist/operations/admin.d.ts +14 -1
  50. package/dist/operations/admin.d.ts.map +1 -1
  51. package/dist/operations/docs-lifecycle.d.ts +23 -0
  52. package/dist/operations/docs-lifecycle.d.ts.map +1 -0
  53. package/dist/operations/docs-lifecycle.js +29 -0
  54. package/dist/operations/docs-lifecycle.js.map +1 -0
  55. package/dist/operations/docs.d.ts +2 -11
  56. package/dist/operations/docs.d.ts.map +1 -1
  57. package/dist/operations/docs.js +3 -17
  58. package/dist/operations/docs.js.map +1 -1
  59. package/dist/project-context.d.ts +17 -2
  60. package/dist/project-context.d.ts.map +1 -1
  61. package/dist/release/evidence-atoms.d.ts +15 -7
  62. package/dist/release/evidence-atoms.d.ts.map +1 -1
  63. package/dist/release/evidence-atoms.js +16 -12
  64. package/dist/release/evidence-atoms.js.map +1 -1
  65. package/dist/resource-governor.d.ts +96 -0
  66. package/dist/resource-governor.d.ts.map +1 -1
  67. package/dist/resource-governor.js.map +1 -1
  68. package/dist/row-identity.d.ts +81 -0
  69. package/dist/row-identity.d.ts.map +1 -1
  70. package/dist/store-restore.d.ts +66 -0
  71. package/dist/store-restore.d.ts.map +1 -0
  72. package/dist/store-restore.js +9 -0
  73. package/dist/store-restore.js.map +1 -0
  74. package/dist/task.d.ts +14 -0
  75. package/dist/task.d.ts.map +1 -1
  76. package/dist/task.js.map +1 -1
  77. package/dist/tasks.d.ts +17 -17
  78. package/dist/workgraph.d.ts +28 -28
  79. package/package.json +6 -2
  80. package/src/__tests__/cloud-replica-retirement.test.ts +135 -0
  81. package/src/__tests__/cloud-status-checkpoint-reads.test.ts +60 -0
  82. package/src/agent-registry.ts +5 -1
  83. package/src/cloud/api.ts +125 -1
  84. package/src/dispatch/operations-registry.ts +1 -1
  85. package/src/doctor.ts +85 -7
  86. package/src/heavy-command-hook.ts +175 -5
  87. package/src/index.ts +58 -0
  88. package/src/invariants/sync-write-invariants.ts +81 -42
  89. package/src/ledger/index.ts +141 -0
  90. package/src/nexus-account.ts +82 -0
  91. package/src/nexus-cloud.ts +193 -0
  92. package/src/nexus-first-run.ts +165 -0
  93. package/src/nexus-vault.ts +124 -1
  94. package/src/operations/admin.ts +14 -1
  95. package/src/operations/conduit.ts +1 -1
  96. package/src/operations/docs-lifecycle.ts +31 -0
  97. package/src/operations/docs.ts +4 -19
  98. package/src/project-context.ts +17 -2
  99. package/src/release/evidence-atoms.ts +16 -12
  100. package/src/resource-governor.ts +100 -0
  101. package/src/row-identity.ts +98 -0
  102. package/src/store-restore.ts +70 -0
  103. package/src/task.ts +14 -0
@@ -119,6 +119,34 @@ function rulePending(task: string): SyncWriteInvariantPending {
119
119
  return { task, reason: 'the typed merge-rule registry does not define it yet (T12344 item)' };
120
120
  }
121
121
 
122
+ /** Where every implemented typed merge rule is enforced at apply (T12344). */
123
+ const MERGE_ENGINE_GATE = {
124
+ module: 'packages/core/src/store/sync/merge/engine.ts',
125
+ functionName: 'applyOp',
126
+ } as const;
127
+
128
+ /**
129
+ * A typed merge rule the merge-rule registry defines (`SYNC_MERGE_RULES`),
130
+ * enforced by the merge engine on every applied op: no longer pending.
131
+ */
132
+ function implemented(entry: SyncWriteInvariant): SyncWriteInvariant {
133
+ const { pending: _pending, ...rest } = entry;
134
+ return { ...rest, runtimeGate: MERGE_ENGINE_GATE };
135
+ }
136
+
137
+ /** A post-apply check the apply runs (T12344 PR-5): no longer pending. */
138
+ function implementedCheck(entry: SyncWriteInvariant, functionName: string): SyncWriteInvariant {
139
+ const { pending: _pending, ...rest } = entry;
140
+ return {
141
+ ...rest,
142
+ check: {
143
+ module: 'packages/core/src/store/sync/apply/post-apply.ts',
144
+ functionName,
145
+ footprint: entry.check?.footprint ?? [],
146
+ },
147
+ };
148
+ }
149
+
122
150
  function trigger(
123
151
  id: string,
124
152
  tables: readonly string[],
@@ -256,13 +284,16 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
256
284
  ),
257
285
 
258
286
  // §3.6.5: post-apply check families (T12344 items).
259
- pac(
260
- 'task.tree.shape',
261
- 'PAC-01',
262
- 'T12922',
263
- ['tasks_tasks'],
264
- ['rows with a changed parent_id or type', 'their direct children and ancestor chain'],
265
- 'saga→epic→task→subtask matrix, non-saga has a parent, depth ≤ 3, no self-parent',
287
+ implementedCheck(
288
+ pac(
289
+ 'task.tree.shape',
290
+ 'PAC-01',
291
+ 'T12922',
292
+ ['tasks_tasks'],
293
+ ['rows with a changed parent_id or type', 'their direct children and ancestor chain'],
294
+ 'saga→epic→task→subtask matrix, non-saga has a parent, depth ≤ 3, no self-parent',
295
+ ),
296
+ 'checkTaskTreeShape',
266
297
  ),
267
298
  pac(
268
299
  'task.terminal-parent.live-children',
@@ -272,13 +303,12 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
272
303
  ['rows whose status, stage or parent changed', 'their parent and siblings'],
273
304
  'no terminal parent with a live child; epic stage ≥ children; rollup re-evaluated',
274
305
  ),
275
- pac(
306
+ trigger(
276
307
  'task.dependency.graph',
277
- 'PAC-03',
278
- 'T12924',
279
308
  ['tasks_task_dependencies', 'tasks_tasks'],
280
- ['dependency edges inserted in the page', 'reachability from each depends_on'],
281
- 'dependencies are acyclic, with no self-edge',
309
+ ['tasks_task_dependencies_cycle_guard_insert', 'tasks_task_dependencies_cycle_guard_update'],
310
+ ['PAC-03'],
311
+ 'dependencies are acyclic, with no self-edge: enforced by the T12886 cycle guards on apply (a guard conflict)',
282
312
  ),
283
313
  pac(
284
314
  'task.done.evidence',
@@ -368,39 +398,48 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
368
398
  ['the owning rows'],
369
399
  'derived counters and cross-column facts agree with their rows',
370
400
  ),
371
- pac(
372
- 'apply.preconditions',
373
- 'PAC-15',
374
- 'T12936',
375
- ['tasks_tasks'],
376
- ['the whole page'],
377
- 'no apply while the local twin collapse has failed; restore is a re-baseline',
401
+ implementedCheck(
402
+ pac(
403
+ 'apply.preconditions',
404
+ 'PAC-15',
405
+ 'T12936',
406
+ ['tasks_tasks'],
407
+ ['the whole page'],
408
+ 'no apply while the local twin collapse has failed; restore is a re-baseline',
409
+ ),
410
+ 'checkApplyPreconditions',
378
411
  ),
379
412
 
380
413
  // §3.6.6: typed merge rules (T12344).
381
- rule(
382
- 'task.status.absorbing',
383
- 'T12937',
384
- 'tasks_tasks',
385
- ['status', 'completed_at', 'cancelled_at', 'cancellation_reason', 'pipeline_stage'],
386
- ['V18', 'V20', 'C22', 'P40', 'P46', 'P47'],
387
- 'done, cancelled and archived are absorbing; leaving them needs an explicit op',
388
- ),
389
- rule(
390
- 'task.pipeline-stage.max',
391
- 'T12938',
392
- 'tasks_tasks',
393
- ['pipeline_stage'],
394
- ['V21', 'C20', 'P01', 'F3'],
395
- 'pipeline_stage is the maximum by STAGE_ORDER, except an explicit restore',
396
- ),
397
- rule(
398
- 'task.verification.frozen-on-done',
399
- 'T12939',
400
- 'tasks_tasks',
401
- ['verification_json'],
402
- ['C01', 'C02', 'C03', 'C17'],
403
- 'verification is frozen once done; otherwise merged per gate',
414
+ implemented(
415
+ rule(
416
+ 'task.status.absorbing',
417
+ 'T12937',
418
+ 'tasks_tasks',
419
+ ['status', 'completed_at', 'cancelled_at', 'cancellation_reason', 'pipeline_stage'],
420
+ ['V18', 'V20', 'C22', 'P40', 'P46', 'P47'],
421
+ 'done, cancelled and archived are absorbing; leaving them needs an explicit op',
422
+ ),
423
+ ),
424
+ implemented(
425
+ rule(
426
+ 'task.pipeline-stage.max',
427
+ 'T12938',
428
+ 'tasks_tasks',
429
+ ['pipeline_stage'],
430
+ ['V21', 'C20', 'P01', 'F3'],
431
+ 'pipeline_stage is the maximum by STAGE_ORDER, except an explicit restore',
432
+ ),
433
+ ),
434
+ implemented(
435
+ rule(
436
+ 'task.verification.frozen-on-done',
437
+ 'T12939',
438
+ 'tasks_tasks',
439
+ ['verification_json'],
440
+ ['C01', 'C02', 'C03', 'C17'],
441
+ 'verification is frozen once done; otherwise merged per gate',
442
+ ),
404
443
  ),
405
444
  rule(
406
445
  'session.status.terminal',
@@ -0,0 +1,141 @@
1
+ /**
2
+ * `@cleocode/contracts/ledger`: the decoded change-journal transaction and op
3
+ * shapes (journal spec `t12342-t12343-journal-design` §2.6).
4
+ *
5
+ * A sealed segment carries an array of {@link LedgerTxn}; each transaction
6
+ * carries {@link LedgerOp}s on sync-set rows, keyed by row uid. Values use the
7
+ * canonical typed wire encoding ({@link LedgerWireValue}), and counter deltas
8
+ * travel as `{ $inc }` ({@link LedgerValue}).
9
+ *
10
+ * Zod schemas, inferred types and const data only (arch gate 10). The sealer
11
+ * that produces these shapes and the merge engine that applies them live in
12
+ * `@cleocode/core` (`store/sync/`).
13
+ *
14
+ * @module ledger
15
+ * @task T12344
16
+ * @epic T12323
17
+ */
18
+
19
+ import { z } from 'zod';
20
+ import { Hlc } from '../cloud/ids.js';
21
+
22
+ /** The transaction format version this build writes and reads (`LedgerTxn.v`). */
23
+ export const LEDGER_TXN_VERSION = 1;
24
+
25
+ /**
26
+ * A typed wire value: a string, null, a safe integer, or a typed escape for an
27
+ * integer beyond 2^53 (`$i`), a REAL (`$r`, `%!.17g` text, `Inf` accepted) or a
28
+ * BLOB (`$b`, base64).
29
+ */
30
+ export const LedgerWireValue = z.union([
31
+ z.string(),
32
+ z.number(),
33
+ z.null(),
34
+ z.object({ $i: z.string() }).strict(),
35
+ z.object({ $r: z.string() }).strict(),
36
+ z.object({ $b: z.string() }).strict(),
37
+ ]);
38
+ export type LedgerWireValue = z.infer<typeof LedgerWireValue>;
39
+
40
+ /** A counter delta (`SYNC_COUNTER_COLUMNS`): applied by summing, never by plain LWW. */
41
+ export const LedgerIncrement = z.object({ $inc: z.number() }).strict();
42
+ export type LedgerIncrement = z.infer<typeof LedgerIncrement>;
43
+
44
+ /** An after-value: a wire value or a counter delta. */
45
+ export const LedgerValue = z.union([LedgerWireValue, LedgerIncrement]);
46
+ export type LedgerValue = z.infer<typeof LedgerValue>;
47
+
48
+ /** The op kinds: insert, update, delete, re-key. */
49
+ export const LedgerOpKind = z.enum(['I', 'U', 'D', 'K']);
50
+ export type LedgerOpKind = z.infer<typeof LedgerOpKind>;
51
+
52
+ /** One op on one sync-set row (§2.6). */
53
+ export const LedgerOp = z
54
+ .object({
55
+ /** Table. */
56
+ t: z.string().min(1),
57
+ /** Row uid; for K the OLD uid. */
58
+ u: z.string().min(1),
59
+ o: LedgerOpKind,
60
+ /** The op's HLC; also every field's HLC unless `fh` names a different one. */
61
+ h: Hlc,
62
+ /** K only: the new uid. */
63
+ nu: z.string().min(1).optional(),
64
+ /** birth_fp, on every op of a minted row; for K, the new value. */
65
+ bfp: z.string().optional(),
66
+ /** K only: the old birth_fp. */
67
+ obfp: z.string().optional(),
68
+ /** Per-field HLCs where they differ from `h`. */
69
+ fh: z.record(z.string(), Hlc).optional(),
70
+ /** Natural rows: the key, with refs as uids. */
71
+ k: z.record(z.string(), LedgerValue).optional(),
72
+ /** After-values (refs as uids; counters as `{ $inc }`). */
73
+ a: z.record(z.string(), LedgerValue).optional(),
74
+ /** Before-image (secrets excluded). */
75
+ b: z.record(z.string(), LedgerWireValue).optional(),
76
+ })
77
+ .strict()
78
+ .refine((op) => (op.o === 'K') === (op.nu !== undefined), {
79
+ message: 'nu is required on a K op and only there',
80
+ });
81
+ export type LedgerOp = z.infer<typeof LedgerOp>;
82
+
83
+ /** Who wrote a transaction; `op` names the command (e.g. `tasks.reopen`). */
84
+ export const LedgerActor = z
85
+ .object({
86
+ agent: z.string().optional(),
87
+ session: z.string().optional(),
88
+ op: z.string().optional(),
89
+ })
90
+ .strict();
91
+ export type LedgerActor = z.infer<typeof LedgerActor>;
92
+
93
+ /** How a transaction's writes reached the store. */
94
+ export const LedgerTxnVia = z.enum(['accessor', 'connection', 'foreign', 'repair', 'rebind']);
95
+ export type LedgerTxnVia = z.infer<typeof LedgerTxnVia>;
96
+
97
+ /** What a transaction is for. */
98
+ export const LedgerTxnKind = z.enum([
99
+ 'write',
100
+ 'remint',
101
+ 'rekey',
102
+ 'repair',
103
+ 'exodus',
104
+ 'import',
105
+ 'retire',
106
+ ]);
107
+ export type LedgerTxnKind = z.infer<typeof LedgerTxnKind>;
108
+
109
+ /**
110
+ * One sealed transaction (§2.6). `v` is checked by the receiver, not by this
111
+ * schema, so a newer format is refused with an explicit error rather than a
112
+ * parse failure.
113
+ */
114
+ export const LedgerTxn = z
115
+ .object({
116
+ v: z.number().int().positive(),
117
+ /** `${replicaId}:${localTxnSeq}`. */
118
+ txn: z.string().min(1),
119
+ /** Only for split oversized groups: [part, of]. */
120
+ part: z.tuple([z.number().int().positive(), z.number().int().positive()]).optional(),
121
+ hlc: Hlc,
122
+ /** Merge key is (project, uid). */
123
+ project: z.string().nullable(),
124
+ scope: z.enum(['project', 'global']),
125
+ via: LedgerTxnVia,
126
+ kind: LedgerTxnKind,
127
+ actor: LedgerActor.nullable(),
128
+ ops: z.array(LedgerOp),
129
+ retire: z
130
+ .object({
131
+ replica: z.string(),
132
+ successor: z.string(),
133
+ lastReplicaSeq: z.number().int().nonnegative(),
134
+ })
135
+ .strict()
136
+ .optional(),
137
+ /** Ed25519 over the transaction signing message (§2.8). */
138
+ sig: z.string(),
139
+ })
140
+ .strict();
141
+ export type LedgerTxn = z.infer<typeof LedgerTxn>;
@@ -101,6 +101,11 @@ export const NEXUS_ACCOUNT_ERROR_CODES = [
101
101
  'E_NEXUS_VAULT_STORE_BUSY',
102
102
  /** A project restore target already holds a different project. */
103
103
  'E_NEXUS_VAULT_TARGET_OCCUPIED',
104
+ // Projects by name (`cleo cloud restore <name>`, T13102).
105
+ /** No project of the account has that name, label or id. */
106
+ 'E_NEXUS_PROJECT_NOT_FOUND',
107
+ /** Several of the account's projects have that name or label; the error lists them. */
108
+ 'E_NEXUS_PROJECT_AMBIGUOUS',
104
109
  ] as const;
105
110
 
106
111
  /** One of {@link NEXUS_ACCOUNT_ERROR_CODES}. */
@@ -181,6 +186,72 @@ export interface NexusLoginDevice {
181
186
  created: boolean | null;
182
187
  }
183
188
 
189
+ /**
190
+ * A step of the account setup `cleo login nexus` runs right after enrolment
191
+ * (T13100), named when it fails:
192
+ *
193
+ * - `connect`: open the vault connection with the new device credential;
194
+ * - `escrow-read`: read the escrowed account master key (`GET /v1/account/keys/escrow`);
195
+ * - `escrow-mint`: on the account's first device, create the master key and escrow it
196
+ * (`PUT /v1/account/keys/escrow`; a 409 means another device won, and its key is read);
197
+ * - `certify`: certify this device under the master key (`PUT /v1/devices/:id/key`);
198
+ * - `trust`: evaluate and record the signer trust state (`GET /v1/devices/trust`,
199
+ * `<cleoHome>/nexus-vault.json`).
200
+ */
201
+ export type NexusAccountSetupStep = 'connect' | 'escrow-read' | 'escrow-mint' | 'certify' | 'trust';
202
+
203
+ /**
204
+ * What `cleo login nexus` did to make the account ready for encrypted backups
205
+ * (T13100). Holds no key.
206
+ *
207
+ * - `ready`: this device holds the account master key and is certified under it;
208
+ * - `unsupported`: the server has no account key escrow; the login still succeeded;
209
+ * - `skipped`: a read-only device, which never mints or certifies;
210
+ * - `failed`: the login succeeded but the setup did not; `step` and `fix` say where and what to do.
211
+ */
212
+ export type NexusAccountSetup =
213
+ | {
214
+ status: 'ready';
215
+ /**
216
+ * `fetched`: the escrowed key was opened; `minted`: this device created and escrowed it;
217
+ * `adopted`: another device escrowed first (409) and its key was read, never re-minted.
218
+ */
219
+ escrow: 'fetched' | 'minted' | 'adopted';
220
+ /** `new`: this login certified the device; `existing`: it already was. */
221
+ certificate: 'new' | 'existing';
222
+ /** Version of the account master key. */
223
+ keyVersion: number;
224
+ /** One human line. */
225
+ summary: string;
226
+ }
227
+ | {
228
+ status: 'unsupported';
229
+ /** `E_NEXUS_VAULT_UNSUPPORTED`. */
230
+ code: NexusAccountErrorCode;
231
+ /** The remedy. */
232
+ fix: string;
233
+ /** One human line. */
234
+ summary: string;
235
+ }
236
+ | {
237
+ status: 'skipped';
238
+ /** One human line. */
239
+ summary: string;
240
+ }
241
+ | {
242
+ status: 'failed';
243
+ /** The step that failed. */
244
+ step: NexusAccountSetupStep;
245
+ /** Stable error code (a {@link NexusAccountErrorCode} when the flow raised one). */
246
+ code: string;
247
+ /** What went wrong. */
248
+ message: string;
249
+ /** The remedy. */
250
+ fix: string;
251
+ /** One human line naming the step and the remedy. */
252
+ summary: string;
253
+ };
254
+
184
255
  /** Result of `cleo login nexus` (never carries the token). */
185
256
  export interface NexusLoginResult {
186
257
  /** API origin the session belongs to. */
@@ -199,6 +270,8 @@ export interface NexusLoginResult {
199
270
  device?: NexusLoginDevice;
200
271
  /** Scopes of the stored device credential; present only with device credentials. */
201
272
  scopes?: string[];
273
+ /** The account key setup run right after enrolment (T13100); present only with device credentials. */
274
+ account?: NexusAccountSetup;
202
275
  }
203
276
 
204
277
  /** Result of `cleo logout nexus`. */
@@ -337,6 +410,15 @@ export interface NexusProjectLinkResult {
337
410
  * text is also in {@link NexusProjectLinkResult.warnings}.
338
411
  */
339
412
  attachError: { code: string; message: string; fix: string | null } | null;
413
+ /**
414
+ * The project data key version the server holds from this registration
415
+ * (onboarding B): `1` when the link sent a new project's first key with it,
416
+ * `null` when it sent none (the project already existed, the server does
417
+ * not take keys at registration, or this device has no account key; see
418
+ * {@link NexusProjectLinkResult.warnings}). With `null`, the first
419
+ * `cleo cloud push` creates the key.
420
+ */
421
+ initialKeyVersion: number | null;
340
422
  /** Non-fatal problems (for example, presence could not be sent). */
341
423
  warnings: string[];
342
424
  }
@@ -167,8 +167,15 @@ const projectFields = {
167
167
  export const nexusCloudProjectListItemSchema = z.object({
168
168
  ...projectFields,
169
169
  role: z.string(),
170
+ /**
171
+ * The project name sealed with the project data key (`Project.encryptedName`), base64;
172
+ * only a holder of the account key can open it (T13102).
173
+ */
174
+ encryptedName: z.string().nullable().optional(),
170
175
  streamId: z.string().optional(),
171
176
  headSeq: z.number().int().nonnegative().nullable().optional(),
177
+ /** The stream's head snapshot; null when nothing was ever pushed (T13102: restorable or not). */
178
+ headCheckpointId: z.string().nullable().optional(),
172
179
  openConflicts: z.number().int().nonnegative().optional(),
173
180
  replicas: z.array(nexusCloudReplicaSchema).default([]),
174
181
  devices: nexusCloudDeviceCountsSchema.optional(),
@@ -257,10 +264,21 @@ export const nexusCloudStatusCheckSchema = z.object({
257
264
  detail: z.string(),
258
265
  });
259
266
 
267
+ /**
268
+ * The E3 `features` entry of a server whose `POST /v1/projects` stores `initialKey`, a new project's
269
+ * first data key (onboarding B, cleo-nexus T095). `cleo project link` sends a key only to such a server.
270
+ */
271
+ export const NEXUS_FEATURE_PROJECT_INITIAL_KEY = 'project.initial-key';
272
+
260
273
  /** `NexusStatus` (E3 `GET /v1/status`). */
261
274
  export const nexusCloudStatusSchema = z.object({
262
275
  serverTime: isoTime.optional(),
263
276
  apiVersion: z.string().optional(),
277
+ /**
278
+ * Optional server features (e.g. {@link NEXUS_FEATURE_PROJECT_INITIAL_KEY}). Absent from servers older
279
+ * than the list, which therefore support none of them.
280
+ */
281
+ features: z.array(z.string()).optional(),
264
282
  user: nexusCloudUserSchema,
265
283
  credential: nexusCloudCredentialSchema,
266
284
  device: nexusCloudDeviceSchema.nullable(),
@@ -386,6 +404,23 @@ export interface CloudProjectsResult {
386
404
  warnings: CloudWarning[];
387
405
  }
388
406
 
407
+ /**
408
+ * A replica this device retired: the store file it named was replaced (a
409
+ * vault restore or pull) or rolled back, so a new replica took its place
410
+ * (journal spec §1.5). The server lists it as history until S4's signed
411
+ * `retire` transaction announces it (T13109).
412
+ */
413
+ export interface CloudRetiredReplica {
414
+ /** The retired replica id. */
415
+ replicaId: string;
416
+ /** The replica that replaced it. */
417
+ successor: string;
418
+ /** When it was retired on this device. */
419
+ retiredAt: string;
420
+ /** The rebind reason(s), e.g. `vault-restore`; `null` when not recorded. */
421
+ reason: string | null;
422
+ }
423
+
389
424
  /** `cleo cloud projects show [<id>]` (operation `cloud.projects.show`). */
390
425
  export interface CloudProjectShowResult extends NexusCloudProjectDetail {
391
426
  /** API origin asked. */
@@ -399,6 +434,8 @@ export interface CloudProjectShowResult extends NexusCloudProjectDetail {
399
434
  * fetched from E15 and `replicas` holds them all (up to the page budget).
400
435
  */
401
436
  replicaPaging: CloudPaging;
437
+ /** Listed replicas this device retired (from its local registry), newest first. */
438
+ retiredHere: CloudRetiredReplica[];
402
439
  /** Non-fatal problems. */
403
440
  warnings: CloudWarning[];
404
441
  }
@@ -441,6 +478,11 @@ export interface CloudStatusLocal {
441
478
  projectId: string | null;
442
479
  /** The active replica id of the current project's store (read-only; never bound here). */
443
480
  replicaId: string | null;
481
+ /**
482
+ * Earlier replicas of the current project's store that this device retired
483
+ * (from its local registry), newest first: the server still lists them.
484
+ */
485
+ retiredReplicas: CloudRetiredReplica[];
444
486
  /** `.cleo/nexus-link.json` when it holds an entry for the origin, else `null`. */
445
487
  linkPath: string | null;
446
488
  /** The device credential store (`nexus-device.json`). */
@@ -448,6 +490,25 @@ export interface CloudStatusLocal {
448
490
  }
449
491
 
450
492
  /** `CloudStatusResult` (§4.4): the data of the `cloud.status` envelope. */
493
+ /**
494
+ * One device holding the project, as `cleo cloud status` lists it (T13290):
495
+ * a replica row reduced to who holds it and whether its presence is fresh.
496
+ */
497
+ export interface CloudStatusHolder {
498
+ /** Nexus device id. */
499
+ deviceId: string;
500
+ /** The device's display name. */
501
+ deviceName: string;
502
+ /** The replica this device holds. */
503
+ replicaId: string;
504
+ /** Last presence report, or `null` when it never reported. */
505
+ presenceAt: string | null;
506
+ /** Presence within {@link NEXUS_PRESENCE_FRESH_SECONDS}. */
507
+ fresh: boolean;
508
+ /** This machine's own device. */
509
+ thisDevice: boolean;
510
+ }
511
+
451
512
  export interface CloudStatusResult {
452
513
  /** The remote verdict, downgraded by local facts the server cannot see. */
453
514
  verdict: CloudStatusVerdict;
@@ -459,10 +520,140 @@ export interface CloudStatusResult {
459
520
  remote: NexusCloudStatus | null;
460
521
  /** This device's global store on the account's home stream (T12952); absent when not signed in. */
461
522
  global?: CloudStatusGlobalStore;
523
+ /**
524
+ * The local sync journal of each store this machine holds (T12998): the
525
+ * project store and the global store. Read on every path (signed in or not,
526
+ * online or offline); absent when no store could be read.
527
+ */
528
+ sync?: CloudStatusSync;
529
+ /**
530
+ * The devices holding the project (T13290), from the replica list; absent
531
+ * when there is no registered project to ask about or the list failed (a
532
+ * warning says so). Additive: older readers ignore it.
533
+ */
534
+ holders?: CloudStatusHolder[];
462
535
  /** Non-fatal problems. */
463
536
  warnings: CloudWarning[];
464
537
  }
465
538
 
539
+ /**
540
+ * A sync fact this build cannot know yet, and why (T12998). The block's shape
541
+ * is fixed now, so the fields keep their names when the outbox (T12343) and
542
+ * segment push (S4) fill them in.
543
+ */
544
+ export interface CloudSyncUnknown {
545
+ readonly known: false;
546
+ /** What is missing: the transactional outbox (T12343) or segment push/pull (S4). */
547
+ readonly needs: 'T12343' | 'S4';
548
+ /** One sentence for the operator. */
549
+ readonly reason: string;
550
+ }
551
+
552
+ /** A sync fact this build knows. */
553
+ export interface CloudSyncKnown<T> {
554
+ readonly known: true;
555
+ readonly value: T;
556
+ }
557
+
558
+ /** A sync fact: known, or unknown with the reason (T12998). */
559
+ export type CloudSyncFact<T> = CloudSyncKnown<T> | CloudSyncUnknown;
560
+
561
+ /** The sync flags of a store (`_sync_meta`; all off when the journal is absent). */
562
+ export interface CloudSyncFlags {
563
+ readonly capture: boolean;
564
+ readonly seal: boolean;
565
+ readonly push: boolean;
566
+ readonly pull: boolean;
567
+ readonly strict: boolean;
568
+ }
569
+
570
+ /** How far a stream lags the server. */
571
+ export interface CloudSyncLag {
572
+ /** Ops on the server this store has not pulled, plus ops here not yet pushed. */
573
+ readonly ops: number;
574
+ /** Age of the oldest op not yet exchanged, in milliseconds. */
575
+ readonly ms: number;
576
+ }
577
+
578
+ /** One store's local sync journal, as `cleo cloud status` reports it (T12998). Read-only. */
579
+ /** A local write a sync rebase holds unreplayed until its echo is decided (journal spec §3.5 Rule 5). */
580
+ export interface CloudStatusHeldRow {
581
+ /** The table it writes. */
582
+ readonly table: string;
583
+ /** The row's uid. */
584
+ readonly uid: string;
585
+ /** The local transaction it belongs to. */
586
+ readonly txn: string;
587
+ /** When it was held (ISO-8601). */
588
+ readonly heldAt: string;
589
+ /** What refused its replay: the conflict preview. */
590
+ readonly reason: string;
591
+ }
592
+
593
+ /** `CloudStatusSyncStream.held`: the store's held writes (§3.5 Rule 5). */
594
+ export interface CloudStatusHeld {
595
+ /** How many local ops are held. */
596
+ readonly count: number;
597
+ /** When the oldest was held (ISO-8601), or `null` when none is. */
598
+ readonly oldestAt: string | null;
599
+ /** Holds older than `warnDays`, oldest first (`cleo doctor sync-journal` lists the same). */
600
+ readonly long: readonly CloudStatusHeldRow[];
601
+ /** The age, in days, past which a hold is long. */
602
+ readonly warnDays: number;
603
+ }
604
+
605
+ export interface CloudStatusSyncStream {
606
+ /** Which store. */
607
+ readonly scope: 'project' | 'global';
608
+ /** Its stream (`project:<id>`, `home:<userId>`) when known locally, else `null`. */
609
+ readonly stream: string | null;
610
+ /** The store file. */
611
+ readonly dbPath: string;
612
+ /** The sync journal tables are installed in this store. */
613
+ readonly journalInstalled: boolean;
614
+ /** The store's sync flags. */
615
+ readonly flags: CloudSyncFlags;
616
+ /** Captured changes not yet sealed into ops (`_sync_capture` live rows). */
617
+ readonly unsealedOps: number;
618
+ /** When the oldest unsealed change was captured (epoch ms), or `null`. */
619
+ readonly oldestUnsealedAtMs: number | null;
620
+ /** The highest sealed local sequence (`_sync_txn.local_seq`), or `null` before the first seal. */
621
+ readonly lastSealedSeq: number | null;
622
+ /** Captures the sealer could not read and holds aside (`_sync_quarantine`), per table. */
623
+ readonly quarantined: Readonly<Record<string, number>>;
624
+ /** Tables marked suspect until a repair re-emits them. */
625
+ readonly suspectTables: readonly string[];
626
+ /** Local writes a sync rebase holds, until their echo is decided (§3.5 Rule 5). */
627
+ readonly held: CloudStatusHeld;
628
+ /** Sealed ops not yet handed to the outbox for sending. */
629
+ readonly unsentOps: CloudSyncFact<number>;
630
+ /** The last sequence this store pushed to the server. */
631
+ readonly lastPushedSeq: CloudSyncFact<number>;
632
+ /** The last server sequence this store pulled. */
633
+ readonly lastPulledSeq: CloudSyncFact<number>;
634
+ /** The server's head sequence for this stream. */
635
+ readonly serverHeadSeq: CloudSyncFact<number>;
636
+ /** Devices attached to this stream and when each last synced. */
637
+ readonly devices: CloudSyncFact<
638
+ ReadonlyArray<{ readonly deviceId: string; readonly lastSyncAt: string | null }>
639
+ >;
640
+ /** Conflicts held open on this stream. */
641
+ readonly openConflicts: CloudSyncFact<number>;
642
+ /** How far this store lags the server. */
643
+ readonly lag: CloudSyncFact<CloudSyncLag>;
644
+ }
645
+
646
+ /** `CloudStatusResult.sync` (T12998). */
647
+ export interface CloudStatusSync {
648
+ /** One entry per store this machine holds (project, then global), when its file exists. */
649
+ readonly streams: readonly CloudStatusSyncStream[];
650
+ /**
651
+ * This build reads the local journal only; the server-side fields stay
652
+ * unknown until the outbox (T12343) and segment push/pull (S4) land.
653
+ */
654
+ readonly partial: true;
655
+ }
656
+
466
657
  /** `cleo cloud status`: this device's global store (the main brain) on Cleo Nexus (T12952). */
467
658
  export interface CloudStatusGlobalStore {
468
659
  /** The server lists home-stream replicas. */
@@ -485,6 +676,8 @@ export interface CloudStatusOfflineDetails {
485
676
  summary: CloudStatusSummary;
486
677
  /** Warnings collected before the server stopped answering. */
487
678
  warnings: CloudWarning[];
679
+ /** The local sync journal, read before the server was asked (T12998). */
680
+ sync?: CloudStatusSync;
488
681
  }
489
682
 
490
683
  // ---------- error mapping (§4.0.4) ----------