@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
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Guided first run of `cleo login nexus` (T13102, owner decision 2026-10-02:
3
+ * onboarding = A + B + C' + D, so that `cleo login` is the only setup step in
4
+ * practice).
5
+ *
6
+ * After a successful sign-in, login looks at where it runs:
7
+ *
8
+ * - Inside a CLEO project this machine has not linked: it offers to link the
9
+ * project and take the first encrypted backup. `--yes` does it without asking,
10
+ * a terminal asks, and a non-interactive (agent) run never asks: it reports
11
+ * the exact next command. When Cleo Nexus already holds a backup of the
12
+ * project that this copy never synced (a fresh clone on a new machine), the
13
+ * offer is to restore that backup here instead; a backup would be refused.
14
+ * - Outside a CLEO project: it lists the account's projects, by name, with
15
+ * the exact `cleo cloud restore` command for each one that has a backup
16
+ * this machine does not hold yet.
17
+ *
18
+ * `cleo cloud restore <name>` resolves a project name or label (or an id) to
19
+ * the server's project id; an ambiguous name lists the candidates.
20
+ *
21
+ * This file is types + const data only (arch gate 10).
22
+ *
23
+ * @task T13102
24
+ * @epic T12322
25
+ */
26
+
27
+ import type { NexusProjectLink } from './nexus-account.js';
28
+ import type { CloudWarning } from './nexus-cloud.js';
29
+ import type { CloudVaultSnapshot } from './nexus-vault.js';
30
+
31
+ /**
32
+ * How the guided first run ended.
33
+ *
34
+ * - `backed-up`: the project was linked (or already linked) and its first backup pushed
35
+ * (or the cloud already held this exact state).
36
+ * - `link-failed`: linking failed; nothing was pushed.
37
+ * - `backup-failed`: the project is linked, but the backup failed.
38
+ * - `restored`: the cloud held a backup this copy never synced; it was restored here and linked.
39
+ * - `restore-failed`: restoring that backup failed (for example, local rows it would overwrite).
40
+ * - `offered`: a non-interactive run inside an unlinked project; nothing was done and
41
+ * `nextCommand` says what to run (link and back up, or restore; see `offer`).
42
+ * - `declined`: the user answered no at the prompt; `nextCommand` says what to run later.
43
+ * - `already-linked`: the project is linked and attached from this device; nothing to do.
44
+ * - `projects`: outside a CLEO project; `projects` lists the account's projects.
45
+ * - `skipped`: the first run does not apply (`reason` says why).
46
+ */
47
+ export const NEXUS_FIRST_RUN_STATES = [
48
+ 'backed-up',
49
+ 'link-failed',
50
+ 'backup-failed',
51
+ 'restored',
52
+ 'restore-failed',
53
+ 'offered',
54
+ 'declined',
55
+ 'already-linked',
56
+ 'projects',
57
+ 'skipped',
58
+ ] as const;
59
+
60
+ /** One of {@link NEXUS_FIRST_RUN_STATES}. */
61
+ export type NexusFirstRunState = (typeof NEXUS_FIRST_RUN_STATES)[number];
62
+
63
+ /** Where a project's display name came from. */
64
+ export type NexusProjectNameSource = 'encrypted-name' | 'label' | 'id';
65
+
66
+ /** One of the account's projects, as the first run and `cleo cloud restore <name>` see it. */
67
+ export interface NexusNamedProject {
68
+ /** The server's project id (the tracked local project id of whoever linked it). */
69
+ projectId: string;
70
+ /**
71
+ * Display name: the plaintext label, else the id. (`encryptedName` is shown only through a
72
+ * reader for its format, which cleo-nexus T098 will specify; none is wired in yet.)
73
+ */
74
+ name: string;
75
+ /** Where {@link NexusNamedProject.name} came from. */
76
+ nameSource: NexusProjectNameSource;
77
+ /** The plaintext label the server holds, if any. */
78
+ label: string | null;
79
+ /** The owning organization's name, when the server sent it. */
80
+ organizationName: string | null;
81
+ /** The newest sync over every replica, when the server sent it. */
82
+ lastSyncAt: string | null;
83
+ /**
84
+ * The project's stream holds a snapshot to restore. `null` when the server did not say
85
+ * (an older server without `headCheckpointId` on the project list).
86
+ */
87
+ hasBackup: boolean | null;
88
+ /** A replica of the project is attached from this device. */
89
+ onThisDevice: boolean;
90
+ /**
91
+ * The exact command that restores it into the current directory, by its id (an id never
92
+ * changes; for agents and scripts). `null` when there is nothing to restore here (no
93
+ * backup, or it is already on this device).
94
+ */
95
+ restoreCommand: string | null;
96
+ /**
97
+ * The same restore by name, for a person to type: set only when the name resolves to this
98
+ * project alone (and cannot be read as a flag or an id), else `null`.
99
+ */
100
+ restoreByNameCommand: string | null;
101
+ }
102
+
103
+ /** The first backup the guided run pushed. */
104
+ export interface NexusFirstRunBackup {
105
+ /** `pushed`: a new snapshot; `up-to-date`: the cloud already held this state. */
106
+ status: 'pushed' | 'up-to-date';
107
+ /** The snapshot, when the server named one. */
108
+ snapshot: CloudVaultSnapshot | null;
109
+ }
110
+
111
+ /** The backup the guided run restored into the project (`restored` only). */
112
+ export interface NexusFirstRunRestore {
113
+ /** `restored`: the snapshot was activated; `up-to-date`: this copy already held it. */
114
+ status: 'restored' | 'up-to-date';
115
+ /** The snapshot, when the server named one. */
116
+ snapshot: CloudVaultSnapshot | null;
117
+ /** Tables verified by count and hash. */
118
+ tables: number;
119
+ /** The local safety backup taken first, if any. */
120
+ safetyBackup: string | null;
121
+ }
122
+
123
+ /**
124
+ * What the run offered inside an unlinked project: `link-and-backup` (the
125
+ * project is new to the cloud, or this copy already synced with it), or
126
+ * `restore` (the cloud holds a backup this copy never synced).
127
+ */
128
+ export type NexusFirstRunOffer = 'link-and-backup' | 'restore';
129
+
130
+ /** One option of a choice the first run leaves to the user. */
131
+ export interface NexusFirstRunChoice {
132
+ /** The exact command. */
133
+ command: string;
134
+ /** What it does, in one line. */
135
+ effect: string;
136
+ }
137
+
138
+ /** The guided first run's outcome, carried as `data.firstRun` of the `cleo login nexus` envelope. */
139
+ export interface NexusFirstRunResult {
140
+ /** How it ended. */
141
+ state: NexusFirstRunState;
142
+ /** Why it was skipped (`skipped` only), e.g. a read-only device. */
143
+ reason: string | null;
144
+ /** The CLEO project login ran in, or `null` outside one. */
145
+ projectRoot: string | null;
146
+ /** The project's link after the run, when it is linked. */
147
+ link: NexusProjectLink | null;
148
+ /** What was offered inside an unlinked project, or `null` elsewhere. */
149
+ offer: NexusFirstRunOffer | null;
150
+ /** The backup pushed (`backed-up` only). */
151
+ backup: NexusFirstRunBackup | null;
152
+ /** The backup restored (`restored` only). */
153
+ restore: NexusFirstRunRestore | null;
154
+ /** The account's projects (`projects` only), newest first. */
155
+ projects: NexusNamedProject[];
156
+ /** The exact command to run next, when there is one (never asks: agents run it). */
157
+ nextCommand: string | null;
158
+ /**
159
+ * When the next step is a choice only the user can make (`nextCommand` is `null`), each
160
+ * option as `{ command, effect }`; empty otherwise.
161
+ */
162
+ choices: NexusFirstRunChoice[];
163
+ /** Non-fatal problems met on the way (secret-free). */
164
+ warnings: CloudWarning[];
165
+ }
@@ -46,6 +46,38 @@ export const nexusProjectKeysSchema = z.looseObject({
46
46
  ),
47
47
  });
