@cleocode/contracts 2026.6.12 → 2026.6.14

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 (216) hide show
  1. package/dist/__tests__/operation-def.test.js +5 -1
  2. package/dist/__tests__/operation-def.test.js.map +1 -1
  3. package/dist/__tests__/provider-def.test.d.ts +12 -0
  4. package/dist/__tests__/provider-def.test.d.ts.map +1 -0
  5. package/dist/__tests__/provider-def.test.js +84 -0
  6. package/dist/__tests__/provider-def.test.js.map +1 -0
  7. package/dist/__tests__/sealed-credential.test.d.ts +19 -0
  8. package/dist/__tests__/sealed-credential.test.d.ts.map +1 -0
  9. package/dist/__tests__/sealed-credential.test.js +74 -0
  10. package/dist/__tests__/sealed-credential.test.js.map +1 -0
  11. package/dist/__tests__/service-provider.test.d.ts +17 -0
  12. package/dist/__tests__/service-provider.test.d.ts.map +1 -0
  13. package/dist/__tests__/service-provider.test.js +108 -0
  14. package/dist/__tests__/service-provider.test.js.map +1 -0
  15. package/dist/__tests__/skill-executor-contract.test.js +3 -0
  16. package/dist/__tests__/skill-executor-contract.test.js.map +1 -1
  17. package/dist/acceptance-gate.d.ts +2 -3
  18. package/dist/acceptance-gate.d.ts.map +1 -1
  19. package/dist/boundary.d.ts +52 -0
  20. package/dist/boundary.d.ts.map +1 -1
  21. package/dist/boundary.js +24 -19
  22. package/dist/boundary.js.map +1 -1
  23. package/dist/conduit.d.ts +171 -0
  24. package/dist/conduit.d.ts.map +1 -1
  25. package/dist/config/index.d.ts +23 -0
  26. package/dist/config/index.d.ts.map +1 -0
  27. package/dist/config/index.js +22 -0
  28. package/dist/config/index.js.map +1 -0
  29. package/dist/config.d.ts +104 -1
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/data-accessor.d.ts +21 -1
  32. package/dist/data-accessor.d.ts.map +1 -1
  33. package/dist/data-accessor.js.map +1 -1
  34. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts +21 -0
  35. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts.map +1 -0
  36. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +84 -0
  37. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -0
  38. package/dist/dispatch/identity.d.ts +1 -1
  39. package/dist/dispatch/identity.d.ts.map +1 -1
  40. package/dist/dispatch/identity.js +25 -0
  41. package/dist/dispatch/identity.js.map +1 -1
  42. package/dist/dispatch/operation-def.d.ts +20 -0
  43. package/dist/dispatch/operation-def.d.ts.map +1 -1
  44. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  45. package/dist/dispatch/operations-registry.js +1044 -182
  46. package/dist/dispatch/operations-registry.js.map +1 -1
  47. package/dist/docs/read.d.ts +2 -2
  48. package/dist/index.d.ts +25 -11
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +17 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/lease-ipc/__tests__/freeze.test.d.ts +15 -0
  53. package/dist/lease-ipc/__tests__/freeze.test.d.ts.map +1 -0
  54. package/dist/lease-ipc/__tests__/freeze.test.js +313 -0
  55. package/dist/lease-ipc/__tests__/freeze.test.js.map +1 -0
  56. package/dist/lease-ipc/index.d.ts +17 -0
  57. package/dist/lease-ipc/index.d.ts.map +1 -0
  58. package/dist/lease-ipc/index.js +16 -0
  59. package/dist/lease-ipc/index.js.map +1 -0
  60. package/dist/lease-ipc/messages.d.ts +918 -0
  61. package/dist/lease-ipc/messages.d.ts.map +1 -0
  62. package/dist/lease-ipc/messages.js +476 -0
  63. package/dist/lease-ipc/messages.js.map +1 -0
  64. package/dist/lease-ipc/version.d.ts +41 -0
  65. package/dist/lease-ipc/version.d.ts.map +1 -0
  66. package/dist/lease-ipc/version.js +43 -0
  67. package/dist/lease-ipc/version.js.map +1 -0
  68. package/dist/llm/catalog-schema.d.ts +242 -0
  69. package/dist/llm/catalog-schema.d.ts.map +1 -0
  70. package/dist/llm/catalog-schema.js +153 -0
  71. package/dist/llm/catalog-schema.js.map +1 -0
  72. package/dist/llm/catalog-schema.json +139 -0
  73. package/dist/llm/index.d.ts +15 -0
  74. package/dist/llm/index.d.ts.map +1 -0
  75. package/dist/llm/index.js +14 -0
  76. package/dist/llm/index.js.map +1 -0
  77. package/dist/llm/interfaces.d.ts +23 -0
  78. package/dist/llm/interfaces.d.ts.map +1 -1
  79. package/dist/llm/oauth.d.ts +16 -2
  80. package/dist/llm/oauth.d.ts.map +1 -1
  81. package/dist/llm/onboarding.d.ts +126 -0
  82. package/dist/llm/onboarding.d.ts.map +1 -0
  83. package/dist/llm/onboarding.js +34 -0
  84. package/dist/llm/onboarding.js.map +1 -0
  85. package/dist/llm/provider-def.d.ts +254 -0
  86. package/dist/llm/provider-def.d.ts.map +1 -0
  87. package/dist/llm/provider-def.js +93 -0
  88. package/dist/llm/provider-def.js.map +1 -0
  89. package/dist/llm/sealed-credential.d.ts +164 -0
  90. package/dist/llm/sealed-credential.d.ts.map +1 -0
  91. package/dist/llm/sealed-credential.js +43 -0
  92. package/dist/llm/sealed-credential.js.map +1 -0
  93. package/dist/llm/system-of-use.d.ts +383 -0
  94. package/dist/llm/system-of-use.d.ts.map +1 -0
  95. package/dist/llm/system-of-use.js +238 -0
  96. package/dist/llm/system-of-use.js.map +1 -0
  97. package/dist/llm/system-resolver.d.ts +87 -11
  98. package/dist/llm/system-resolver.d.ts.map +1 -1
  99. package/dist/llm/system-resolver.js +14 -7
  100. package/dist/llm/system-resolver.js.map +1 -1
  101. package/dist/operations/__tests__/ensures-schema-registry.test.d.ts +17 -0
  102. package/dist/operations/__tests__/ensures-schema-registry.test.d.ts.map +1 -0
  103. package/dist/operations/__tests__/ensures-schema-registry.test.js +113 -0
  104. package/dist/operations/__tests__/ensures-schema-registry.test.js.map +1 -0
  105. package/dist/operations/admin.d.ts +124 -1
  106. package/dist/operations/admin.d.ts.map +1 -1
  107. package/dist/operations/ensures-schema-registry.d.ts +172 -0
  108. package/dist/operations/ensures-schema-registry.d.ts.map +1 -0
  109. package/dist/operations/ensures-schema-registry.js +226 -0
  110. package/dist/operations/ensures-schema-registry.js.map +1 -0
  111. package/dist/operations/entities.d.ts +110 -0
  112. package/dist/operations/entities.d.ts.map +1 -0
  113. package/dist/operations/entities.js +695 -0
  114. package/dist/operations/entities.js.map +1 -0
  115. package/dist/operations/llm.d.ts +116 -9
  116. package/dist/operations/llm.d.ts.map +1 -1
  117. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  118. package/dist/operations/output-contracts-data.js +230 -0
  119. package/dist/operations/output-contracts-data.js.map +1 -1
  120. package/dist/operations/service.d.ts +68 -0
  121. package/dist/operations/service.d.ts.map +1 -0
  122. package/dist/operations/service.js +336 -0
  123. package/dist/operations/service.js.map +1 -0
  124. package/dist/operations/tasks.d.ts +109 -0
  125. package/dist/operations/tasks.d.ts.map +1 -1
  126. package/dist/operations/tasks.js.map +1 -1
  127. package/dist/operations/validate.d.ts +1 -12
  128. package/dist/operations/validate.d.ts.map +1 -1
  129. package/dist/operations/validate.js +1 -1
  130. package/dist/playbook.d.ts +116 -0
  131. package/dist/playbook.d.ts.map +1 -1
  132. package/dist/release/plan.d.ts +5 -5
  133. package/dist/session.d.ts +11 -0
  134. package/dist/session.d.ts.map +1 -1
  135. package/dist/session.js.map +1 -1
  136. package/dist/templates/manifest.d.ts +1 -1
  137. package/dist/tools/agent-tools.d.ts +231 -0
  138. package/dist/tools/agent-tools.d.ts.map +1 -0
  139. package/dist/tools/agent-tools.js +25 -0
  140. package/dist/tools/agent-tools.js.map +1 -0
  141. package/dist/tools/atomic.d.ts +19 -0
  142. package/dist/tools/atomic.d.ts.map +1 -1
  143. package/dist/tools/atomic.js +17 -0
  144. package/dist/tools/atomic.js.map +1 -1
  145. package/dist/tools/index.d.ts +2 -0
  146. package/dist/tools/index.d.ts.map +1 -1
  147. package/dist/tools/skill-executor.d.ts +7 -0
  148. package/dist/tools/skill-executor.d.ts.map +1 -1
  149. package/dist/tools/web-tools.d.ts +217 -0
  150. package/dist/tools/web-tools.d.ts.map +1 -0
  151. package/dist/tools/web-tools.js +31 -0
  152. package/dist/tools/web-tools.js.map +1 -0
  153. package/dist/vault/service-provider.d.ts +343 -0
  154. package/dist/vault/service-provider.d.ts.map +1 -0
  155. package/dist/vault/service-provider.js +857 -0
  156. package/dist/vault/service-provider.js.map +1 -0
  157. package/dist/workgraph.d.ts +12 -12
  158. package/package.json +14 -2
  159. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  160. package/src/__tests__/operation-def.test.ts +7 -1
  161. package/src/__tests__/provider-def.test.ts +93 -0
  162. package/src/__tests__/sealed-credential.test.ts +100 -0
  163. package/src/__tests__/service-provider.test.ts +129 -0
  164. package/src/__tests__/skill-executor-contract.test.ts +3 -0
  165. package/src/acceptance-gate.ts +2 -3
  166. package/src/boundary.ts +70 -20
  167. package/src/conduit.ts +200 -0
  168. package/src/config/index.ts +35 -0
  169. package/src/config.ts +106 -1
  170. package/src/data-accessor.ts +22 -1
  171. package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +99 -0
  172. package/src/dispatch/identity.ts +25 -0
  173. package/src/dispatch/operation-def.ts +21 -0
  174. package/src/dispatch/operations-registry.ts +1083 -176
  175. package/src/index.ts +240 -25
  176. package/src/lease-ipc/__tests__/freeze.test.ts +342 -0
  177. package/src/lease-ipc/index.ts +75 -0
  178. package/src/lease-ipc/messages.ts +588 -0
  179. package/src/lease-ipc/version.ts +46 -0
  180. package/src/llm/catalog-schema.json +139 -0
  181. package/src/llm/catalog-schema.ts +184 -0
  182. package/src/llm/index.ts +29 -0
  183. package/src/llm/interfaces.ts +23 -0
  184. package/src/llm/oauth.ts +16 -2
  185. package/src/llm/onboarding.ts +131 -0
  186. package/src/llm/provider-def.ts +291 -0
  187. package/src/llm/sealed-credential.ts +166 -0
  188. package/src/llm/system-of-use.ts +503 -0
  189. package/src/llm/system-resolver.ts +91 -11
  190. package/src/operations/__tests__/ensures-schema-registry.test.ts +169 -0
  191. package/src/operations/admin.ts +144 -0
  192. package/src/operations/ensures-schema-registry.ts +260 -0
  193. package/src/operations/entities.ts +758 -0
  194. package/src/operations/llm.ts +120 -8
  195. package/src/operations/output-contracts-data.ts +264 -0
  196. package/src/operations/service.ts +360 -0
  197. package/src/operations/tasks.ts +119 -0
  198. package/src/operations/validate.ts +1 -14
  199. package/src/playbook.ts +117 -0
  200. package/src/session.ts +11 -0
  201. package/src/tools/agent-tools.ts +270 -0
  202. package/src/tools/atomic.ts +21 -0
  203. package/src/tools/index.ts +46 -0
  204. package/src/tools/skill-executor.ts +7 -0
  205. package/src/tools/web-tools.ts +247 -0
  206. package/src/vault/service-provider.ts +1137 -0
  207. package/dist/tessera.d.ts +0 -32
  208. package/dist/tessera.d.ts.map +0 -1
  209. package/dist/tessera.js +0 -11
  210. package/dist/tessera.js.map +0 -1
  211. package/dist/warp-chain.d.ts +0 -129
  212. package/dist/warp-chain.d.ts.map +0 -1
  213. package/dist/warp-chain.js +0 -13
  214. package/dist/warp-chain.js.map +0 -1
  215. package/src/tessera.ts +0 -35
  216. package/src/warp-chain.ts +0 -185
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Service-vault CLI operation contracts — INPUT + OUTPUT schemas (T11941).
3
+ *
4
+ * The schema-first I/O contracts for the four user-facing service-vault CLI
5
+ * verbs (`cleo service connect|list|revoke|status`). They live HERE in the
6
+ * `contracts` leaf package — alongside `tasks.ts` and the OUTPUT-contracts
7
+ * registry — so every consumer (CLI `--describe`, the SDK `describeOperation`,
8
+ * REST clients) resolves the I/O shape against ONE source of truth.
9
+ *
10
+ * AC4 (T11941): each new op carries an INPUT and an OUTPUT schema and appears in
11
+ * the output-contract data. `connect`/`list`/`revoke`/`status` are registered in
12
+ * `INPUT_CONTRACTS` (core) and `OUTPUT_CONTRACTS` (this package) so
13
+ * `cleo service <verb> --describe` resolves a non-null contract pair.
14
+ *
15
+ * ## Secrets never surfaced
16
+ *
17
+ * Every OUTPUT schema describes a NON-SECRET view: `list`/`status` carry
18
+ * `provider`/`label`/`status`/`scopes`/`expiresAt`/`hasCredentials` only —
19
+ * NEVER the decrypted token. `connect` returns the non-secret connection
20
+ * identity. `revoke` returns the `{count, deleted, grantsRemoved}` cascade
21
+ * report. There is no `--field` pointer that resolves to a token, by design.
22
+ *
23
+ * @packageDocumentation
24
+ * @module @cleocode/contracts/operations/service
25
+ *
26
+ * @epic T11765
27
+ * @saga T10409
28
+ * @task T11941 — M2-W4 service CLI verbs
29
+ */
30
+
31
+ import type { JsonSchema, OperationInputContract } from './input-contract.js';
32
+ import type { OperationOutputContract } from './output-contract.js';
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // service.connect — store a service credential (token-direct or paste-code)
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Accepted input shape for `service.connect`.
40
+ *
41
+ * Two non-interactive modes (v1, browser-free + testable):
42
+ * - token-direct: `provider` + `token` (+ optional `refreshToken`, `expiresAt`,
43
+ * `scopes`) — stores the token blob `encryptGlobal`-encrypted.
44
+ * - paste-code: `provider` + `code` + `codeVerifier` + `redirectUri` — runs the
45
+ * OAuth `exchangeCode` dance (the interactive flow is a thin wrapper over it).
46
+ *
47
+ * `additionalProperties: false` so a typo'd flag is rejected loudly (DHQ-033).
48
+ *
49
+ * @task T11941
50
+ */
51
+ export const SERVICE_CONNECT_INPUT_SCHEMA: JsonSchema = {
52
+ type: 'object',
53
+ required: ['provider'],
54
+ additionalProperties: false,
55
+ properties: {
56
+ provider: {
57
+ type: 'string',
58
+ minLength: 1,
59
+ description: 'Service provider key (e.g. github, google, notion).',
60
+ },
61
+ label: {
62
+ type: 'string',
63
+ description: "Connection label, unique within the provider (default: 'default').",
64
+ },
65
+ token: {
66
+ type: 'string',
67
+ description:
68
+ 'Direct access token to store (token-direct mode). SECRET — never logged/echoed.',
69
+ },
70
+ refreshToken: {
71
+ type: 'string',
72
+ description: 'Optional refresh token paired with --token. SECRET.',
73
+ },
74
+ expiresAt: {
75
+ type: 'string',
76
+ description: 'ISO-8601 access-token expiry (token-direct mode).',
77
+ },
78
+ scopes: {
79
+ type: 'array',
80
+ items: { type: 'string' },
81
+ description: 'Granted scope list (non-secret).',
82
+ },
83
+ code: {
84
+ type: 'string',
85
+ description: 'OAuth authorization code from the redirect callback (paste-code mode).',
86
+ },
87
+ codeVerifier: {
88
+ type: 'string',
89
+ description: 'PKCE code verifier from service.auth-url, round-tripped (paste-code mode).',
90
+ },
91
+ redirectUri: {
92
+ type: 'string',
93
+ description: 'Redirect URI used in service.auth-url; must match (paste-code mode).',
94
+ },
95
+ },
96
+ };
97
+
98
+ /** Schema-first INPUT contract for `service.connect`. */
99
+ export const serviceConnectInputContract: OperationInputContract<Record<string, unknown>> = {
100
+ operation: 'service.connect',
101
+ schema: SERVICE_CONNECT_INPUT_SCHEMA,
102
+ examples: [
103
+ {
104
+ name: 'token-direct',
105
+ value: { provider: 'github', token: 'gho_xxx', label: 'personal' },
106
+ description: 'Store an already-obtained access token (browser-free v1).',
107
+ },
108
+ {
109
+ name: 'paste-code',
110
+ value: {
111
+ provider: 'github',
112
+ code: 'auth-code',
113
+ codeVerifier: 'pkce-verifier',
114
+ redirectUri: 'http://127.0.0.1:0/callback',
115
+ },
116
+ description: 'Exchange an OAuth code + PKCE verifier for tokens.',
117
+ },
118
+ ],
119
+ };
120
+
121
+ /** OUTPUT contract for `service.connect` — the non-secret mutate envelope. */
122
+ export const serviceConnectOutputContract: OperationOutputContract = {
123
+ operation: 'service.connect',
124
+ shapeNote:
125
+ 'Mutate envelope: count=1, created=[connectionId]. `connection` carries the NON-SECRET identity (provider/label/expiresAt). No token is ever returned.',
126
+ dataSchema: {
127
+ type: 'object',
128
+ required: ['count', 'created'],
129
+ additionalProperties: true,
130
+ properties: {
131
+ count: { type: 'number', description: 'Number of connections created/updated (1).' },
132
+ created: {
133
+ type: 'array',
134
+ items: { type: 'string' },
135
+ description: 'The connection id(s) created/updated, as strings.',
136
+ },
137
+ connection: {
138
+ type: 'object',
139
+ description: 'Non-secret connection identity.',
140
+ properties: {
141
+ connectionId: { type: 'number' },
142
+ provider: { type: 'string' },
143
+ label: { type: 'string' },
144
+ expiresAt: { type: ['string', 'null'] },
145
+ },
146
+ },
147
+ },
148
+ },
149
+ fieldPointers: [
150
+ '/data/count',
151
+ '/data/created/0',
152
+ '/data/connection/provider',
153
+ '/data/connection/label',
154
+ '/data/connection/expiresAt',
155
+ ],
156
+ };
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // service.list — redacted connection views
160
+ // ---------------------------------------------------------------------------
161
+
162
+ /** Accepted input shape for `service.list`. */
163
+ export const SERVICE_LIST_INPUT_SCHEMA: JsonSchema = {
164
+ type: 'object',
165
+ required: [],
166
+ additionalProperties: false,
167
+ properties: {
168
+ provider: {
169
+ type: 'string',
170
+ description: 'Optional provider filter; lists all providers when omitted.',
171
+ },
172
+ },
173
+ };
174
+
175
+ /** Schema-first INPUT contract for `service.list`. */
176
+ export const serviceListInputContract: OperationInputContract<Record<string, unknown>> = {
177
+ operation: 'service.list',
178
+ schema: SERVICE_LIST_INPUT_SCHEMA,
179
+ examples: [
180
+ { name: 'all', value: {}, description: 'List every service connection (redacted).' },
181
+ { name: 'one-provider', value: { provider: 'github' }, description: 'Filter to one provider.' },
182
+ ],
183
+ };
184
+
185
+ /** The redacted per-connection view schema (shared by list + status). */
186
+ const SERVICE_CONNECTION_VIEW_SCHEMA: JsonSchema = {
187
+ type: 'object',
188
+ required: ['provider', 'label', 'status', 'scopes', 'hasCredentials'],
189
+ additionalProperties: true,
190
+ properties: {
191
+ id: { type: 'number' },
192
+ provider: { type: 'string' },
193
+ label: { type: 'string' },
194
+ status: { type: 'string', enum: ['active', 'expired', 'revoked'] },
195
+ scopes: { type: 'array', items: { type: 'string' } },
196
+ expiresAt: { type: ['string', 'null'] },
197
+ connectedAt: { type: 'string' },
198
+ updatedAt: { type: 'string' },
199
+ hasCredentials: {
200
+ type: 'boolean',
201
+ description: 'Whether a credential blob is present — NEVER the token itself.',
202
+ },
203
+ },
204
+ };
205
+
206
+ /** OUTPUT contract for `service.list` — redacted connection views. */
207
+ export const serviceListOutputContract: OperationOutputContract = {
208
+ operation: 'service.list',
209
+ shapeNote:
210
+ 'connections[] are NON-SECRET views — provider/label/status/scopes/expiresAt/hasCredentials only. The decrypted token is NEVER present in any field.',
211
+ dataSchema: {
212
+ type: 'object',
213
+ required: ['connections'],
214
+ additionalProperties: true,
215
+ properties: {
216
+ connections: {
217
+ type: 'array',
218
+ items: SERVICE_CONNECTION_VIEW_SCHEMA,
219
+ description: 'Redacted connection views.',
220
+ },
221
+ },
222
+ },
223
+ fieldPointers: [
224
+ '/data/connections',
225
+ '/data/connections/0/provider',
226
+ '/data/connections/0/label',
227
+ '/data/connections/0/status',
228
+ '/data/connections/0/expiresAt',
229
+ '/data/connections/0/hasCredentials',
230
+ ],
231
+ };
232
+
233
+ // ---------------------------------------------------------------------------
234
+ // service.revoke — hard delete + cascade grants
235
+ // ---------------------------------------------------------------------------
236
+
237
+ /** Accepted input shape for `service.revoke`. */
238
+ export const SERVICE_REVOKE_INPUT_SCHEMA: JsonSchema = {
239
+ type: 'object',
240
+ required: ['provider', 'label'],
241
+ additionalProperties: false,
242
+ properties: {
243
+ provider: { type: 'string', minLength: 1, description: 'Service provider key.' },
244
+ label: { type: 'string', minLength: 1, description: 'Connection label to revoke.' },
245
+ },
246
+ };
247
+
248
+ /** Schema-first INPUT contract for `service.revoke`. */
249
+ export const serviceRevokeInputContract: OperationInputContract<Record<string, unknown>> = {
250
+ operation: 'service.revoke',
251
+ schema: SERVICE_REVOKE_INPUT_SCHEMA,
252
+ examples: [
253
+ {
254
+ name: 'revoke',
255
+ value: { provider: 'github', label: 'personal' },
256
+ description: 'Delete the connection and cascade its agent grants.',
257
+ },
258
+ ],
259
+ };
260
+
261
+ /** OUTPUT contract for `service.revoke` — the cascade-delete report. */
262
+ export const serviceRevokeOutputContract: OperationOutputContract = {
263
+ operation: 'service.revoke',
264
+ shapeNote:
265
+ 'Mutate envelope: count=1 + deleted=[provider:label] on success (0 + [] when no such connection). `grantsRemoved` counts the cascaded agent_service_grants rows.',
266
+ dataSchema: {
267
+ type: 'object',
268
+ required: ['count', 'deleted'],
269
+ additionalProperties: true,
270
+ properties: {
271
+ count: { type: 'number', description: '1 when a connection was deleted, else 0.' },
272
+ deleted: {
273
+ type: 'array',
274
+ items: { type: 'string' },
275
+ description: 'The `provider:label` identifier(s) deleted.',
276
+ },
277
+ grantsRemoved: {
278
+ type: 'number',
279
+ description: 'How many agent_service_grants rows were cascaded-deleted.',
280
+ },
281
+ },
282
+ },
283
+ fieldPointers: ['/data/count', '/data/deleted/0', '/data/grantsRemoved'],
284
+ };
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // service.status — connection health (expired? needs refresh?)
288
+ // ---------------------------------------------------------------------------
289
+
290
+ /** Accepted input shape for `service.status`. */
291
+ export const SERVICE_STATUS_INPUT_SCHEMA: JsonSchema = {
292
+ type: 'object',
293
+ required: [],
294
+ additionalProperties: false,
295
+ properties: {
296
+ provider: {
297
+ type: 'string',
298
+ description: 'Optional provider filter; reports all connections when omitted.',
299
+ },
300
+ },
301
+ };
302
+
303
+ /** Schema-first INPUT contract for `service.status`. */
304
+ export const serviceStatusInputContract: OperationInputContract<Record<string, unknown>> = {
305
+ operation: 'service.status',
306
+ schema: SERVICE_STATUS_INPUT_SCHEMA,
307
+ examples: [
308
+ { name: 'all', value: {}, description: 'Health of every connection.' },
309
+ {
310
+ name: 'one-provider',
311
+ value: { provider: 'github' },
312
+ description: 'Health for one provider.',
313
+ },
314
+ ],
315
+ };
316
+
317
+ /** OUTPUT contract for `service.status` — per-connection health. */
318
+ export const serviceStatusOutputContract: OperationOutputContract = {
319
+ operation: 'service.status',
320
+ shapeNote:
321
+ 'connections[] are NON-SECRET health views: each adds `expired` + `needsRefresh` booleans computed from expiresAt. No token is ever present.',
322
+ dataSchema: {
323
+ type: 'object',
324
+ required: ['connections'],
325
+ additionalProperties: true,
326
+ properties: {
327
+ connections: {
328
+ type: 'array',
329
+ items: {
330
+ type: 'object',
331
+ required: ['provider', 'label', 'status', 'expired', 'needsRefresh'],
332
+ additionalProperties: true,
333
+ properties: {
334
+ provider: { type: 'string' },
335
+ label: { type: 'string' },
336
+ status: { type: 'string', enum: ['active', 'expired', 'revoked'] },
337
+ expiresAt: { type: ['string', 'null'] },
338
+ expired: {
339
+ type: 'boolean',
340
+ description: 'True when expiresAt is in the past (or status=expired).',
341
+ },
342
+ needsRefresh: {
343
+ type: 'boolean',
344
+ description: 'True when the connection is expired and has stored credentials.',
345
+ },
346
+ hasCredentials: { type: 'boolean' },
347
+ },
348
+ },
349
+ },
350
+ },
351
+ },
352
+ fieldPointers: [
353
+ '/data/connections',
354
+ '/data/connections/0/provider',
355
+ '/data/connections/0/label',
356
+ '/data/connections/0/status',
357
+ '/data/connections/0/expired',
358
+ '/data/connections/0/needsRefresh',
359
+ ],
360
+ };
@@ -1078,6 +1078,113 @@ export interface TasksReorderDispatchResult {
1078
1078
  totalSiblings: number;
1079
1079
  }
