@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.
- package/dist/__tests__/cloud-replica-retirement.test.d.ts +10 -0
- package/dist/__tests__/cloud-replica-retirement.test.d.ts.map +1 -0
- package/dist/__tests__/cloud-replica-retirement.test.js +111 -0
- package/dist/__tests__/cloud-replica-retirement.test.js.map +1 -0
- package/dist/__tests__/cloud-status-checkpoint-reads.test.d.ts +10 -0
- package/dist/__tests__/cloud-status-checkpoint-reads.test.d.ts.map +1 -0
- package/dist/__tests__/cloud-status-checkpoint-reads.test.js +49 -0
- package/dist/__tests__/cloud-status-checkpoint-reads.test.js.map +1 -0
- package/dist/agent-registry.d.ts +5 -1
- package/dist/agent-registry.d.ts.map +1 -1
- package/dist/cloud/api.d.ts +156 -0
- package/dist/cloud/api.d.ts.map +1 -1
- package/dist/cloud/api.js +99 -1
- package/dist/cloud/api.js.map +1 -1
- package/dist/cloud/envelope.d.ts +3 -3
- package/dist/dispatch/operations-registry.js +1 -1
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/doctor.d.ts +79 -7
- package/dist/doctor.d.ts.map +1 -1
- package/dist/heavy-command-hook.d.ts +134 -5
- package/dist/heavy-command-hook.d.ts.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/invariants/sync-write-invariants.d.ts.map +1 -1
- package/dist/invariants/sync-write-invariants.js +31 -6
- package/dist/invariants/sync-write-invariants.js.map +1 -1
- package/dist/ledger/index.d.ts +211 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +122 -0
- package/dist/ledger/index.js.map +1 -0
- package/dist/nexus-account.d.ts +72 -1
- package/dist/nexus-account.d.ts.map +1 -1
- package/dist/nexus-account.js +5 -0
- package/dist/nexus-account.js.map +1 -1
- package/dist/nexus-cloud.d.ts +175 -0
- package/dist/nexus-cloud.d.ts.map +1 -1
- package/dist/nexus-cloud.js +17 -0
- package/dist/nexus-cloud.js.map +1 -1
- package/dist/nexus-first-run.d.ts +145 -0
- package/dist/nexus-first-run.d.ts.map +1 -0
- package/dist/nexus-first-run.js +54 -0
- package/dist/nexus-first-run.js.map +1 -0
- package/dist/nexus-vault.d.ts +113 -1
- package/dist/nexus-vault.d.ts.map +1 -1
- package/dist/nexus-vault.js +27 -0
- package/dist/nexus-vault.js.map +1 -1
- package/dist/operations/admin.d.ts +14 -1
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/docs-lifecycle.d.ts +23 -0
- package/dist/operations/docs-lifecycle.d.ts.map +1 -0
- package/dist/operations/docs-lifecycle.js +29 -0
- package/dist/operations/docs-lifecycle.js.map +1 -0
- package/dist/operations/docs.d.ts +2 -11
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js +3 -17
- package/dist/operations/docs.js.map +1 -1
- package/dist/project-context.d.ts +17 -2
- package/dist/project-context.d.ts.map +1 -1
- package/dist/release/evidence-atoms.d.ts +15 -7
- package/dist/release/evidence-atoms.d.ts.map +1 -1
- package/dist/release/evidence-atoms.js +16 -12
- package/dist/release/evidence-atoms.js.map +1 -1
- package/dist/resource-governor.d.ts +96 -0
- package/dist/resource-governor.d.ts.map +1 -1
- package/dist/resource-governor.js.map +1 -1
- package/dist/row-identity.d.ts +81 -0
- package/dist/row-identity.d.ts.map +1 -1
- package/dist/store-restore.d.ts +66 -0
- package/dist/store-restore.d.ts.map +1 -0
- package/dist/store-restore.js +9 -0
- package/dist/store-restore.js.map +1 -0
- package/dist/task.d.ts +14 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js.map +1 -1
- package/dist/tasks.d.ts +17 -17
- package/dist/workgraph.d.ts +28 -28
- package/package.json +6 -2
- package/src/__tests__/cloud-replica-retirement.test.ts +135 -0
- package/src/__tests__/cloud-status-checkpoint-reads.test.ts +60 -0
- package/src/agent-registry.ts +5 -1
- package/src/cloud/api.ts +125 -1
- package/src/dispatch/operations-registry.ts +1 -1
- package/src/doctor.ts +85 -7
- package/src/heavy-command-hook.ts +175 -5
- package/src/index.ts +58 -0
- package/src/invariants/sync-write-invariants.ts +81 -42
- package/src/ledger/index.ts +141 -0
- package/src/nexus-account.ts +82 -0
- package/src/nexus-cloud.ts +193 -0
- package/src/nexus-first-run.ts +165 -0
- package/src/nexus-vault.ts +124 -1
- package/src/operations/admin.ts +14 -1
- package/src/operations/conduit.ts +1 -1
- package/src/operations/docs-lifecycle.ts +31 -0
- package/src/operations/docs.ts +4 -19
- package/src/project-context.ts +17 -2
- package/src/release/evidence-atoms.ts +16 -12
- package/src/resource-governor.ts +100 -0
- package/src/row-identity.ts +98 -0
- package/src/store-restore.ts +70 -0
- 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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
306
|
+
trigger(
|
|
276
307
|
'task.dependency.graph',
|
|
277
|
-
'PAC-03',
|
|
278
|
-
'T12924',
|
|
279
308
|
['tasks_task_dependencies', 'tasks_tasks'],
|
|
280
|
-
['
|
|
281
|
-
'
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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>;
|
package/src/nexus-account.ts
CHANGED
|
@@ -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
|
}
|
package/src/nexus-cloud.ts
CHANGED
|
@@ -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) ----------
|