48
48
 
49
+ // ---------- wire: replica retirement refusals (cleo-nexus T123) ----------
50
+
51
+ /**
52
+ * The `details.reason` values E31 (`POST …/replicas/:replicaId/retirements`) and the calls it guards answer with
53
+ * (cleo-nexus #41, contract v2.28):
54
+ * - `not-pinned-or-owner` (403): the caller is neither the replica's pinned device nor, on a project stream, an
55
+ * owner's device (on a home stream, any device of the account may retire).
56
+ * - `retire-below-head` (409): `lastReplicaSeq` is under the replica's highest appended replicaSeq, or null while
57
+ * the replica has a segment.
58
+ * - `retired` (409): the replica is already retired with another successor or `lastReplicaSeq` (the same pair
59
+ * again answers 200 with the stored record).
60
+ * - `successor-other-user` (403): on a project stream, the successor is pinned to another user's device.
61
+ * - `successor-taken` (409): another user's retirement already names that successor.
62
+ * - `successor-retired` (409): the successor is itself retired (this also refuses a cycle).
63
+ * - `replica-retired` (409, `E_CONFLICT`, with `lastReplicaSeq` and `successor`): a segment append past the
64
+ * retirement, or a lease request, for a retired replica.
65
+ * The server's strings, not a cloud wire schema: kept here, beside the vault's other wire constants, so the
66
+ * `@cleocode/contracts/cloud` mirror stays byte-for-byte the server's.
67
+ */
68
+ export const NEXUS_REPLICA_RETIREMENT_REASONS = [
69
+ 'not-pinned-or-owner',
70
+ 'retire-below-head',
71
+ 'retired',
72
+ 'successor-other-user',
73
+ 'successor-taken',
74
+ 'successor-retired',
75
+ 'replica-retired',
76
+ ] as const;
77
+
78
+ /** One E31 refusal reason ({@link NEXUS_REPLICA_RETIREMENT_REASONS}). */
79
+ export type NexusReplicaRetirementReason = (typeof NEXUS_REPLICA_RETIREMENT_REASONS)[number];
80
+
49
81
  // ---------- wire: leases (cleo-nexus T083) ----------