1080
1080
 
1081
+ // ---------------------------------------------------------------------------
1082
+ // tasks.reorder-rank / tasks.bulk-move / tasks.assignee
1083
+ // (T11786 · epic T11556 · E1-GATEWAY-CRUD — bulk task mutate ops Studio's
1084
+ // interactive Kanban binds to)
1085
+ // ---------------------------------------------------------------------------
1086
+
1087
+ /**
1088
+ * Params for `tasks.reorder-rank` — explicit within-column positional ordering.
1089
+ *
1090
+ * Unlike `tasks.reorder` (which moves ONE task to a 1-based index and recomputes
1091
+ * every sibling), this op takes the FULL desired ordering of a sibling/column
1092
+ * scope as a task-ID array and writes each task's `position` to its index in the
1093
+ * array. This is the Kanban "drag a card and the whole column re-ranks" shape:
1094
+ * the client sends the new column order, the op persists it in one pass.
1095
+ *
1096
+ * @task T11786
1097
+ */
1098
+ export interface TasksReorderRankParams {
1099
+ /**
1100
+ * Ordered task IDs describing the desired top-to-bottom column order. Each
1101
+ * task's `position` is set to its (1-based) index in this array. All IDs
1102
+ * SHOULD share a sibling scope (same `parentId`); IDs not found are reported
1103
+ * in {@link TasksReorderRankResult.skipped} rather than failing the batch.
1104
+ */
1105
+ orderedIds: string[];
1106
+ }
1107
+ /**
1108
+ * Result of `tasks.reorder-rank` — within-column rank confirmation.
1109
+ *
1110
+ * @task T11786
1111
+ */
1112
+ export interface TasksReorderRankResult {
1113
+ /** Task IDs whose `position` was written, in the new top-to-bottom order. */
1114
+ ranked: string[];
1115
+ /** Task IDs from the request that did not resolve to a task (no-op for these). */
1116
+ skipped: string[];
1117
+ /** Number of tasks re-ranked (= `ranked.length`). */
1118
+ count: number;
1119
+ }
1120
+
1121
+ /**
1122
+ * Params for `tasks.bulk-move` — atomic status/stage transition for N tasks.
1123
+ *
1124
+ * Moves a set of tasks to a new `status` (and optionally `pipelineStage`) inside
1125
+ * a SINGLE DB transaction — the Kanban "select N cards, drop them in a new
1126
+ * column" shape. Either `status` or `pipelineStage` (or both) MUST be supplied;
1127
+ * an empty target is rejected. All-or-nothing: any failure rolls back every move.
1128
+ *
1129
+ * @task T11786
1130
+ */
1131
+ export interface TasksBulkMoveParams {
1132
+ /** Task IDs to move. Non-empty. */
1133
+ taskIds: string[];
1134
+ /** Target lifecycle status applied to every task. */
1135
+ status?: string;
1136
+ /** Target pipeline stage applied to every task (forward-only validation). */
1137
+ pipelineStage?: string;
1138
+ }
1139
+ /**
1140
+ * Result of `tasks.bulk-move` — atomic multi-move confirmation.
1141
+ *
1142
+ * @task T11786
1143
+ */
1144
+ export interface TasksBulkMoveResult {
1145
+ /** Task IDs successfully moved (all-or-nothing — empty only when count is 0). */
1146
+ moved: string[];
1147
+ /** The status applied, when supplied. */
1148
+ status?: string;
1149
+ /** The pipeline stage applied, when supplied. */
1150
+ pipelineStage?: string;
1151
+ /** Number of tasks moved (= `moved.length`). */
1152
+ count: number;
1153
+ }
1154
+
1155
+ /**
1156
+ * Params for `tasks.assignee` — set/clear a task's first-class assignee.
1157
+ *
1158
+ * A first-class human/owner assignee surface, DISTINCT from the agent
1159
+ * claim/unclaim lock (`tasks.claim` does a conditional atomic claim with
1160
+ * agent-lock semantics; `tasks.assignee` is a direct unconditional set/clear an
1161
+ * operator or Studio board uses). Pass `assignee: null` (or omit it) to clear.
1162
+ *
1163
+ * @task T11786
1164
+ */
1165
+ export interface TasksAssigneeParams {
1166
+ /** Task ID whose assignee is being set or cleared. */
1167
+ taskId: string;
1168
+ /**
1169
+ * The assignee to set. `null` / empty / omitted CLEARS the assignee. A
1170
+ * non-empty string assigns the task to that owner.
1171
+ */
1172
+ assignee?: string | null;
1173
+ }
1174
+ /**
1175
+ * Result of `tasks.assignee` — assignee set/clear confirmation.
1176
+ *
1177
+ * @task T11786
1178
+ */
1179
+ export interface TasksAssigneeResult {
1180
+ /** The task ID whose assignee changed. */
1181
+ taskId: string;
1182
+ /** The new assignee value (null when cleared). */
1183
+ assignee: string | null;
1184
+ /** True when an assignee was set; false when it was cleared. */
1185
+ assigned: boolean;
1186
+ }
1187
+
1081
1188
  // tasks.relates.add — relatedId is canonical; targetId kept for backward compat (T5149)
