@cleocode/contracts 2026.10.4 → 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 (90) 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 +76 -3
  19. package/dist/doctor.d.ts.map +1 -1
  20. package/dist/index.d.ts +7 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/invariants/sync-write-invariants.d.ts.map +1 -1
  25. package/dist/invariants/sync-write-invariants.js +31 -6
  26. package/dist/invariants/sync-write-invariants.js.map +1 -1
  27. package/dist/ledger/index.d.ts +211 -0
  28. package/dist/ledger/index.d.ts.map +1 -0
  29. package/dist/ledger/index.js +122 -0
  30. package/dist/ledger/index.js.map +1 -0
  31. package/dist/nexus-account.d.ts +9 -0
  32. package/dist/nexus-account.d.ts.map +1 -1
  33. package/dist/nexus-cloud.d.ts +171 -0
  34. package/dist/nexus-cloud.d.ts.map +1 -1
  35. package/dist/nexus-cloud.js +10 -0
  36. package/dist/nexus-cloud.js.map +1 -1
  37. package/dist/nexus-vault.d.ts +113 -1
  38. package/dist/nexus-vault.d.ts.map +1 -1
  39. package/dist/nexus-vault.js +27 -0
  40. package/dist/nexus-vault.js.map +1 -1
  41. package/dist/operations/admin.d.ts +14 -1
  42. package/dist/operations/admin.d.ts.map +1 -1
  43. package/dist/operations/docs-lifecycle.d.ts +23 -0
  44. package/dist/operations/docs-lifecycle.d.ts.map +1 -0
  45. package/dist/operations/docs-lifecycle.js +29 -0
  46. package/dist/operations/docs-lifecycle.js.map +1 -0
  47. package/dist/operations/docs.d.ts +2 -11
  48. package/dist/operations/docs.d.ts.map +1 -1
  49. package/dist/operations/docs.js +3 -17
  50. package/dist/operations/docs.js.map +1 -1
  51. package/dist/project-context.d.ts +7 -0
  52. package/dist/project-context.d.ts.map +1 -1
  53. package/dist/release/evidence-atoms.d.ts +15 -7
  54. package/dist/release/evidence-atoms.d.ts.map +1 -1
  55. package/dist/release/evidence-atoms.js +16 -12
  56. package/dist/release/evidence-atoms.js.map +1 -1
  57. package/dist/resource-governor.d.ts +39 -0
  58. package/dist/resource-governor.d.ts.map +1 -1
  59. package/dist/resource-governor.js.map +1 -1
  60. package/dist/row-identity.d.ts +81 -0
  61. package/dist/row-identity.d.ts.map +1 -1
  62. package/dist/store-restore.d.ts +66 -0
  63. package/dist/store-restore.d.ts.map +1 -0
  64. package/dist/store-restore.js +9 -0
  65. package/dist/store-restore.js.map +1 -0
  66. package/dist/task.d.ts +6 -0
  67. package/dist/task.d.ts.map +1 -1
  68. package/package.json +6 -2
  69. package/src/__tests__/cloud-replica-retirement.test.ts +135 -0
  70. package/src/__tests__/cloud-status-checkpoint-reads.test.ts +60 -0
  71. package/src/agent-registry.ts +5 -1
  72. package/src/cloud/api.ts +125 -1
  73. package/src/dispatch/operations-registry.ts +1 -1
  74. package/src/doctor.ts +82 -3
  75. package/src/index.ts +33 -0
  76. package/src/invariants/sync-write-invariants.ts +81 -42
  77. package/src/ledger/index.ts +141 -0
  78. package/src/nexus-account.ts +9 -0
  79. package/src/nexus-cloud.ts +186 -0
  80. package/src/nexus-vault.ts +124 -1
  81. package/src/operations/admin.ts +14 -1
  82. package/src/operations/conduit.ts +1 -1
  83. package/src/operations/docs-lifecycle.ts +31 -0
  84. package/src/operations/docs.ts +4 -19
  85. package/src/project-context.ts +7 -0
  86. package/src/release/evidence-atoms.ts +16 -12
  87. package/src/resource-governor.ts +40 -0
  88. package/src/row-identity.ts +98 -0
  89. package/src/store-restore.ts +70 -0
  90. package/src/task.ts +6 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Cloud contract mirror of cleo-nexus #41 (T123 replica retirement, contract