50
82
 
51
83
  /** The lease role of the vault's single writer. */
@@ -195,15 +227,59 @@ export interface CloudRestoreResult {
195
227
  tables: number;
196
228
  /** Local safety backup taken before activation, if any. */
197
229
  safetyBackup: string | null;
230
+ /**
231
+ * The placed file is a new store instance, so the replica this store had is
232
+ * retired and a new one bound (journal spec §1.5; T13109). The server keeps
233
+ * the retired one as history until S4 announces its retirement. `null` when
234
+ * nothing was placed or the store had no replica yet (a first restore here).
235
+ *
236
+ * `reason` is `vault-restore` when the retired replica was this device's
237
+ * replica of the replaced file (recorded as a retire candidate unless the
238
+ * registry write failed), `file-identity` or `foreign-device` when the store
239
+ * was a copy or another device's (the carried replica stays live where it
240
+ * belongs; nothing to retire), and `null` when the rebind's registry write
241
+ * failed.
242
+ */
243
+ replica: {
244
+ retired: string;
245
+ current: string;
246
+ reason: 'vault-restore' | 'file-identity' | 'foreign-device' | null;
247
+ } | null;
198
248
  warnings: CloudWarning[];
199
249
  }
200
250
 
251
+ /**
252
+ * `cleo cloud verify --deep` (T13291): the cloud copy downloaded again and
253
+ * checked byte for byte. Local backups are checked by `cleo backup verify`.
254
+ */
255
+ export interface CloudVerifyDeepCheck {
256
+ /**
257
+ * The head snapshot and each device's newest snapshot: its bundle downloaded,
258
+ * checked against the size and sha256 its signed checkpoint records, and
259
+ * decrypted. `problem` says why a check failed; `null` when it passed.
260
+ */
261
+ snapshots: Array<{
262
+ checkpointId: string;
263
+ deviceId: string;
264
+ sizeBytes: number;
265
+ ok: boolean;
266
+ problem: string | null;
267
+ }>;
268
+ /**
269
+ * The journal segments after the head snapshot (from the start of the stream
270
+ * when there is none), each re-hashed, signature-checked and decrypted.
271
+ * `from` is the stream seq they start after; `checked` counts the segments
272
+ * that passed; `problem` is the first failure, `null` when none.
273
+ */
274
+ segments: { from: number; checked: number; ok: boolean; problem: string | null };
275
+ }
276
+
201
277
  /** `cleo cloud verify`. */