1082
1189
  export interface TasksRelatesAddParams {
1083
1190
  taskId: string;
@@ -1469,6 +1576,14 @@ export interface TasksCompleteQueryParams {
1469
1576
  * @saga T10538 (PM-Core V2 agent-trust)
1470
1577
  */
1471
1578
  cancelledChildWaiverReason?: string;
1579
+ /**
1580
+ * Reason for waiving the `E_CLEO_DEPENDENCY` gate when this task's own work
1581
+ * is done but its `depends` edges point at not-yet-terminal tasks (a stale or
1582
+ * over-specified dependency). Recorded to `.cleo/audit/depends-waiver.jsonl`.
1583
+ *
1584
+ * @task T11954 (DHQ-071)
1585
+ */
1586
+ waiveDependsReason?: string;
1472
1587
  }
1473
1588
  /**
1474
1589
  * Result of `tasks.complete` — completion confirmation with unblocked tasks.
@@ -2004,6 +2119,10 @@ export type TasksOps = {
2004
2119
  readonly restore: readonly [TasksRestoreParams, TasksRestoreResult];
2005
2120
  readonly reparent: readonly [TasksReparentQueryParams, TasksReparentDispatchResult];
2006
2121
  readonly reorder: readonly [TasksReorderQueryParams, TasksReorderDispatchResult];
2122
+ // T11786 (epic T11556) — bulk task mutate ops Studio's interactive Kanban binds to.
2123
+ readonly 'reorder-rank': readonly [TasksReorderRankParams, TasksReorderRankResult];
2124
+ readonly 'bulk-move': readonly [TasksBulkMoveParams, TasksBulkMoveResult];
2125
+ readonly assignee: readonly [TasksAssigneeParams, TasksAssigneeResult];
2007
2126
  readonly 'relates.add': readonly [TasksRelatesAddParams, TasksRelatesAddResult];
2008
2127
  readonly 'relates.add-batch': readonly [TasksRelatesAddBatchParams, TasksRelatesAddBatchResult];
2009
2128
  readonly 'relates.remove': readonly [TasksRelatesRemoveParams, TasksRelatesRemoveResult];
@@ -2,11 +2,9 @@
2
2
  * Validate / Check Domain Operations
3
3
  *
4
4
  * T982 extension: Added new types for gate, archive, coherence, compliance-sync,
5
- * chain, grade, canon, workflow-compliance, verify-explain, and all protocol subtypes.
5
+ * grade, canon, workflow-compliance, verify-explain, and all protocol subtypes.
6
6
  */
7
7
 
8
- import type { WarpChain } from '../warp-chain.js';
9
-
10
8
  export type ValidationSeverity = 'error' | 'warning' | 'info';
11
9
 
12
10
  export interface ValidationViolation {
@@ -242,16 +240,6 @@ export interface ValidateArchiveStatsParams {
242
240
  }
243
241
  export type ValidateArchiveStatsResult = Record<string, unknown>;
244
242
 
245
- export interface ValidateChainParams {
246
- chain: WarpChain;
247
- }
248
- export interface ValidateChainResult {
249
- wellFormed: boolean;
250
- gateSatisfiable: boolean;
251
- errors: string[];
252
- warnings: string[];
253
- }
254
-
255
243
  export interface ValidateGradeParams {
256
244
  sessionId: string;
257
245
  }
@@ -379,7 +367,6 @@ export type CheckOps = {
379
367
  readonly 'gate.set': readonly [ValidateGateParams, ValidateGateResult];
380
368
  readonly 'verify.explain': readonly [ValidateVerifyExplainParams, ValidateVerifyExplainResult];
381
369
  readonly 'archive.stats': readonly [ValidateArchiveStatsParams, ValidateArchiveStatsResult];
382
- readonly 'chain.validate': readonly [ValidateChainParams, ValidateChainResult];
383
370
  readonly grade: readonly [ValidateGradeParams, ValidateGradeResult];
384
371
  readonly 'grade.list': readonly [ValidateGradeListParams, ValidateGradeListResult];
385
372
  readonly canon: readonly [ValidateCanonParams, ValidateCanonResult];
package/src/playbook.ts CHANGED
@@ -58,6 +58,62 @@ export interface PlaybookAgenticNode extends PlaybookNodeBase {
58
58
  * Enforced at spawn time by composeSpawnPayload (T1261 PSYCHE E4).
59
59
  */
60
60
  context_files?: string[];
61
+ /**
62
+ * Pin this stage's LLM to a named profile from `llm.profiles` (T11759 · M4).
63
+ *
64
+ * When present, the dispatcher resolves the stage's LLM through the E9
65
+ * chokepoint (`resolveLLMForSystem`) keyed by the cantbook node identity
66
+ * (`cantbook:<playbook>#<nodeId>`) with this profile name threaded as the
67
+ * pin — so the named profile's provider/model/credential win over the
68
+ * background role default. Additive: a node without `profile`/`model`/
69
+ * `provider` resolves exactly as before.
70
+ */
71
+ profile?: string;
72
+ /**
73
+ * Pin this stage's LLM to an explicit model id (T11759 · M4).
74
+ *
75
+ * A lower-precedence escape hatch than {@link profile}: when no `profile` is
76
+ * declared the dispatcher threads this model id as the resolution hint. The
77
+ * runtime never constructs a transport from it — it is a resolution hint
78
+ * consumed by the Pi runner / model-runner downstream (Gate-13).
79
+ */
80
+ model?: string;
81
+ /**
82
+ * Pin this stage's LLM to an explicit provider transport (T11759 · M4).
83
+ *
84
+ * Paired with {@link model} as the inline escape hatch when no {@link profile}
85
+ * is declared. A resolution hint only — never used to build a client here.
86
+ */
87
+ provider?: string;
88
+ /**
89
+ * Per-step skill allowlist enforced at spawn time (T1947 · M4 cantbook
90
+ * done-gate, Gap E/F).
91
+ *
92
+ * When present, the spawned agent's effective skill set is the INTERSECTION
93
+ * of the agent's resolved skills (the Tier-0/1/2 baseline) and this list — a
94
+ * skill not in both is dropped. The allowlist can only RESTRICT the baseline,
95
+ * never expand it: a skill named here that the agent does not already carry is
96
+ * NOT added. Additive: a node without `allowed_skills` spawns with its full
97
+ * baseline skill set, exactly as before.
98
+ *
99
+ * Enforced by `composeSpawnPayload` (it intersects `resolvedAgent.skills` with
100
+ * this list). An empty set after intersection is logged as a warning and the
101
+ * spawn proceeds with no skills.
102
+ */
103
+ allowed_skills?: string[];
104
+ /**
105
+ * Per-step tool allowlist enforced at spawn time (T1947 · M4 cantbook
106
+ * done-gate, Gap E/F).
107
+ *
108
+ * When present, the spawned agent's effective tool set is the INTERSECTION of
109
+ * the agent's resolved tool baseline and this list — a tool not in both is
110
+ * dropped. Like {@link allowed_skills}, this can only RESTRICT the baseline,
111
+ * never expand it. Additive: a node without `allowed_tools` is unaffected.
112
+ *
113
+ * Enforced by `composeSpawnPayload` AFTER the thin-agent guard, so a worker
114
+ * still cannot acquire a spawn-capable tool it was never granted.
115
+ */
116
+ allowed_tools?: string[];
61
117
  }
62
118
 
63
119
  export interface PlaybookDeterministicNode extends PlaybookNodeBase {
@@ -77,6 +133,55 @@ export interface PlaybookApprovalNode extends PlaybookNodeBase {
77
133
 
78
134
  export type PlaybookNode = PlaybookAgenticNode | PlaybookDeterministicNode | PlaybookApprovalNode;
79
135
 
136
+ /**
137
+ * Declarative, eval-free predicate over the playbook run `context` used to
138
+ * guard a conditional branch edge (T11806).
139
+ *
140
+ * The predicate is intentionally a *pure data* description — never an
141
+ * arbitrary expression — so the runtime stays deterministic and sandbox-safe.
142
+ * The named `field` is resolved against the accumulated run context
143
+ * (`context[field]`) and compared by exactly one of the optional operators
144
+ * below. When a `PlaybookEdge` carries a `when` predicate, the runtime only
145
+ * traverses that edge if the predicate evaluates to `true`.
146
+ *
147
+ * Exactly one comparison operator (`equals`, `notEquals`, `exists`, `in`, or
148
+ * `truthy`) should be supplied; the parser enforces this so an ambiguous
149
+ * predicate is rejected at parse time rather than mis-routing at runtime.
150
+ *
151
+ * @example
152
+ * ```yaml
153
+ * edges:
154
+ * - from: classify
155
+ * to: deep-research # taken when context.needsResearch === true
156
+ * when: { field: needsResearch, equals: true }
157
+ * - from: classify
158
+ * to: skip-research # taken when context.needsResearch !== true
159
+ * when: { field: needsResearch, notEquals: true }
160
+ * ```
161
+ *
162
+ * @task T11806 — cantbook runtime branching
163
+ */
164
+ export interface PlaybookEdgeCondition {
165
+ /** Context key whose value the predicate is evaluated against. */
166
+ field: string;
167
+ /** Edge is satisfied when `context[field]` strictly equals this value. */
168
+ equals?: unknown;
169
+ /** Edge is satisfied when `context[field]` strictly does NOT equal this value. */
170
+ notEquals?: unknown;
171
+ /**
172
+ * When `true`, edge is satisfied iff `field` is present in context;
173
+ * when `false`, satisfied iff `field` is absent from context.
174
+ */
175
+ exists?: boolean;
176
+ /** Edge is satisfied when `context[field]` is one of these values. */
177
+ in?: unknown[];
178
+ /**
179
+ * When `true`, edge is satisfied iff `context[field]` is truthy;
180
+ * when `false`, satisfied iff `context[field]` is falsy.
181
+ */
182
+ truthy?: boolean;
183
+ }
184
+
80
185
  export interface PlaybookEdge {
81
186
  from: string;
82
187
  to: string;
@@ -84,6 +189,18 @@ export interface PlaybookEdge {
84
189
  requires?: string[];
85
190
  ensures?: string[];
86
191
  };
192
+ /**
193
+ * Optional declarative branch guard (T11806). When present, the edge is
194
+ * only traversed if the {@link PlaybookEdgeCondition} predicate over the
195
+ * run `context` is satisfied. Edges without `when` are unconditional.
196
+ *
197
+ * A node may have multiple outgoing edges as long as every additional
198
+ * edge carries a `when` guard — the runtime evaluates them against the
199
+ * current context and routes to the single satisfied successor. More than
200
+ * one *unconditional* successor remains a fail-closed error (genuine
201
+ * ambiguity).
202
+ */
203
+ when?: PlaybookEdgeCondition;
87
204
  }
88
205
 
89
206
  export interface PlaybookErrorHandler {
package/src/session.ts CHANGED
@@ -121,6 +121,17 @@ export interface Session {
121
121
  previousSessionId?: string | null;
122
122
  /** ID of the session that follows this one. @defaultValue undefined */
123
123
  nextSessionId?: string | null;
124
+ /**
125
+ * ID of the fork-tree PARENT session that spawned this one (T11639).
126
+ *
127
+ * Sourced from `CLEO_PARENT_SESSION_ID` (stamped by the supervisor — T11629).
128
+ * Distinct from {@link Session.previousSessionId} (the linear resume chain):
129
+ * `parentSessionId` is the orchestrator→worker spawn edge. NULL/undefined for a
130
+ * root session with no spawning parent.
131
+ *
132
+ * @defaultValue undefined
133
+ */
134
+ parentSessionId?: string | null;
124
135
  /** Provider-specific agent identifier string. @defaultValue undefined */
125
136
  agentIdentifier?: string | null;
126
137
  /** ISO 8601 timestamp of when the handoff was consumed. @defaultValue undefined */