3
+ * v2.28; T13284): the E31 request and answer, the stored retirement, and the
4
+ * `retiredAt` / `successor` fields home replicas gain, as the server validates
5
+ * them.
6
+ *
7
+ * @task T13284
8
+ */
9
+
10
+ import { describe, expect, it } from 'vitest';
11
+ import {
12
+ AttachHomeReplicaResult,
13
+ HomeReplica,
14
+ ListHomeReplicasResult,
15
+ ReplicaRetirement,
16
+ RetireReplicaRequest,
17
+ RetireReplicaResult,
18
+ } from '../cloud/index.js';
19
+ import { NEXUS_REPLICA_RETIREMENT_REASONS } from '../nexus-vault.js';
20
+
21
+ const DEVICE = '0192f1c2-7d3e-4abc-8def-0000000000b0';
22
+ const R1 = '0192f1c2-7d3e-7abc-8def-0000000000a1';
23
+ const R2 = '0192f1c2-7d3e-7abc-8def-0000000000b1';
24
+ const STREAM = 'project:0192f1c2-7d3e-7abc-8def-0123456789ab';
25
+ const SIG = Buffer.alloc(64, 7).toString('base64');
26
+
27
+ const request = {
28
+ deviceId: DEVICE,
29
+ successor: R2,
30
+ lastReplicaSeq: 4,
31
+ txnId: `${R1}:17`,
32
+ signature: SIG,
33
+ };
34
+
35
+ describe('RetireReplicaRequest (T13284)', () => {
36
+ it('takes a successor, lastReplicaSeq and txn id, each nullable', () => {
37
+ expect(RetireReplicaRequest.safeParse(request).success).toBe(true);
38
+ expect(
39
+ RetireReplicaRequest.safeParse({
40
+ ...request,
41
+ successor: null,
42
+ lastReplicaSeq: null,
43
+ txnId: null,
44
+ }).success,
45
+ ).toBe(true);
46
+ });
47
+
48
+ it('refuses a negative or fractional lastReplicaSeq and a malformed txn id', () => {
49
+ expect(RetireReplicaRequest.safeParse({ ...request, lastReplicaSeq: -1 }).success).toBe(false);
50
+ expect(RetireReplicaRequest.safeParse({ ...request, lastReplicaSeq: 1.5 }).success).toBe(false);
51
+ for (const txnId of [
52
+ R1,
53
+ `${R1}:`,
54
+ `${R1}:01`,
55
+ `${R1}:-1`,
56
+ `not-a-uuid:3`,
57
+ `${R1}:12345678901234567`,
58
+ ]) {
59
+ expect(RetireReplicaRequest.safeParse({ ...request, txnId }).success).toBe(false);
60
+ }
61
+ // Any UUID may emit the transaction (the successor on a rebind), not only a v7 replica id.
62
+ expect(RetireReplicaRequest.safeParse({ ...request, txnId: `${DEVICE}:0` }).success).toBe(true);
63
+ });
64
+
65
+ it('a successor must be a replica id (UUIDv7)', () => {
66
+ expect(RetireReplicaRequest.safeParse({ ...request, successor: DEVICE }).success).toBe(false);
67
+ });
68
+ });
69
+
70
+ describe('ReplicaRetirement and RetireReplicaResult (T13284)', () => {
71
+ const stored = {
72
+ streamId: STREAM,
73
+ replicaId: R1,
74
+ successor: R2,
75
+ lastReplicaSeq: null,
76
+ signerDeviceId: DEVICE,
77
+ txnId: null,
78
+ signature: SIG,
79
+ retiredAt: '2026-10-06T07:00:00.000Z',
80
+ };
81
+
82
+ it('is the stored record, wrapped as { retirement }', () => {
83
+ expect(ReplicaRetirement.safeParse(stored).success).toBe(true);
84
+ expect(RetireReplicaResult.safeParse({ retirement: stored }).success).toBe(true);
85
+ expect(RetireReplicaResult.safeParse(stored).success).toBe(false);
86
+ const { retiredAt: _retiredAt, ...undated } = stored;
87
+ expect(ReplicaRetirement.safeParse(undated).success).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe('home replicas carry retiredAt and successor (T13284)', () => {
92
+ const replica = {
93
+ replicaId: R1,
94
+ deviceId: DEVICE,
95
+ deviceState: 'active',
96
+ attachedAt: '2026-10-01T00:00:00.000Z',
97
+ lastSyncAt: null,
98
+ presence: null,
99
+ presenceAt: null,
100
+ };
101
+
102
+ it('parses an older server without them, and a retired replica with them', () => {
103
+ expect(HomeReplica.safeParse(replica).success).toBe(true);
104
+ const retired = { ...replica, retiredAt: '2026-10-06T07:00:00.000Z', successor: R2 };
105
+ expect(HomeReplica.parse(retired)).toMatchObject({
106
+ retiredAt: retired.retiredAt,
107
+ successor: R2,
108
+ });
109
+ expect(HomeReplica.safeParse({ ...replica, retiredAt: null, successor: null }).success).toBe(
110
+ true,
111
+ );
112
+ expect(HomeReplica.safeParse({ ...replica, successor: DEVICE }).success).toBe(false);
113
+ });
114
+
115
+ it('the attach answer and the list follow HomeReplica', () => {
116
+ expect(AttachHomeReplicaResult).toBe(HomeReplica);
117
+ expect(ListHomeReplicasResult.shape.replicas.element).toBe(HomeReplica);
118
+ });
119
+ });
120
+
121
+ describe('E31 refusal reasons (T13284)', () => {
122
+ it('are the seven server reasons', () => {
123
+ expect([...NEXUS_REPLICA_RETIREMENT_REASONS].sort()).toEqual(
124
+ [
125
+ 'not-pinned-or-owner',
126
+ 'replica-retired',
127
+ 'retire-below-head',
128
+ 'retired',
129
+ 'successor-other-user',
130
+ 'successor-retired',
131
+ 'successor-taken',
132
+ ].sort(),
133
+ );
134
+ });
135
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Cloud contract mirrors of cleo-nexus #38 (`DeviceScope`, `StatusQuery`;
3
+ * T13282) and #39 (`GetCheckpointResult`; T13283): the shapes the server
4
+ * validates, so a client that parses with them agrees with it.
5
+ *
6
+ * @task T13282
7
+ * @task T13283
8
+ */
9
+
10
+ import { describe, expect, it } from 'vitest';
11
+ import {
12
+ DeviceScope,
13
+ GetCheckpointResult,
14
+ ListCheckpointsResult,
15
+ StatusQuery,
16
+ } from '../cloud/index.js';
17
+
18
+ const PROJECT = '0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b';
19
+ const REPLICA = '0198a1b2-0000-7000-8000-0000000000a1';
20
+
21
+ describe('DeviceScope and StatusQuery (T13282)', () => {
22
+ it('DeviceScope is the eight §2.3 scopes and nothing else', () => {
23
+ expect(DeviceScope.options).toEqual([
24
+ 'account:read',
25
+ 'devices:read',
26
+ 'projects:read',
27
+ 'projects:write',
28
+ 'sync:read',
29
+ 'sync:write',
30
+ 'keys:read',
31
+ 'keys:write',
32
+ ]);
33
+ expect(DeviceScope.safeParse('admin').success).toBe(false);
34
+ });
35
+
36
+ it('StatusQuery: both optional, replicaId requires projectId and must be a UUIDv7', () => {
37
+ expect(StatusQuery.safeParse({}).success).toBe(true);
38
+ expect(StatusQuery.safeParse({ projectId: PROJECT }).success).toBe(true);
39
+ expect(StatusQuery.safeParse({ projectId: PROJECT, replicaId: REPLICA }).success).toBe(true);
40
+ const orphan = StatusQuery.safeParse({ replicaId: REPLICA });
41
+ expect(orphan.success).toBe(false);
42
+ expect(orphan.error?.issues[0]?.message).toBe('replicaId requires projectId');
43
+ // A UUIDv4 is not a replica id.
44
+ expect(
45
+ StatusQuery.safeParse({
46
+ projectId: PROJECT,
47
+ replicaId: '0198a1b2-0000-4000-8000-0000000000a1',
48
+ }).success,
49
+ ).toBe(false);
50
+ });
51
+ });
52
+
53
+ describe('GetCheckpointResult (T13283)', () => {
54
+ it('is one checkpoint shaped exactly as a list item', () => {
55
+ const listItem = ListCheckpointsResult.shape.checkpoints.element;
56
+ expect(GetCheckpointResult.shape.checkpoint).toBe(listItem);
57
+ expect(Object.keys(GetCheckpointResult.shape)).toEqual(['checkpoint']);
58
+ expect(GetCheckpointResult.safeParse({ checkpoints: [] }).success).toBe(false);
59
+ });
60
+ });
@@ -47,7 +47,11 @@ export interface AgentCredential {
47
47
  * device). Re-register with `cleo agent register --id <id> --api-key <key>`.
48
48
  */
49
49
  requiresReauth?: boolean;
50
- /** Base URL of the messaging API (default: api.signaldock.io). */
50
+ /**
51
+ * Base URL of the cloud messaging API, or `'local'` for none. Rows from
52
+ * before T13169 may still hold the retired `https://api.signaldock.io`;
53
+ * CLEO never calls a SignalDock host.
54
+ */
51
55
  apiBaseUrl: string;
52
56
  /** Agent classification from the registry (e.g. 'code_dev', 'orchestrator'). */
53
57
  classification?: string;
package/src/cloud/api.ts CHANGED
@@ -69,8 +69,45 @@ export type Device = z.infer<typeof Device>;
69
69
  export const ListDevicesResult = z.object({ devices: z.array(Device) });
70
70
  export type ListDevicesResult = z.infer<typeof ListDevicesResult>;
71
71
 
72
+ // ---------- device credential scopes and E3 status (§4.1) ----------
73
+
74
+ /** What a device credential may do (§2.3). A signed-in session holds every scope. */
75
+ export const DeviceScope = z.enum([
76
+ 'account:read',
77
+ 'devices:read',
78
+ 'projects:read',
79
+ 'projects:write',
80
+ 'sync:read',
81
+ 'sync:write',
82
+ 'keys:read',
83
+ 'keys:write',
84
+ ]);
85
+ /** One device credential scope. */
86
+ export type DeviceScope = z.infer<typeof DeviceScope>;
87
+
88
+ /** E3 `GET /v1/status` query (§4.1): `replicaId` (a UUIDv7) requires `projectId`. */
89
+ export const StatusQuery = z
90
+ .object({ projectId: ProjectId.optional(), replicaId: ReplicaId.optional() })
91
+ .refine((q) => q.replicaId === undefined || q.projectId !== undefined, {
92
+ message: 'replicaId requires projectId',
93
+ });
94
+ /** The E3 status query. */
95
+ export type StatusQuery = z.infer<typeof StatusQuery>;
96
+
72
97
  // ---------- projects ----------
73
98
 
99
+ /**
100
+ * A new project's first data key, stored in the same transaction as its registration (onboarding B): key
101
+ * version 1, wrapped by the registrant's account master key. Needs keys:write on top of projects:write, and is
102
+ * honoured only when the call creates the project. Later versions stay rotations
103
+ * (PUT /v1/projects/:projectId/keys/:userId).
104
+ */
105
+ export const InitialProjectKey = z.object({
106
+ /** The project data key, wrapped by the registrant's master key at key version 1. */
107
+ wrappedProjectKey: Base64,
108
+ });
109
+ export type InitialProjectKey = z.infer<typeof InitialProjectKey>;
110
+
74
111
  export const RegisterProjectRequest = z.object({
75
112
  projectId: ProjectId,
76
113
  /**
@@ -87,6 +124,8 @@ export const RegisterProjectRequest = z.object({
87
124
  * caller's personal organization, which every account has.
88
125
  */
89
126
  organizationId: z.string().uuid().optional(),
127
+ /** The project's first data key (onboarding B). Honoured only when this call creates the project. */
128
+ initialKey: InitialProjectKey.optional(),
90
129
  });
91
130
  export type RegisterProjectRequest = z.infer<typeof RegisterProjectRequest>;
92
131
 
@@ -103,7 +142,16 @@ export const Project = z.object({
103
142
  });
104
143
  export type Project = z.infer<typeof Project>;
105
144
 
106
- export const RegisterProjectResult = z.object({ project: Project, streamId: StreamId });
145
+ export const RegisterProjectResult = z.object({
146
+ project: Project,
147
+ streamId: StreamId,
148
+ /**
149
+ * The key version `initialKey` is stored at for the caller: 1 when this call created the project with it,
150
+ * or repeated the caller's identical version 1; null when no initialKey was sent, or the project already
151
+ * existed with no key (initialKey ignored). Absent from servers older than initialKey.
152
+ */
153
+ initialKeyVersion: KeyVersion.nullable().optional(),
154
+ });
107
155
  export type RegisterProjectResult = z.infer<typeof RegisterProjectResult>;
108
156
 
109
157
  /**
@@ -162,6 +210,10 @@ export const HomeReplica = z.object({
162
210
  lastSyncAt: z.iso.datetime().nullable(),
163
211
  presence: ReplicaPresence.nullable(),
164
212
  presenceAt: z.iso.datetime().nullable(),
213
+ /** When the replica was retired (T123), else null. Absent from servers older than retirement. */
214
+ retiredAt: z.iso.datetime().nullable().optional(),
215
+ /** The retired replica's named successor, else null (not retired, or retired with none). */
216
+ successor: ReplicaId.nullable().optional(),
165
217
  });
166
218
  export type HomeReplica = z.infer<typeof HomeReplica>;
167
219
 
@@ -501,6 +553,15 @@ export type CreateCheckpointResult = z.infer<typeof CreateCheckpointResult>;
501
553
  export const ListCheckpointsResult = z.object({ checkpoints: z.array(Checkpoint) });
502
554
  export type ListCheckpointsResult = z.infer<typeof ListCheckpointsResult>;
503
555
 
556
+ /**
557
+ * E29 `GET …/checkpoints/head` and E30 `GET …/checkpoints/:checkpointId` (cleo-nexus T122, contract
558
+ * v2.27): one checkpoint, shaped as a list item. Both answer 404 when there is no head, or the id is
559
+ * unknown, malformed or on another stream.
560
+ */
561
+ export const GetCheckpointResult = z.object({ checkpoint: Checkpoint });
562
+ /** One checkpoint read by id or as the head. */
563
+ export type GetCheckpointResult = z.infer<typeof GetCheckpointResult>;
564
+
504
565
  // ---------- blobs ----------
505
566
 
506
567
  export const PresignUploadRequest = z.object({
@@ -585,6 +646,69 @@ export type ListedLease = z.infer<typeof ListedLease>;
585
646
  export const ListLeasesResult = z.object({ leases: z.array(ListedLease) });
586
647
  export type ListLeasesResult = z.infer<typeof ListLeasesResult>;
587
648
 
649
+ // ---------- replica retirement (T123; cleocode journal spec §1.3, §3.5 D5) ----------
650
+
651
+ /**
652
+ * A journal transaction id, `<emittingReplicaId>:<localTxnSeq>`: the replica that emitted the transaction and its own
653
+ * transaction counter. Only the format is checked: the emitting replica may be the successor (a rebind), not the
654
+ * retired one.
655
+ */
656
+ const JournalTxnId = z
657
+ .string()
658
+ .regex(
659
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:(0|[1-9][0-9]{0,15})$/,
660
+ 'expected <uuid>:<non-negative integer>',
661
+ );
662
+
663
+ /**
664
+ * E31 `POST /v1/streams/:streamId/replicas/:replicaId/retirements` (cleo-nexus T123, contract v2.28): the replica
665
+ * writes no segment past `lastReplicaSeq` (none at all when it is null). The journal's own `retire` transaction
666
+ * travels E2E-encrypted; this is the server's signed copy, so it can refuse later appends and show the retirement in
667
+ * the reads.
668
+ */
669
+ export const RetireReplicaRequest = z.object({
670
+ /** The signing device. It must be the calling device. */
671
+ deviceId: DeviceId,
672
+ /** The replica that carries on this one's history, or null. Never the retired replica itself. */
673
+ successor: ReplicaId.nullable(),
674
+ /**
675
+ * The replica's last segment: at least its highest appended replicaSeq, and late segments up to it still land. Null
676
+ * when the replica wrote nothing: then no segment of it is stored, and none may land.
677
+ */
678
+ lastReplicaSeq: z.number().int().nonnegative().nullable(),
679
+ /**
680
+ * The journal transaction that carries the retire (`${emittingReplicaId}:${localTxnSeq}`), or null for an owner's
681
+ * retirement with no journal transaction.
682
+ */
683
+ txnId: JournalTxnId.nullable(),
684
+ /** Ed25519 by the calling device over replicaRetireMessage(...). */
685
+ signature: Base64,
686
+ });
687
+ /** The E31 request body. */
688
+ export type RetireReplicaRequest = z.infer<typeof RetireReplicaRequest>;
689
+
690
+ /** A stored retirement: one per (stream, replica), insert-only. */
691
+ export const ReplicaRetirement = z.object({
692
+ streamId: StreamId,
693
+ replicaId: ReplicaId,
694
+ successor: ReplicaId.nullable(),
695
+ /** Null: the replica wrote nothing. */
696
+ lastReplicaSeq: z.number().int().nonnegative().nullable(),
697
+ /** The device that signed it: the replica's own, a project owner's, or (home streams) another of the account's. */
698
+ signerDeviceId: DeviceId,
699
+ txnId: JournalTxnId.nullable(),
700
+ /** The signer's Ed25519 over replicaRetireMessage(...), so a client or an auditor can verify the record. */
701
+ signature: Base64,
702
+ retiredAt: z.iso.datetime(),
703
+ });
704
+ /** A stored replica retirement. */
705
+ export type ReplicaRetirement = z.infer<typeof ReplicaRetirement>;
706
+
707
+ /** 201 for a new retirement, 200 with the stored one for a repeat with the same successor and lastReplicaSeq. */
708
+ export const RetireReplicaResult = z.object({ retirement: ReplicaRetirement });
709
+ /** The E31 answer. */
710
+ export type RetireReplicaResult = z.infer<typeof RetireReplicaResult>;
711
+
588
712
  // ---------- keys (wrapped project keys; the account master key is escrowed on the server, T082: encryption at rest, not end-to-end) ----------
589
713
 
590
714
  /**
@@ -9,7 +9,7 @@
9
9
  * @since SG-ARCH-SOLID Saga T9831 · E-CLI-BOUNDARY T9833 · T10061
10
10
  */
11
11
 
12
- import { DOCS_LIFECYCLE_STATUSES } from '../operations/docs.js';
12
+ import { DOCS_LIFECYCLE_STATUSES } from '../operations/docs-lifecycle.js';
13
13
  import { OUTPUT_CONTRACTS } from '../operations/output-contracts-data.js';
14
14
  import { tasksUpdateInputContract } from '../operations/tasks.js';
15
15
  import type { CanonicalDomain, Gateway, OperationDef, ParamDef, Tier } from './operation-def.js';
package/src/doctor.ts CHANGED
@@ -1124,8 +1124,71 @@ export interface SupersededStoreConflict {
1124
1124
  targetTable: string;
1125
1125
  /** Legacy rows left uncopied. */
1126
1126
  rows: number;
1127
- /** Why they were left. */
1128
- reason: 'live-authoritative' | 'collides-with-live';
1127
+ /**
1128
+ * Why they were left. `id-collision-undecided` (full mode, T13172): a live
1129
+ * task holds the legacy task's id and one of the two creation times does
1130
+ * not parse, so the run cannot tell whether they are the same task; it
1131
+ * neither renumbers nor copies the legacy task. `withheld-undecided`: a
1132
+ * task-graph table such a run did not copy at all, so the undecided task's
1133
+ * children, dependencies and criteria cannot attach to the live task.
1134
+ */
1135
+ reason:
1136
+ | 'live-authoritative'
1137
+ | 'collides-with-live'
1138
+ | 'id-collision-undecided'
1139
+ | 'withheld-undecided';
1140
+ /** The legacy ids left uncopied, when the run can name them. */
1141
+ ids?: string[];
1142
+ }
1143
+
1144
+ /**
1145
+ * A legacy task recovered under a fresh id because a DIFFERENT live task holds
1146
+ * its id (a task written into the empty store after a deferred exodus, T13172).
1147
+ * The renumbering happens in a scratch copy of the legacy file, with every
1148
+ * reference to the task re-pointed; the legacy file and live rows are untouched.
1149
+ *
1150
+ * @task T13172
1151
+ */
1152
+ export interface SupersededStoreIdRemap {
1153
+ /** Logical legacy source. */
1154
+ sourceDb: string;
1155
+ /** The legacy task's id, which the live store uses for another task. */
1156
+ legacyId: string;
1157
+ /** The id the legacy task is recovered under. */
1158
+ newId: string;
1159
+ /** The legacy task's title. */
1160
+ legacyTitle: string;
1161
+ /** Title of the live task that holds `legacyId`. */
1162
+ liveTitle: string;
1163
+ /** `true` when a previous run already recovered it as `newId` (nothing new is copied). */
1164
+ alreadyRecovered: boolean;
1165
+ /** Legacy rows re-pointed from `legacyId` to `newId` (children, dependencies, criteria, …). */
1166
+ referencesRepointed: number;
1167
+ /**
1168
+ * The legacy task's creation time and type, which a later run (and
1169
+ * `cleo show <legacy id>`) checks against the recovered task before trusting
1170
+ * this record (T13183). Absent in receipts written before T13183.
1171
+ */
1172
+ legacyCreatedAt?: string;
1173
+ legacyType?: string | null;
1174
+ }
1175
+
1176
+ /**
1177
+ * One bare legacy table of the live store a reconcile carried: its row count
1178
+ * and key digest when the run verified (T13319). The run records it in the
1179
+ * store (T13320) and in its receipt; the sync refusal for stranded bare rows
1180
+ * stands down for a table that still matches, so a row the runtime later
1181
+ * deletes from the prefixed twin is never mistaken for one never carried.
1182
+ *
1183
+ * @task T13319
1184
+ */
1185
+ export interface SupersededStoreBareAccount {
1186
+ /** The bare table, e.g. `task_labels`. */
1187
+ table: string;
1188
+ /** Its rows when the run carried it. */
1189
+ rows: number;
1190
+ /** sha256 of its primary-key values in order (every column if keyless). */
1191
+ digest: string;
1129
1192
  }
1130
1193
 
1131
1194
  /**
@@ -1161,8 +1224,17 @@ export interface SupersededStoreReconcileResult {
1161
1224
  * touch the live task graph, and list everything left in `conflicts`.
1162
1225
  */
1163
1226
  mode: 'full' | 'additive';
1164
- /** Legacy rows deliberately left uncopied (additive mode); empty in full mode. */
1227
+ /**
1228
+ * Legacy rows deliberately left uncopied: additive mode's live-authoritative
1229
+ * tables, and in full mode the task-id collisions the run cannot decide.
1230
+ */
1165
1231
  conflicts: SupersededStoreConflict[];
1232
+ /**
1233
+ * Legacy tasks whose id a different live task holds, recovered (full mode)
1234
+ * or to be recovered (dry-run) under a new id. Empty in additive mode, which
1235
+ * never writes the task graph.
1236
+ */
1237
+ remaps: SupersededStoreIdRemap[];
1166
1238
  /** `true` when nothing was written. */
1167
1239
  dryRun: boolean;
1168
1240
  /** Absolute project root. */
@@ -1185,4 +1257,11 @@ export interface SupersededStoreReconcileResult {
1185
1257
  receiptPath: string | null;
1186
1258
  /** Human-readable explanation, suitable for printing verbatim. */
1187
1259
  reason: string;
1260
+ /**
1261
+ * The live store's bare tables this run carried, on a `reconciled` receipt
1262
+ * that copied the bare family (T13319). Audit only: the sync check reads the
1263
+ * record the run wrote into the store itself, which travels with backups
1264
+ * (T13320).
1265
+ */
1266
+ accounted?: SupersededStoreBareAccount[];
1188
1267
  }
package/src/index.ts CHANGED
@@ -514,7 +514,9 @@ export type {
514
514
  SagaAuditResult,
515
515
  SagaAuditViolation,
516
516
  SagaAuditViolationKind,
517
+ SupersededStoreBareAccount,
517
518
  SupersededStoreConflict,
519
+ SupersededStoreIdRemap,
518
520
  SupersededStoreReconcileOutcome,
519
521
  SupersededStoreReconcileResult,
520
522
  SupersededStoreTableCount,
@@ -2281,6 +2283,7 @@ export type {
2281
2283
  HeavyHeapSource,
2282
2284
  HeavyLeverChange,
2283
2285
  HeavyToolResourcePlan,
2286
+ MemoryPressureReading,
2284
2287
  ResourceClass,
2285
2288
  ResourceDeferral,
2286
2289
  ResourceGrant,
@@ -2320,7 +2323,13 @@ export type {
2320
2323
  // === Row Identity Types (T12341 — uid merge keys) ===
2321
2324
  export type {
2322
2325
  RowIdentityKind,
2326
+ RowIdentityNexusAnswer,
2323
2327
  RowIdentityRef,
2328
+ RowIdentityRefillCounts,
2329
+ RowIdentityRefillReport,
2330
+ RowIdentityShareSignal,
2331
+ RowIdentityShareSignalCode,
2332
+ RowIdentityShareState,
2324
2333
  RowIdentitySpec,
2325
2334
  StoredRefUid,
2326
2335
  SymmetricEdge,
@@ -2461,6 +2470,14 @@ export {
2461
2470
  // Terminal state sets
2462
2471
  TERMINAL_TASK_STATUSES,
2463
2472
  } from './status-registry.js';
2473
+ // === Store restore (T13240 — restore cleo.db from a named snapshot or backup id) ===
2474
+ export type {
2475
+ StoreRecoverResult,
2476
+ StoreRestoreKept,
2477
+ StoreRestoreResult,
2478
+ StoreRestoreSource,
2479
+ StoreRestoreVerification,
2480
+ } from './store-restore.js';
2464
2481
  // === Sub-Accessor Contracts (T9188) ===
2465
2482
  export type {
2466
2483
  AgentRegistrySubAccessor,
@@ -3049,12 +3066,21 @@ export type {
3049
3066
  CloudPaging,
3050
3067
  CloudProjectShowResult,
3051
3068
  CloudProjectsResult,
3069
+ CloudRetiredReplica,
3052
3070
  CloudStatusGlobalStore,
3071
+ CloudStatusHolder,
3053
3072
  CloudStatusLocal,
3054
3073
  CloudStatusOfflineDetails,
3055
3074
  CloudStatusResult,
3056
3075
  CloudStatusSummary,
3076
+ CloudStatusSync,
3077
+ CloudStatusSyncStream,
3057
3078
  CloudStatusVerdict,
3079
+ CloudSyncFact,
3080
+ CloudSyncFlags,
3081
+ CloudSyncKnown,
3082
+ CloudSyncLag,
3083
+ CloudSyncUnknown,
3058
3084
  CloudWarning,
3059
3085
  CloudWhoamiResult,
3060
3086
  NexusCloudCredential,
@@ -3078,6 +3104,7 @@ export {
3078
3104
  NEXUS_DEVICE_LIST_STATES,
3079
3105
  NEXUS_DEVICE_STATE_UNKNOWN,
3080
3106
  NEXUS_DEVICE_STATES,
3107
+ NEXUS_FEATURE_PROJECT_INITIAL_KEY,
3081
3108
  NEXUS_FORBIDDEN_ERRORS,
3082
3109
  NEXUS_FORBIDDEN_REASONS,
3083
3110
  NEXUS_PAGE_LIMIT_MAX,
@@ -3120,6 +3147,9 @@ export {
3120
3147
  export {
3121
3148
  type CloudActivityItem,
3122
3149
  type CloudActivityResult,
3150
+ type CloudConflict,
3151
+ type CloudConflictResolveResult,
3152
+ type CloudConflictsResult,
3123
3153
  type CloudLeaseReleaseResult,
3124
3154
  type CloudPushResult,
3125
3155
  type CloudRestoreResult,
@@ -3129,9 +3159,12 @@ export {
3129
3159
  type CloudVaultStatusResult,
3130
3160
  type CloudVaultTable,
3131
3161
  type CloudVaultTableDiff,
3162
+ type CloudVerifyDeepCheck,
3132
3163
  type CloudVerifyResult,
3164
+ NEXUS_REPLICA_RETIREMENT_REASONS,
3133
3165
  NEXUS_VAULT_LEASE_ROLE,
3134
3166
  type NexusActivityEvent,
3167
+ type NexusReplicaRetirementReason,
3135
3168
  nexusActivityEventSchema,
3136
3169
  nexusActivityPageSchema,
3137
3170
  nexusLeaseAcquireSchema,