202
278
  export interface CloudVerifyResult {
203
279
  apiUrl: string;
204
280
  scope: CloudVaultScope;
205
281
  streamId: string;
206
- /** `match`: local equals the head snapshot; `ahead`: local changed since this machine's last snapshot; `behind`: the cloud has a newer snapshot; `diverged`: both; `empty`: no snapshot yet; `untrusted`: the head snapshot's signature does not verify against a trusted device key (nothing is compared with it). */
282
+ /** `match`: local equals the head snapshot; `ahead`: local changed since this machine's last snapshot; `behind`: the cloud has a newer snapshot; `diverged`: both; `empty`: no snapshot yet; `untrusted`: the head snapshot's signature does not verify against a trusted device key (nothing is compared with it), or with `--deep`, its bundle or a segment after it failed the byte check. */
207
283
  verdict: 'match' | 'ahead' | 'behind' | 'diverged' | 'empty' | 'untrusted';
208
284
  /** What to do about a verdict other than `match` (or a failed integrity check); `null` when nothing. */
209
285
  remedy: string | null;
@@ -222,6 +298,8 @@ export interface CloudVerifyResult {
222
298
  createdAt: string | null;
223
299
  matchesHead: boolean;
224
300
  }>;
301
+ /** The byte-level check of `--deep`; absent without it. */
302
+ deep?: CloudVerifyDeepCheck;
225
303
  warnings: CloudWarning[];
226
304
  }
227
305
 
@@ -278,3 +356,48 @@ export interface CloudActivityResult {
278
356
  nextBefore: string | null;
279
357
  warnings: CloudWarning[];
280
358
  }
359
+
360
+ /** One conflict an apply recorded (`cleo cloud conflicts`, T12344 PR-6). */
361
+ export interface CloudConflict {
362
+ id: number;
363
+ stream: string;
364
+ seq: number;
365
+ txnIdx: number;
366
+ /** Op index in the transaction; -1 for a whole-transaction (post-apply) conflict. */
367
+ opIdx: number;
368
+ /** `field`, `typed-rule`, `edit-vs-delete`, `delete-vs-edit`, `dangling-ref`, `guard`, `delete-with-live-children`, `uid-collision`, `post-apply` … */
369
+ kind: string;
370
+ table: string;
371
+ uid: string;
372
+ columns: string[];
373
+ /** The typed rule, guard message or invariant, when there is one. */
374
+ rule: string | null;
375
+ /** What happened to the incoming op: `incoming-applied`, `incoming-dropped`, `row-deleted`, `op-voided`. */
376
+ resolution: string;
377
+ opHlc: string;
378
+ localHlc: string | null;
379
+ /** The replica that wrote the op. */
380
+ origin: string;
381
+ createdAt: string;
382
+ resolvedAt: string | null;
383
+ }
384
+
385
+ /** `cleo cloud conflicts`: the store's recorded sync conflicts. */
386
+ export interface CloudConflictsResult {
387
+ scope: CloudVaultScope;
388
+ /** Unresolved conflicts in the store. */
389
+ open: number;
390
+ /** All conflicts in the store. */
391
+ total: number;
392
+ conflicts: CloudConflict[];
393
+ warnings: CloudWarning[];
394
+ }
395
+
396
+ /** `cleo cloud conflicts resolve <id>`. */
397
+ export interface CloudConflictResolveResult {
398
+ scope: CloudVaultScope;
399
+ id: number;
400
+ /** False when no open conflict has that id. */
401
+ resolved: boolean;
402
+ warnings: CloudWarning[];
403
+ }
@@ -108,6 +108,10 @@ export interface AdminBackupEntry {
108
108
  note?: string;
109
109
  /** File names captured in this backup. */
110
110
  files: string[];
111
+ /** Which store it is of: `project` (`.cleo/cleo.db`) or `global` (`<CLEO_HOME>/cleo.db`, T13245). */
112
+ scope?: 'project' | 'global';
113
+ /** What its store file holds: `tasks`, `brain`, `conduit` (project) or `global` (T13245). */
114
+ contains?: string[];
111
115
  }
112
116
 
113
117
  /**
@@ -1194,7 +1198,10 @@ export type AdminAdrShowResult = AdminAdrRecord;
1194
1198
  // ---------------------------------------------------------------------------
1195
1199
 
1196
1200
  /** Parameters for `admin.backup` query — none required. */
1197
- export type AdminBackupListParams = Record<string, never>;
1201
+ export interface AdminBackupListParams {
1202
+ /** `project`, `global` or `all` (default `all`; T13245). */
1203
+ scope?: 'project' | 'global' | 'all';
1204
+ }
1198
1205
 
1199
1206
  /** Result of `admin.backup` query. */
1200
1207
  export interface AdminBackupListResult {
@@ -1653,6 +1660,10 @@ export interface AdminBackupMutateParams {
1653
1660
  * project's live store (T12680). Without it such a restore is refused.
1654
1661
  */
1655
1662
  confirmOwnerStore?: boolean;
1663
+ /** Create: also back up the global store (`<CLEO_HOME>/cleo.db`, T13245). */
1664
+ includeGlobal?: boolean;
1665
+ /** Restore by id: `global` restores a backup of the global store (T13245). */
1666
+ scope?: 'project' | 'global';
1656
1667
  }
1657
1668
 
1658
1669
  /** Result of `admin.backup` mutate (create action). */
@@ -1667,6 +1678,8 @@ export interface AdminBackupCreateResult {
1667
1678
  type: string;
1668
1679
  /** Files captured in the backup. */
1669
1680
  files: string[];
1681
+ /** The global store's backup, when `includeGlobal` was set (T13245). */
1682
+ global?: AdminBackupCreateResult | null;
1670
1683
  }
1671
1684
 
1672
1685
  /** Result of `admin.backup` mutate (restore action — by ID). */
@@ -97,7 +97,7 @@ export interface ConduitStatusResult {
97
97
  }
98
98
 
99
99
  // --------------------------------------------------------------------------
100
- // conduit.peek → one-shot poll for new messages (ACKs as it reads)
100
+ // conduit.peek → one-shot poll for new messages (never acks or consumes, T13169)
101
101
  // --------------------------------------------------------------------------
102
102
 
103
103
  /** Parameters for `conduit.peek`. */
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Lifecycle statuses of a canonical doc.
3
+ *
4
+ * A leaf module with no schema imports: the operation registry
5
+ * (`dispatch/operations-registry.ts`) and the dispatch input sanitizer read
6
+ * these on every command, and `./docs.js` imports zod for its payload schemas.
7
+ * `./docs.js` re-exports both names.
8
+ *
9
+ * @task T10161
10
+ * @task T13126 - split out of operations/docs.ts
11
+ */
12
+
13
+ /**
14
+ * Allowed `lifecycle_status` values mirrored from the attachments-table enum
15
+ * (`ATTACHMENT_LIFECYCLE_STATUSES` in
16
+ * `packages/core/src/store/schema/attachments.ts`). Kept inline here so the
17
+ * contract surface stays self-contained — the dispatch handler narrows raw
18
+ * `--status` input against this set before touching the store.
19
+ *
20
+ * @task T10161 (Epic T10157 / Saga T9855)
21
+ */
22
+ export const DOCS_LIFECYCLE_STATUSES = [
23
+ 'draft',
24
+ 'proposed',
25
+ 'accepted',
26
+ 'superseded',
27
+ 'archived',
28
+ 'deprecated',
29
+ ] as const;
30
+
31
+ export type DocsLifecycleStatus = (typeof DOCS_LIFECYCLE_STATUSES)[number];
@@ -39,6 +39,7 @@ import type {
39
39
  OperationExecutionIdentity,
40
40
  } from '../jobs.js';
41
41
  import type { DocAttachmentObservationPayload } from '../memory/observe.js';
42
+ import type { DocsLifecycleStatus } from './docs-lifecycle.js';
42
43
 
43
44
  // ============================================================================
44
45
  // Shared Attachment Types (API wire format)
@@ -686,25 +687,9 @@ export interface DocsRemoveResult {
686
687
  // docs.update — UPDATE-in-place via slug (T10161)
687
688
  // --------------------------------------------------------------------------
688
689
 
689
- /**
690
- * Allowed `lifecycle_status` values mirrored from the attachments-table enum
691
- * (`ATTACHMENT_LIFECYCLE_STATUSES` in
692
- * `packages/core/src/store/schema/attachments.ts`). Kept inline here so the
693
- * contract surface stays self-contained — the dispatch handler narrows raw
694
- * `--status` input against this set before touching the store.
695
- *
696
- * @task T10161 (Epic T10157 / Saga T9855)
697
- */
698
- export const DOCS_LIFECYCLE_STATUSES = [
699
- 'draft',
700
- 'proposed',
701
- 'accepted',
702
- 'superseded',
703
- 'archived',
704
- 'deprecated',
705
- ] as const;
706
-
707
- export type DocsLifecycleStatus = (typeof DOCS_LIFECYCLE_STATUSES)[number];
690
+ // The lifecycle statuses live in `./docs-lifecycle.js`, a leaf without zod: the
691
+ // operation registry reads them on every dispatch (T13126).
692
+ export { DOCS_LIFECYCLE_STATUSES, type DocsLifecycleStatus } from './docs-lifecycle.js';
708
693
 
709
694
  /**
710
695
  * Parameters for `docs.update`.
@@ -90,6 +90,13 @@ export interface ProjectEvidenceContext {
90
90
  /** Job-name globs that must have run and succeeded for a code task's gate. */
91
91
  jobs?: { tests?: string[]; qa?: string[] };
92
92
  };
93
+ /**
94
+ * Repo-relative paths or globs (`path.matchesGlob`, `**` crosses directories)
95
+ * that never count as part of a change when evidence decides its scope —
96
+ * runtime state a tool writes into the checkout, e.g. `.opencode/goals/**`.
97
+ * CLEO's own installed hook files are always excluded (T13135, gh#1805).
98
+ */
99
+ scopeExcludes?: string[];
93
100
  }
94
101
 
95
102
  /**
@@ -126,10 +133,18 @@ export interface ProjectContext {
126
133
  command?: string;
127
134
  /**
128
135
  * Affected-scope test command template for `tool:test-affected` (D11150,
129
- * T12635): `{projects}`, `{filters}` and `{packages}` expand per affected
130
- * package, e.g. `pnpm exec vitest run {projects}`.
136
+ * T12635) and a scope-aware `tool:test` (T12959): `{projects}`,
137
+ * `{filters}`, `{workspaces}` and `{packages}` expand per affected
138
+ * package, e.g. `pnpm exec vitest run {projects}`. When absent, one is
139
+ * derived from a workspace-wide `command` where that is mechanical
140
+ * (`pnpm -r … test`, `npm … test --workspaces`, `turbo run test`; T13125).
131
141
  */
132
142
  affectedCommand?: string;
143
+ /**
144
+ * `false` makes `tool:test` always run the full suite instead of the
145
+ * affected scope (T12959).
146
+ */
147
+ preferAffected?: boolean;
133
148
  testFilePatterns?: string[];
134
149
  directories?: {
135
150
  unit?: string;
@@ -136,26 +136,30 @@ export const ghPrViewSchema = z
136
136
  export type GhPrViewPayload = z.infer<typeof ghPrViewSchema>;
137
137
 
138
138
  /**
139
- * Default required-workflow names enforced by `pr:<number>` validation.
140
- *
141
- * Mirrors the canonical branch-protection list documented in
142
- * `docs/release/branch-protection-setup.md`. Override via
143
- * {@link PR_REQUIRED_WORKFLOWS_ENV_VAR} when projects deviate (e.g. an
144
- * out-of-tree consumer with different gating workflows).
139
+ * cleocode's OWN required-check names: the `CI` aggregate of its ci.yml and the
140
+ * `Contracts Dep Lint` job, both real check names there (T13285; a test pins
141
+ * every name to a job or workflow in cleocode's `.github/workflows/`).
142
+ *
143
+ * This is cleocode's configuration, not a library default for other
144
+ * repositories. Since gh#1323 the evidence path (`resolveRequiredWorkflowsDetailed`)
145
+ * never applies it to another repository: with no env override, no
146
+ * `release.prRequiredWorkflows` in `.cleo/project-context.json` and no readable
147
+ * branch protection it returns tier `unknown` and refuses loudly. Only the
148
+ * synchronous `resolveRequiredWorkflows` still returns it as its last tier.
149
+ * cleocode itself resolves through its project-context entry. (`Lockfile Check`
150
+ * was dropped: no check ever reported under that name — its job is
151
+ * "Verify pnpm-lock.yaml consistency" — and CI now covers it, T13279.)
145
152
  *
146
153
  * @task T9764
154
+ * @task T13285
147
155
  */
148
- export const PR_REQUIRED_WORKFLOWS: readonly string[] = Object.freeze([
149
- 'CI',
150
- 'Lockfile Check',
151
- 'Contracts Dep Lint',
152
- ]);
156
+ export const PR_REQUIRED_WORKFLOWS: readonly string[] = Object.freeze(['CI', 'Contracts Dep Lint']);
153
157
 
154
158
  /**
155
159
  * Name of the env var that overrides {@link PR_REQUIRED_WORKFLOWS}.
156
160
  *
157
161
  * Format: comma-separated workflow names (e.g.
158
- * `"CI,Lockfile Check,MyExtraGate"`). Whitespace around commas is
162
+ * `"CI,Contracts Dep Lint,MyExtraGate"`). Whitespace around commas is
159
163
  * trimmed.
160
164
  *
161
165
  * @task T9764
@@ -88,6 +88,41 @@ export const RUN_DEFERRED_EXIT_CODE = 75;
88
88
  */
89
89
  export const RESOURCE_BACKPRESSURE_CODE = 'E_RESOURCE_BACKPRESSURE' as const;
90
90
 
91
+ /**
92
+ * The numbers behind a memory-pressure refusal of a heavy class (T13127).
93
+ *
94
+ * The memory gate refuses `test-run`, `scoped-build` and `full-build` while
95
+ * memory pressure is above {@link MemoryPressureReading.refuseAbove}; once it
96
+ * has refused, it admits again only when pressure falls to
97
+ * {@link MemoryPressureReading.resumeAtOrBelow} (hysteresis), so admission
98
+ * does not flap at the threshold.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const r: MemoryPressureReading = {
103
+ * score: 31, fullStall: 0, refuseAbove: 25, resumeAtOrBelow: 15, latched: false,
104
+ * memAvailableBytes: 21_000_000_000,
105
+ * summary: 'kernel level warn; 59% of RAM wired or compressed; swap 11.4 GiB used (24% of RAM)',
106
+ * };
107
+ * ```
108
+ */
109
+ export interface MemoryPressureReading {
110
+ /** Memory pressure on the PSI `some avg10` scale (0–100). */
111
+ readonly score: number;
112
+ /** Memory `full avg10` stall (0–100); 0 when the platform reports none. */
113
+ readonly fullStall: number;
114
+ /** Heavy classes are refused while {@link score} is above this. */
115
+ readonly refuseAbove: number;
116
+ /** A refusing gate admits again once {@link score} is at or below this. */
117
+ readonly resumeAtOrBelow: number;
118
+ /** `true` when the gate was already refusing, so the resume threshold applied. */
119
+ readonly latched: boolean;
120
+ /** Available memory in bytes, when the platform reports it. */
121
+ readonly memAvailableBytes: number | null;
122
+ /** The platform signals in one line (kernel level, swap, compressor, PSI). */
123
+ readonly summary: string;
124
+ }
125
+
91
126
  /**
92
127
  * Structured, retryable deferral returned when admission is denied. Never a
93
128
  * silent drop; callers back off `retryAfterMs` and re-request, or annotate the
@@ -102,6 +137,11 @@ export interface ResourceDeferral {
102
137
  readonly retryAfterMs: number;
103
138
  /** Human-readable reason (pressure state, budget, held count). */
104
139
  readonly reason: string;
140
+ /**
141
+ * Set when the deferral is a memory-pressure refusal of a heavy class rather
142
+ * than a full budget: the readings behind it (T13127).
143
+ */
144
+ readonly memoryPressure?: MemoryPressureReading;
105
145
  }
106
146
 
107
147
  /**
@@ -139,6 +179,66 @@ export function isResourceGrant(r: AdmissionResult): r is ResourceGrant {
139
179
  return r.deferred === false;
140
180
  }
141
181
 
182
+ /**
183
+ * Where the heap ceiling of a heavy tool run came from (T13122).
184
+ *
185
+ * - `default` — no heap in the caller's `NODE_OPTIONS`; CLEO's default applies.
186
+ * - `inherited` — the caller's `NODE_OPTIONS` heap fits the run's budget and is
187
+ * kept; the worker count shrinks so the run still fits.
188
+ * - `clamped` — the caller's `NODE_OPTIONS` heap exceeds the run's budget (a
189
+ * shell-profile export is not a per-project choice) and was lowered to it.
190
+ * - `override` — `CLEO_HEAVY_HEAP_MB` set it explicitly; never clamped.
191
+ */
192
+ export type HeavyHeapSource = 'default' | 'inherited' | 'clamped' | 'override';
193
+
194
+ /** One inherited environment value CLEO replaced because it exceeded the plan (T13122). */
195
+ export interface HeavyLeverChange {
196
+ /** Variable name, e.g. `VITEST_MAX_WORKERS` or `NODE_OPTIONS`. */
197
+ readonly name: string;
198
+ /** The inherited value, as the caller's environment carried it. */
199
+ readonly from: string;
200
+ /** The value the tool was spawned with. */
201
+ readonly to: string;
202
+ }
203
+
204
+ /**
205
+ * The resource plan a memory-bound tool (`test`, `build`, and since T13123
206
+ * `typecheck`, `lint`) was spawned with, and why (T13122). Reported by
207
+ * `cleo verify` (on the `tool` evidence atom and in a resource-kill message)
208
+ * and by `cleo run`, so an operator can see when an inherited value was
209
+ * clamped. A single-process tool's plan has one worker.
210
+ *
211
+ * The invariant it describes: `workspaceConcurrency × workers × heapMb` stays
212
+ * within `budgetMb`, unless an explicit `CLEO_HEAVY_*` override asked for more
213
+ * (`overBudget`).
214
+ */
215
+ export interface HeavyToolResourcePlan {
216
+ /** V8 old-space ceiling given to every Node process in the tool's tree, in MiB. */
217
+ readonly heapMb: number;
218
+ /** Where {@link heapMb} came from. */
219
+ readonly heapSource: HeavyHeapSource;
220
+ /** The heap the caller's `NODE_OPTIONS` asked for, in MiB; `null` when it set none. */
221
+ readonly inheritedHeapMb: number | null;
222
+ /** Worker count given to every runner that reads one (`VITEST_MAX_WORKERS`, `GOMAXPROCS`, …). */
223
+ readonly workers: number;
224
+ /** `plan` when derived from the heap and budget; `override` when `CLEO_HEAVY_WORKERS` set it. */
225
+ readonly workersSource: 'plan' | 'override';
226
+ /** Workspace packages allowed to run their script at once (`npm_config_workspace_concurrency`). */
227
+ readonly workspaceConcurrency: number;
228
+ /** Heap budget for the whole run, in MiB: the default worker count times the default heap. */
229
+ readonly budgetMb: number;
230
+ /** Total RAM the budget derives from, in MiB. */
231
+ readonly totalRamMb: number;
232
+ /** Inherited values replaced because they exceeded the plan. Empty when nothing was clamped. */
233
+ readonly clamped: readonly HeavyLeverChange[];
234
+ /** Inherited worker or concurrency values kept because they were within the plan, as `NAME=value`. */
235
+ readonly kept: readonly string[];
236
+ /** `true` when explicit `CLEO_HEAVY_*` overrides put the run over {@link budgetMb}. */
237
+ readonly overBudget: boolean;
238
+ /** One line naming the heap and worker count chosen and why. */
239
+ readonly summary: string;
240
+ }
241
+
142
242
  /** Explicit local user-manager connection for resource-controlled process launch. */
143
243
  export interface SystemdControlContext {
144
244
  /** Absolute existing runtime directory used only by the manager probe and launcher. */