@cleocode/contracts 2026.6.13 → 2026.6.15

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 (140) hide show
  1. package/dist/__tests__/operation-def.test.js +4 -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__/service-provider.test.d.ts +17 -0
  8. package/dist/__tests__/service-provider.test.d.ts.map +1 -0
  9. package/dist/__tests__/service-provider.test.js +108 -0
  10. package/dist/__tests__/service-provider.test.js.map +1 -0
  11. package/dist/boundary.d.ts +52 -0
  12. package/dist/boundary.d.ts.map +1 -1
  13. package/dist/boundary.js +23 -0
  14. package/dist/boundary.js.map +1 -1
  15. package/dist/branch-lock.d.ts +4 -0
  16. package/dist/branch-lock.d.ts.map +1 -1
  17. package/dist/branch-lock.js.map +1 -1
  18. package/dist/conduit.d.ts +171 -0
  19. package/dist/conduit.d.ts.map +1 -1
  20. package/dist/config/index.d.ts +23 -0
  21. package/dist/config/index.d.ts.map +1 -0
  22. package/dist/config/index.js +22 -0
  23. package/dist/config/index.js.map +1 -0
  24. package/dist/config.d.ts +109 -1
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts +21 -0
  27. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts.map +1 -0
  28. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +84 -0
  29. package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -0
  30. package/dist/dispatch/identity.d.ts +1 -1
  31. package/dist/dispatch/identity.d.ts.map +1 -1
  32. package/dist/dispatch/identity.js +20 -0
  33. package/dist/dispatch/identity.js.map +1 -1
  34. package/dist/dispatch/operation-def.d.ts +20 -0
  35. package/dist/dispatch/operation-def.d.ts.map +1 -1
  36. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  37. package/dist/dispatch/operations-registry.js +1030 -46
  38. package/dist/dispatch/operations-registry.js.map +1 -1
  39. package/dist/index.d.ts +17 -8
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +15 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/lease-ipc/__tests__/freeze.test.js +92 -0
  44. package/dist/lease-ipc/__tests__/freeze.test.js.map +1 -1
  45. package/dist/lease-ipc/index.d.ts +2 -2
  46. package/dist/lease-ipc/index.d.ts.map +1 -1
  47. package/dist/lease-ipc/index.js +1 -1
  48. package/dist/lease-ipc/index.js.map +1 -1
  49. package/dist/lease-ipc/messages.d.ts +167 -3
  50. package/dist/lease-ipc/messages.d.ts.map +1 -1
  51. package/dist/lease-ipc/messages.js +93 -0
  52. package/dist/lease-ipc/messages.js.map +1 -1
  53. package/dist/llm/catalog-schema.d.ts +242 -0
  54. package/dist/llm/catalog-schema.d.ts.map +1 -0
  55. package/dist/llm/catalog-schema.js +153 -0
  56. package/dist/llm/catalog-schema.js.map +1 -0
  57. package/dist/llm/catalog-schema.json +139 -0
  58. package/dist/llm/index.d.ts +15 -0
  59. package/dist/llm/index.d.ts.map +1 -0
  60. package/dist/llm/index.js +14 -0
  61. package/dist/llm/index.js.map +1 -0
  62. package/dist/llm/oauth.d.ts +16 -2
  63. package/dist/llm/oauth.d.ts.map +1 -1
  64. package/dist/llm/onboarding.d.ts +126 -0
  65. package/dist/llm/onboarding.d.ts.map +1 -0
  66. package/dist/llm/onboarding.js +34 -0
  67. package/dist/llm/onboarding.js.map +1 -0
  68. package/dist/llm/provider-def.d.ts +254 -0
  69. package/dist/llm/provider-def.d.ts.map +1 -0
  70. package/dist/llm/provider-def.js +93 -0
  71. package/dist/llm/provider-def.js.map +1 -0
  72. package/dist/llm/system-resolver.d.ts +42 -0
  73. package/dist/llm/system-resolver.d.ts.map +1 -1
  74. package/dist/operations/admin.d.ts +124 -1
  75. package/dist/operations/admin.d.ts.map +1 -1
  76. package/dist/operations/entities.d.ts +110 -0
  77. package/dist/operations/entities.d.ts.map +1 -0
  78. package/dist/operations/entities.js +695 -0
  79. package/dist/operations/entities.js.map +1 -0
  80. package/dist/operations/llm.d.ts +16 -1
  81. package/dist/operations/llm.d.ts.map +1 -1
  82. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  83. package/dist/operations/output-contracts-data.js +230 -0
  84. package/dist/operations/output-contracts-data.js.map +1 -1
  85. package/dist/operations/service.d.ts +68 -0
  86. package/dist/operations/service.d.ts.map +1 -0
  87. package/dist/operations/service.js +336 -0
  88. package/dist/operations/service.js.map +1 -0
  89. package/dist/operations/tasks.d.ts +109 -0
  90. package/dist/operations/tasks.d.ts.map +1 -1
  91. package/dist/operations/tasks.js.map +1 -1
  92. package/dist/operations/worktree.d.ts +11 -1
  93. package/dist/operations/worktree.d.ts.map +1 -1
  94. package/dist/playbook.d.ts +56 -0
  95. package/dist/playbook.d.ts.map +1 -1
  96. package/dist/release/plan.d.ts +5 -5
  97. package/dist/spawn.d.ts +70 -0
  98. package/dist/spawn.d.ts.map +1 -1
  99. package/dist/spawn.js +1 -0
  100. package/dist/spawn.js.map +1 -1
  101. package/dist/vault/service-provider.d.ts +343 -0
  102. package/dist/vault/service-provider.d.ts.map +1 -0
  103. package/dist/vault/service-provider.js +857 -0
  104. package/dist/vault/service-provider.js.map +1 -0
  105. package/dist/workgraph.d.ts +12 -12
  106. package/package.json +10 -2
  107. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  108. package/src/__tests__/operation-def.test.ts +6 -1
  109. package/src/__tests__/provider-def.test.ts +93 -0
  110. package/src/__tests__/service-provider.test.ts +129 -0
  111. package/src/boundary.ts +69 -0
  112. package/src/branch-lock.ts +4 -0
  113. package/src/conduit.ts +200 -0
  114. package/src/config/index.ts +35 -0
  115. package/src/config.ts +116 -1
  116. package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +99 -0
  117. package/src/dispatch/identity.ts +20 -0
  118. package/src/dispatch/operation-def.ts +21 -0
  119. package/src/dispatch/operations-registry.ts +1062 -36
  120. package/src/index.ts +147 -2
  121. package/src/lease-ipc/__tests__/freeze.test.ts +96 -0
  122. package/src/lease-ipc/index.ts +12 -0
  123. package/src/lease-ipc/messages.ts +117 -0
  124. package/src/llm/catalog-schema.json +139 -0
  125. package/src/llm/catalog-schema.ts +184 -0
  126. package/src/llm/index.ts +29 -0
  127. package/src/llm/oauth.ts +16 -2
  128. package/src/llm/onboarding.ts +131 -0
  129. package/src/llm/provider-def.ts +291 -0
  130. package/src/llm/system-resolver.ts +45 -0
  131. package/src/operations/admin.ts +144 -0
  132. package/src/operations/entities.ts +758 -0
  133. package/src/operations/llm.ts +16 -0
  134. package/src/operations/output-contracts-data.ts +264 -0
  135. package/src/operations/service.ts +360 -0
  136. package/src/operations/tasks.ts +119 -0
  137. package/src/operations/worktree.ts +11 -0
  138. package/src/playbook.ts +56 -0
  139. package/src/spawn.ts +79 -0
  140. package/src/vault/service-provider.ts +1137 -0
@@ -10,6 +10,8 @@
10
10
  */
11
11
 
12
12
  import { DOCS_LIFECYCLE_STATUSES } from '../operations/docs.js';
13
+ import { OUTPUT_CONTRACTS } from '../operations/output-contracts-data.js';
14
+ import { tasksUpdateInputContract } from '../operations/tasks.js';
13
15
  import type { CanonicalDomain, Gateway, OperationDef, ParamDef, Tier } from './operation-def.js';
14
16
 
15
17
  export type { CanonicalDomain, Gateway, OperationDef, ParamDef, Tier };
@@ -241,6 +243,41 @@ export const OPERATIONS: OperationDef[] = [
241
243
  },
242
244
  ] satisfies ParamDef[],
243
245
  },
246
+ // T11785 (epic T11556 · E1-GATEWAY-CRUD) — the FIRST streaming task op. Served
247
+ // as `GET /v1/tasks/subscribe` → a `text/event-stream` body of
248
+ // `GatewayStreamEvent` frames (task/board created/updated/deleted lifecycle
249
+ // events), scoped to an optional saga/parent root. Studio's interactive Kanban
250
+ // (T11929) and the Pi-TUI board consume it for live updates. The default
251
+ // source ships a bounded heartbeat producer (stream-sources.ts); the daemon
252
+ // injects its real origin-tailing source via `registerStreamSource`.
253
+ {
254
+ gateway: 'query',
255
+ domain: 'tasks',
256
+ operation: 'subscribe',
257
+ description:
258
+ 'tasks.subscribe (query, streaming) — SSE stream of task/board lifecycle events (created/updated/deleted), optionally scoped to a saga/parent root',
259
+ tier: 2,
260
+ idempotent: true,
261
+ sessionRequired: false,
262
+ streaming: true,
263
+ requiredParams: [],
264
+ params: [
265
+ {
266
+ name: 'root',
267
+ type: 'string',
268
+ required: false,
269
+ description:
270
+ 'Optional saga/parent task ID to scope the event stream to (omit for the whole board)',
271
+ },
272
+ {
273
+ name: 'ticks',
274
+ type: 'number',
275
+ required: false,
276
+ description:
277
+ 'Maximum number of event frames to emit before the terminal `done` frame (default: open-ended until client disconnect)',
278
+ },
279
+ ] satisfies ParamDef[],
280
+ },
244
281
  {
245
282
  gateway: 'query',
246
283
  domain: 'tasks',
@@ -790,6 +827,32 @@ export const OPERATIONS: OperationDef[] = [
790
827
  requiredParams: [],
791
828
  params: [],
792
829
  },
830
+ // T11921 — gateway SSE streaming on /v1. The agent/worker/board event stream
831
+ // the Studio Kanban (T11929) + Pi-TUI (T11936) consume as a realtime
832
+ // transport. Served as `GET /v1/orchestrate/events` → a `text/event-stream`
833
+ // body of `GatewayStreamEvent` frames terminated by a `done` frame, NOT as a
834
+ // unary POST. Secrets never touch the wire — the source resolves server-side.
835
+ {
836
+ gateway: 'query',
837
+ domain: 'orchestrate',
838
+ operation: 'events',
839
+ description:
840
+ 'orchestrate.events (query, streaming) — SSE stream of agent/worker/board lifecycle events',
841
+ tier: 2,
842
+ idempotent: true,
843
+ sessionRequired: false,
844
+ streaming: true,
845
+ requiredParams: [],
846
+ params: [
847
+ {
848
+ name: 'ticks',
849
+ type: 'number',
850
+ required: false,
851
+ description:
852
+ 'Maximum number of event frames to emit before the terminal `done` frame (default: open-ended until client disconnect)',
853
+ },
854
+ ] satisfies ParamDef[],
855
+ },
793
856
  // T811 — IVTR multi-agent enforcement
794
857
  {
795
858
  gateway: 'query',
@@ -1758,6 +1821,73 @@ export const OPERATIONS: OperationDef[] = [
1758
1821
  requiredParams: [],
1759
1822
  params: [],
1760
1823
  },
1824
+ {
1825
+ gateway: 'query',
1826
+ domain: 'admin',
1827
+ operation: 'config.get',
1828
+ description:
1829
+ 'admin.config.get (query) — resolve a single config value via the ConfigManifest cascade (T11917)',
1830
+ tier: 1,
1831
+ idempotent: true,
1832
+ sessionRequired: false,
1833
+ requiredParams: ['key'],
1834
+ params: [
1835
+ {
1836
+ name: 'key',
1837
+ type: 'string',
1838
+ required: true,
1839
+ description: 'Dot-notation config key to read (e.g. "release.branchModel")',
1840
+ cli: { positional: true },
1841
+ },
1842
+ {
1843
+ name: 'scope',
1844
+ type: 'string',
1845
+ required: false,
1846
+ description: 'Cascade slice to read from. Defaults to merged.',
1847
+ enum: ['global', 'project', 'merged'],
1848
+ },
1849
+ ] satisfies ParamDef[],
1850
+ },
1851
+ {
1852
+ gateway: 'query',
1853
+ domain: 'admin',
1854
+ operation: 'config.list',
1855
+ description:
1856
+ 'admin.config.list (query) — resolve the full config cascade and its flattened keys (T11917)',
1857
+ tier: 1,
1858
+ idempotent: true,
1859
+ sessionRequired: false,
1860
+ requiredParams: [],
1861
+ params: [
1862
+ {
1863
+ name: 'scope',
1864
+ type: 'string',
1865
+ required: false,
1866
+ description: 'Cascade slice to project. Defaults to merged.',
1867
+ enum: ['global', 'project', 'merged'],
1868
+ },
1869
+ ] satisfies ParamDef[],
1870
+ },
1871
+ {
1872
+ gateway: 'query',
1873
+ domain: 'admin',
1874
+ operation: 'config.validate',
1875
+ description:
1876
+ 'admin.config.validate (query) — schema-validate a scoped config file via its manifest entry (T11917)',
1877
+ tier: 1,
1878
+ idempotent: true,
1879
+ sessionRequired: false,
1880
+ requiredParams: [],
1881
+ params: [
1882
+ {
1883
+ name: 'scope',
1884
+ type: 'string',
1885
+ required: false,
1886
+ description: 'Scope to validate (global or project). Defaults to project.',
1887
+ enum: ['global', 'project'],
1888
+ },
1889
+ ] satisfies ParamDef[],
1890
+ },
1761
1891
  {
1762
1892
  gateway: 'query',
1763
1893
  domain: 'admin',
@@ -2439,10 +2569,18 @@ export const OPERATIONS: OperationDef[] = [
2439
2569
  ] satisfies ParamDef[],
2440
2570
  },
2441
2571
  {
2572
+ // T11784 (epic T11556 · E1-GATEWAY-CRUD) — the OperationDef.params used to
2573
+ // declare ONLY `taskId`, so every other mutable field a Studio form binds
2574
+ // to was invisible to dispatch introspection (`cleo update --describe`, the
2575
+ // SDK `describeOperation`) and silently dropped at the dispatch boundary.
2576
+ // The full mutable-field set is now enumerated here, grounded in
2577
+ // {@link TasksUpdateQueryParams} / {@link TASKS_UPDATE_INPUT_SCHEMA}; a
2578
+ // regression test (operations-registry tasks-update parity) asserts the
2579
+ // param names stay in sync with the schema's `properties`.
2442
2580
  gateway: 'mutate',
2443
2581
  domain: 'tasks',
2444
2582
  operation: 'update',
2445
- description: 'tasks.update (mutate)',
2583
+ description: 'tasks.update (mutate) — partial update; only supplied fields are mutated',
2446
2584
  tier: 0,
2447
2585
  idempotent: false,
2448
2586
  sessionRequired: false,
@@ -2452,10 +2590,183 @@ export const OPERATIONS: OperationDef[] = [
2452
2590
  name: 'taskId',
2453
2591
  type: 'string',
2454
2592
  required: true,
2455
- description: 'taskId parameter',
2593
+ description: 'Task ID to update',
2456
2594
  cli: { positional: true },
2457
2595
  },
2596
+ { name: 'title', type: 'string', required: false, description: 'New title (3–500 chars)' },
2597
+ {
2598
+ name: 'description',
2599
+ type: 'string',
2600
+ required: false,
2601
+ description: 'New description',
2602
+ cli: { flag: 'description', short: '-d' },
2603
+ },
2604
+ {
2605
+ name: 'status',
2606
+ type: 'string',
2607
+ required: false,
2608
+ description: 'New status',
2609
+ enum: ['pending', 'active', 'blocked', 'done', 'cancelled'] as const,
2610
+ cli: { flag: 'status', short: '-s' },
2611
+ },
2612
+ {
2613
+ name: 'priority',
2614
+ type: 'string',
2615
+ required: false,
2616
+ description: 'New priority (orthogonal to severity)',
2617
+ enum: ['low', 'medium', 'high', 'critical'] as const,
2618
+ cli: { flag: 'priority', short: '-p' },
2619
+ },
2620
+ { name: 'notes', type: 'string', required: false, description: 'Append a note entry' },
2621
+ {
2622
+ name: 'labels',
2623
+ type: 'array',
2624
+ required: false,
2625
+ description: 'Replace labels (comma-separated)',
2626
+ cli: { flag: 'labels', short: '-l' },
2627
+ },
2628
+ {
2629
+ name: 'addLabels',
2630
+ type: 'array',
2631
+ required: false,
2632
+ description: 'Add labels incrementally',
2633
+ },
2634
+ {
2635
+ name: 'removeLabels',
2636
+ type: 'array',
2637
+ required: false,
2638
+ description: 'Remove labels incrementally',
2639
+ },
2640
+ {
2641
+ name: 'depends',
2642
+ type: 'array',
2643
+ required: false,
2644
+ description: 'Replace dependency task IDs',
2645
+ cli: { flag: 'depends', short: '-D' },
2646
+ },
2647
+ {
2648
+ name: 'addDepends',
2649
+ type: 'array',
2650
+ required: false,
2651
+ description: 'Add dependency task IDs incrementally',
2652
+ },
2653
+ {
2654
+ name: 'removeDepends',
2655
+ type: 'array',
2656
+ required: false,
2657
+ description: 'Remove dependency task IDs incrementally',
2658
+ },
2659
+ {
2660
+ name: 'acceptance',
2661
+ type: 'array',
2662
+ required: false,
2663
+ description: 'Replace acceptance criteria (pipe-separated)',
2664
+ },
2665
+ {
2666
+ name: 'parent',
2667
+ type: 'string',
2668
+ required: false,
2669
+ description: 'Set parent task ID (null/empty promotes to root)',
2670
+ },
2671
+ {
2672
+ name: 'type',
2673
+ type: 'string',
2674
+ required: false,
2675
+ description: 'New hierarchy type',
2676
+ enum: ['saga', 'epic', 'task', 'subtask'] as const,
2677
+ cli: { flag: 'type', short: '-t' },
2678
+ },
2679
+ {
2680
+ name: 'size',
2681
+ type: 'string',
2682
+ required: false,
2683
+ description: 'New scope-size estimate',
2684
+ enum: ['small', 'medium', 'large'] as const,
2685
+ },
2686
+ {
2687
+ name: 'files',
2688
+ type: 'array',
2689
+ required: false,
2690
+ description: 'Replace associated file paths',
2691
+ },
2692
+ {
2693
+ name: 'addFiles',
2694
+ type: 'array',
2695
+ required: false,
2696
+ description: 'Add file paths incrementally',
2697
+ },
2698
+ {
2699
+ name: 'removeFiles',
2700
+ type: 'array',
2701
+ required: false,
2702
+ description: 'Remove file paths incrementally',
2703
+ },
2704
+ {
2705
+ name: 'pipelineStage',
2706
+ type: 'string',
2707
+ required: false,
2708
+ description: 'Set pipeline stage (forward-only)',
2709
+ },
2710
+ {
2711
+ name: 'kind',
2712
+ type: 'string',
2713
+ required: false,
2714
+ description: 'Task kind / intent axis (orthogonal to type)',
2715
+ enum: ['work', 'research', 'experiment', 'bug', 'spike', 'release'] as const,
2716
+ },
2717
+ {
2718
+ name: 'scope',
2719
+ type: 'string',
2720
+ required: false,
2721
+ description: 'Task scope / granularity axis',
2722
+ enum: ['project', 'feature', 'unit'] as const,
2723
+ },
2724
+ {
2725
+ name: 'severity',
2726
+ type: 'string',
2727
+ required: false,
2728
+ description: 'Severity level (orthogonal to priority)',
2729
+ enum: ['P0', 'P1', 'P2', 'P3'] as const,
2730
+ },
2731
+ {
2732
+ name: 'reason',
2733
+ type: 'string',
2734
+ required: false,
2735
+ description: 'Operator override reason for the AC-immutability guard',
2736
+ },
2737
+ {
2738
+ name: 'dependsWaiver',
2739
+ type: 'string',
2740
+ required: false,
2741
+ description: 'Justification to waive the critical-priority --depends requirement',
2742
+ },
2743
+ {
2744
+ name: 'clearBlockedBy',
2745
+ type: 'boolean',
2746
+ required: false,
2747
+ description: 'Clear the free-text blockedBy reason',
2748
+ },
2749
+ {
2750
+ name: 'relates',
2751
+ type: 'array',
2752
+ required: false,
2753
+ description: 'Replace related-task edges ({taskId, type, reason?})',
2754
+ },
2755
+ {
2756
+ name: 'addRelates',
2757
+ type: 'array',
2758
+ required: false,
2759
+ description: 'Add related-task edges without overwriting existing',
2760
+ },
2761
+ {
2762
+ name: 'removeRelates',
2763
+ type: 'array',
2764
+ required: false,
2765
+ description: 'Remove related-task edges by taskId',
2766
+ },
2458
2767
  ] satisfies ParamDef[],
2768
+ inputSchema: tasksUpdateInputContract,
2769
+ outputSchema: OUTPUT_CONTRACTS['tasks.update'],
2459
2770
  },
2460
2771
  {
2461
2772
  gateway: 'mutate',
@@ -2600,69 +2911,161 @@ export const OPERATIONS: OperationDef[] = [
2600
2911
  },
2601
2912
  ] satisfies ParamDef[],
2602
2913
  },
2914
+ // T11786 (epic T11556 · E1-GATEWAY-CRUD) — bulk task mutate ops the Studio
2915
+ // interactive Kanban (M6) binds to: within-column re-rank, atomic multi-move,
2916
+ // and first-class assignee (distinct from the agent claim lock).
2603
2917
  {
2604
2918
  gateway: 'mutate',
2605
2919
  domain: 'tasks',
2606
- operation: 'relates.add',
2607
- description: 'tasks.relates.add (mutate)',
2920
+ operation: 'reorder-rank',
2921
+ description:
2922
+ 'tasks.reorder-rank (mutate) — within-column positional re-rank from an explicit top-to-bottom task-ID order (Kanban drag-reorder)',
2608
2923
  tier: 1,
2609
2924
  idempotent: false,
2610
2925
  sessionRequired: false,
2611
- requiredParams: ['taskId', 'type'],
2926
+ requiredParams: ['orderedIds'],
2612
2927
  params: [
2613
2928
  {
2614
- name: 'taskId',
2615
- type: 'string',
2616
- required: true,
2617
- description: 'taskId parameter',
2618
- cli: { positional: true },
2619
- },
2620
- {
2621
- name: 'type',
2622
- type: 'string',
2929
+ name: 'orderedIds',
2930
+ type: 'array',
2623
2931
  required: true,
2624
- description: 'type parameter',
2932
+ description:
2933
+ 'Ordered task IDs describing the desired top-to-bottom column order; each task position is set to its 1-based index',
2625
2934
  },
2626
2935
  ] satisfies ParamDef[],
2936
+ outputSchema: OUTPUT_CONTRACTS['tasks.reorder-rank'],
2627
2937
  },
2628
2938
  {
2629
- // T11575 — the handler (dispatch/domains/tasks.ts 'relates.add-batch') and
2630
- // CLI command (cli/commands/relates.ts add-batch) shipped, but no
2631
- // OperationDef existed here, so `resolve()` returned undefined and the
2632
- // dispatcher rejected the call with E_INVALID_OPERATION before it ever
2633
- // reached the handler. Registering the op (gateway:'mutate') closes the gap.
2634
2939
  gateway: 'mutate',
2635
2940
  domain: 'tasks',
2636
- operation: 'relates.add-batch',
2941
+ operation: 'bulk-move',
2637
2942
  description:
2638
- 'tasks.relates.add-batch (mutate) — bulk-add advisory relation edges from a JSON payload ({relations:[...]} or {edges:[...]}); use dryRun to preview without writing.',
2943
+ 'tasks.bulk-move (mutate) — atomically move N tasks to a new status and/or pipeline stage in a single transaction (Kanban multi-card column move)',
2639
2944
  tier: 1,
2640
2945
  idempotent: false,
2641
2946
  sessionRequired: false,
2642
- // Neither `relations` nor `edges` is individually required — the handler
2643
- // accepts either shape and validates emptiness in core — so no param is
2644
- // declared required at the dispatch boundary.
2645
- requiredParams: [],
2947
+ requiredParams: ['taskIds'],
2646
2948
  params: [
2647
2949
  {
2648
- name: 'relations',
2950
+ name: 'taskIds',
2649
2951
  type: 'array',
2650
- required: false,
2651
- description: 'Array of relation edges ({from|taskId, to|relatedId, type, reason})',
2952
+ required: true,
2953
+ description: 'Task IDs to move (all-or-nothing; any failure rolls back every move)',
2652
2954
  },
2653
2955
  {
2654
- name: 'edges',
2655
- type: 'array',
2956
+ name: 'status',
2957
+ type: 'string',
2656
2958
  required: false,
2657
- description: 'Alias for `relations` — array of relation edges',
2959
+ description: 'Target lifecycle status applied to every task',
2960
+ enum: [
2961
+ 'pending',
2962
+ 'active',
2963
+ 'blocked',
2964
+ 'done',
2965
+ 'cancelled',
2966
+ 'archived',
2967
+ 'proposed',
2968
+ ] as const,
2658
2969
  },
2659
2970
  {
2660
- name: 'dryRun',
2661
- type: 'boolean',
2971
+ name: 'pipelineStage',
2972
+ type: 'string',
2662
2973
  required: false,
2663
- description: 'Validate and report what would be created without writing',
2974
+ description: 'Target pipeline stage applied to every task (forward-only)',
2664
2975
  },
2665
- {
2976
+ ] satisfies ParamDef[],
2977
+ outputSchema: OUTPUT_CONTRACTS['tasks.bulk-move'],
2978
+ },
2979
+ {
2980
+ gateway: 'mutate',
2981
+ domain: 'tasks',
2982
+ operation: 'assignee',
2983
+ description:
2984
+ 'tasks.assignee (mutate) — set/clear a first-class human assignee (DISTINCT from the agent claim/unclaim lock; pass assignee:null to clear)',
2985
+ tier: 1,
2986
+ idempotent: true,
2987
+ sessionRequired: false,
2988
+ requiredParams: ['taskId'],
2989
+ params: [
2990
+ {
2991
+ name: 'taskId',
2992
+ type: 'string',
2993
+ required: true,
2994
+ description: 'Task ID whose assignee is being set or cleared',
2995
+ cli: { positional: true },
2996
+ },
2997
+ {
2998
+ name: 'assignee',
2999
+ type: 'string',
3000
+ required: false,
3001
+ description: 'Assignee to set; omit / empty / null clears the assignee',
3002
+ },
3003
+ ] satisfies ParamDef[],
3004
+ outputSchema: OUTPUT_CONTRACTS['tasks.assignee'],
3005
+ },
3006
+ {
3007
+ gateway: 'mutate',
3008
+ domain: 'tasks',
3009
+ operation: 'relates.add',
3010
+ description: 'tasks.relates.add (mutate)',
3011
+ tier: 1,
3012
+ idempotent: false,
3013
+ sessionRequired: false,
3014
+ requiredParams: ['taskId', 'type'],
3015
+ params: [
3016
+ {
3017
+ name: 'taskId',
3018
+ type: 'string',
3019
+ required: true,
3020
+ description: 'taskId parameter',
3021
+ cli: { positional: true },
3022
+ },
3023
+ {
3024
+ name: 'type',
3025
+ type: 'string',
3026
+ required: true,
3027
+ description: 'type parameter',
3028
+ },
3029
+ ] satisfies ParamDef[],
3030
+ },
3031
+ {
3032
+ // T11575 — the handler (dispatch/domains/tasks.ts 'relates.add-batch') and
3033
+ // CLI command (cli/commands/relates.ts add-batch) shipped, but no
3034
+ // OperationDef existed here, so `resolve()` returned undefined and the
3035
+ // dispatcher rejected the call with E_INVALID_OPERATION before it ever
3036
+ // reached the handler. Registering the op (gateway:'mutate') closes the gap.
3037
+ gateway: 'mutate',
3038
+ domain: 'tasks',
3039
+ operation: 'relates.add-batch',
3040
+ description:
3041
+ 'tasks.relates.add-batch (mutate) — bulk-add advisory relation edges from a JSON payload ({relations:[...]} or {edges:[...]}); use dryRun to preview without writing.',
3042
+ tier: 1,
3043
+ idempotent: false,
3044
+ sessionRequired: false,
3045
+ // Neither `relations` nor `edges` is individually required — the handler
3046
+ // accepts either shape and validates emptiness in core — so no param is
3047
+ // declared required at the dispatch boundary.
3048
+ requiredParams: [],
3049
+ params: [
3050
+ {
3051
+ name: 'relations',
3052
+ type: 'array',
3053
+ required: false,
3054
+ description: 'Array of relation edges ({from|taskId, to|relatedId, type, reason})',
3055
+ },
3056
+ {
3057
+ name: 'edges',
3058
+ type: 'array',
3059
+ required: false,
3060
+ description: 'Alias for `relations` — array of relation edges',
3061
+ },
3062
+ {
3063
+ name: 'dryRun',
3064
+ type: 'boolean',
3065
+ required: false,
3066
+ description: 'Validate and report what would be created without writing',
3067
+ },
3068
+ {
2666
3069
  name: 'reasonWaiver',
2667
3070
  type: 'string',
2668
3071
  required: false,
@@ -4431,6 +4834,32 @@ export const OPERATIONS: OperationDef[] = [
4431
4834
  },
4432
4835
  ],
4433
4836
  },
4837
+ {
4838
+ gateway: 'mutate',
4839
+ domain: 'admin',
4840
+ operation: 'config.unset',
4841
+ description:
4842
+ 'admin.config.unset (mutate) — remove a key from a scoped config file (idempotent) (T11917)',
4843
+ tier: 1,
4844
+ idempotent: true,
4845
+ sessionRequired: false,
4846
+ requiredParams: ['key'],
4847
+ params: [
4848
+ {
4849
+ name: 'key',
4850
+ type: 'string',
4851
+ required: true,
4852
+ description: 'Dot-notation config key to remove',
4853
+ cli: { positional: true },
4854
+ },
4855
+ {
4856
+ name: 'global',
4857
+ type: 'boolean',
4858
+ required: false,
4859
+ description: 'Remove from the global file instead of the project file',
4860
+ },
4861
+ ] satisfies ParamDef[],
4862
+ },
4434
4863
  {
4435
4864
  gateway: 'query',
4436
4865
  domain: 'admin',
@@ -8837,4 +9266,601 @@ export const OPERATIONS: OperationDef[] = [
8837
9266
  },
8838
9267
  ] satisfies ParamDef[],
8839
9268
  },
9269
+
9270
+ // ── service domain: universal service-vault OAuth flow (T11939 · epic T11765) ──
9271
+ // Four verbs driving the declarative SERVICE_PROVIDERS registry: auth-url (query
9272
+ // — build a PKCE authorization URL), exchange (mutate — persist encrypted tokens
9273
+ // from an auth code), refresh (mutate — renew an access token honoring the
9274
+ // per-provider RefreshConfig), self-heal (mutate — resolve + transparently
9275
+ // refresh a past-expiry connection). The flow lives in CORE (store/service-oauth.ts).
9276
+ {
9277
+ gateway: 'query',
9278
+ domain: 'service',
9279
+ operation: 'auth-url',
9280
+ description:
9281
+ 'service.auth-url (query) — build a PKCE OAuth authorization URL for a service provider; returns the URL + the code verifier + state to round-trip into service.exchange.',
9282
+ tier: 2,
9283
+ idempotent: false,
9284
+ sessionRequired: false,
9285
+ requiredParams: ['provider'],
9286
+ params: [
9287
+ {
9288
+ name: 'provider',
9289
+ type: 'string',
9290
+ required: true,
9291
+ description:
9292
+ 'Service provider key (e.g. github, google, notion) — must be in SERVICE_PROVIDERS.',
9293
+ },
9294
+ {
9295
+ name: 'state',
9296
+ type: 'string',
9297
+ required: false,
9298
+ description: 'Opaque CSRF state to embed; a random value is generated when omitted.',
9299
+ },
9300
+ {
9301
+ name: 'scope',
9302
+ type: 'string',
9303
+ required: false,
9304
+ description: 'Scope override (e.g. a BYOC custom scope). Defaults to the provider scope.',
9305
+ },
9306
+ {
9307
+ name: 'redirectUri',
9308
+ type: 'string',
9309
+ required: false,
9310
+ description: 'Redirect URI override. Defaults to the provider loopback redirect.',
9311
+ },
9312
+ ] satisfies ParamDef[],
9313
+ },
9314
+ {
9315
+ gateway: 'mutate',
9316
+ domain: 'service',
9317
+ operation: 'exchange',
9318
+ description:
9319
+ 'service.exchange (mutate) — exchange an OAuth authorization code (+ PKCE verifier) for tokens and PERSIST them encryptGlobal-encrypted into service_connections. Returns the non-secret connection identity.',
9320
+ tier: 2,
9321
+ idempotent: false,
9322
+ sessionRequired: false,
9323
+ requiredParams: ['provider', 'code', 'codeVerifier', 'redirectUri'],
9324
+ params: [
9325
+ { name: 'provider', type: 'string', required: true, description: 'Service provider key.' },
9326
+ {
9327
+ name: 'code',
9328
+ type: 'string',
9329
+ required: true,
9330
+ description: 'Authorization code from the redirect callback.',
9331
+ },
9332
+ {
9333
+ name: 'codeVerifier',
9334
+ type: 'string',
9335
+ required: true,
9336
+ description: 'PKCE code verifier from service.auth-url (round-tripped).',
9337
+ },
9338
+ {
9339
+ name: 'redirectUri',
9340
+ type: 'string',
9341
+ required: true,
9342
+ description: 'Redirect URI used in service.auth-url (must match).',
9343
+ },
9344
+ {
9345
+ name: 'label',
9346
+ type: 'string',
9347
+ required: false,
9348
+ description: 'Connection label, unique within the provider. Defaults to "default".',
9349
+ },
9350
+ ] satisfies ParamDef[],
9351
+ },
9352
+ {
9353
+ gateway: 'mutate',
9354
+ domain: 'service',
9355
+ operation: 'refresh',
9356
+ description:
9357
+ 'service.refresh (mutate) — refresh a service connection access token honoring the per-provider RefreshConfig (Form/Json × Body/BasicAuth, plus github-app / service-account-jwt / client-credentials variants); re-encrypts + persists the new blob.',
9358
+ tier: 2,
9359
+ idempotent: false,
9360
+ sessionRequired: false,
9361
+ requiredParams: ['agentId', 'provider', 'label'],
9362
+ params: [
9363
+ {
9364
+ name: 'agentId',
9365
+ type: 'string',
9366
+ required: true,
9367
+ description: 'Agent requesting the refresh (trust-gated).',
9368
+ },
9369
+ { name: 'provider', type: 'string', required: true, description: 'Service provider key.' },
9370
+ { name: 'label', type: 'string', required: true, description: 'Connection label.' },
9371
+ {
9372
+ name: 'approved',
9373
+ type: 'boolean',
9374
+ required: false,
9375
+ description: 'Out-of-band manual-approval flag forwarded to the trust gate.',
9376
+ },
9377
+ ] satisfies ParamDef[],
9378
+ },
9379
+ {
9380
+ gateway: 'mutate',
9381
+ domain: 'service',
9382
+ operation: 'self-heal',
9383
+ description:
9384
+ 'service.self-heal (mutate) — resolve a service connection and, when its expires_at is past, transparently refresh + re-encrypt + persist; returns a fresh sealed-credential reference (agents never see a stale token).',
9385
+ tier: 2,
9386
+ idempotent: true,
9387
+ sessionRequired: false,
9388
+ requiredParams: ['agentId', 'provider', 'label'],
9389
+ params: [
9390
+ {
9391
+ name: 'agentId',
9392
+ type: 'string',
9393
+ required: true,
9394
+ description: 'Agent requesting the connection (trust-gated).',
9395
+ },
9396
+ { name: 'provider', type: 'string', required: true, description: 'Service provider key.' },
9397
+ { name: 'label', type: 'string', required: true, description: 'Connection label.' },
9398
+ {
9399
+ name: 'approved',
9400
+ type: 'boolean',
9401
+ required: false,
9402
+ description: 'Out-of-band manual-approval flag forwarded to the trust gate.',
9403
+ },
9404
+ ] satisfies ParamDef[],
9405
+ },
9406
+
9407
+ // ── service domain: user-facing service-vault CLI verbs (T11941 · M2-W4) ──
9408
+ // Four verbs making the universal vault USABLE from the CLI: connect (mutate —
9409
+ // store a credential via token-direct or paste-code OAuth exchange), list
9410
+ // (query — redacted connection views, NEVER the token), revoke (mutate — hard
9411
+ // delete + cascade agent grants), status (query — connection health: expired?
9412
+ // needsRefresh?). Thin handlers delegate to CORE accessor + service-oauth.
9413
+ {
9414
+ gateway: 'mutate',
9415
+ domain: 'service',
9416
+ operation: 'connect',
9417
+ description:
9418
+ 'service.connect (mutate) — connect a service credential: store an already-obtained token (--token) OR exchange an OAuth authorization code (--paste-code + --code-verifier + --redirect-uri). Persists the token blob encryptGlobal-encrypted; returns the NON-SECRET connection identity (never the token).',
9419
+ tier: 2,
9420
+ idempotent: false,
9421
+ sessionRequired: false,
9422
+ requiredParams: ['provider'],
9423
+ params: [
9424
+ {
9425
+ name: 'provider',
9426
+ type: 'string',
9427
+ required: true,
9428
+ description: 'Service provider key (e.g. github, google, notion).',
9429
+ },
9430
+ {
9431
+ name: 'label',
9432
+ type: 'string',
9433
+ required: false,
9434
+ description: "Connection label, unique within the provider. Defaults to 'default'.",
9435
+ },
9436
+ {
9437
+ name: 'token',
9438
+ type: 'string',
9439
+ required: false,
9440
+ description: 'Direct access token to store (token-direct mode). SECRET — never echoed.',
9441
+ },
9442
+ {
9443
+ name: 'refreshToken',
9444
+ type: 'string',
9445
+ required: false,
9446
+ description: 'Optional refresh token paired with --token. SECRET.',
9447
+ },
9448
+ {
9449
+ name: 'expiresAt',
9450
+ type: 'string',
9451
+ required: false,
9452
+ description: 'ISO-8601 access-token expiry (token-direct mode).',
9453
+ },
9454
+ {
9455
+ name: 'code',
9456
+ type: 'string',
9457
+ required: false,
9458
+ description: 'OAuth authorization code from the redirect callback (paste-code mode).',
9459
+ },
9460
+ {
9461
+ name: 'codeVerifier',
9462
+ type: 'string',
9463
+ required: false,
9464
+ description: 'PKCE code verifier from service.auth-url, round-tripped (paste-code mode).',
9465
+ },
9466
+ {
9467
+ name: 'redirectUri',
9468
+ type: 'string',
9469
+ required: false,
9470
+ description: 'Redirect URI used in service.auth-url; must match (paste-code mode).',
9471
+ },
9472
+ ] satisfies ParamDef[],
9473
+ },
9474
+ {
9475
+ gateway: 'query',
9476
+ domain: 'service',
9477
+ operation: 'list',
9478
+ description:
9479
+ 'service.list (query) — list service connections as NON-SECRET views (provider/label/status/scopes/expiresAt/hasCredentials). The decrypted token is NEVER surfaced.',
9480
+ tier: 2,
9481
+ idempotent: true,
9482
+ sessionRequired: false,
9483
+ requiredParams: [],
9484
+ params: [
9485
+ {
9486
+ name: 'provider',
9487
+ type: 'string',
9488
+ required: false,
9489
+ description: 'Optional provider filter; lists all providers when omitted.',
9490
+ },
9491
+ ] satisfies ParamDef[],
9492
+ },
9493
+ {
9494
+ gateway: 'mutate',
9495
+ domain: 'service',
9496
+ operation: 'revoke',
9497
+ description:
9498
+ 'service.revoke (mutate) — DELETE a service connection and CASCADE its agent_service_grants. Returns the {count, deleted, grantsRemoved} envelope (count=0 when no such connection).',
9499
+ tier: 2,
9500
+ idempotent: false,
9501
+ sessionRequired: false,
9502
+ requiredParams: ['provider', 'label'],
9503
+ params: [
9504
+ { name: 'provider', type: 'string', required: true, description: 'Service provider key.' },
9505
+ {
9506
+ name: 'label',
9507
+ type: 'string',
9508
+ required: true,
9509
+ description: 'Connection label to revoke.',
9510
+ },
9511
+ ] satisfies ParamDef[],
9512
+ },
9513
+ {
9514
+ gateway: 'query',
9515
+ domain: 'service',
9516
+ operation: 'status',
9517
+ description:
9518
+ 'service.status (query) — report connection health: each connection adds `expired` + `needsRefresh` booleans computed from expires_at. NON-SECRET; never surfaces a token.',
9519
+ tier: 2,
9520
+ idempotent: true,
9521
+ sessionRequired: false,
9522
+ requiredParams: [],
9523
+ params: [
9524
+ {
9525
+ name: 'provider',
9526
+ type: 'string',
9527
+ required: false,
9528
+ description: 'Optional provider filter; reports all connections when omitted.',
9529
+ },
9530
+ ] satisfies ParamDef[],
9531
+ },
9532
+
9533
+ // ── 5-entity provider experience (T11700 · epic T11666 · North-Star §2) ──
9534
+ // The addressable Provider / Alias / Account / Model / Profile surface. Each op
9535
+ // dispatches to the CORE entity-ops layer (llm/entity-ops.ts), which is a thin
9536
+ // composition over the ALREADY-MERGED accessors: the credential pool
9537
+ // (account.* — secret-bearing, redacted to tokenPreview), the declarative
9538
+ // `providers` table (#1039), the `models_catalog` table (#1037), and the
9539
+ // resolver-consumed `llm.profiles[name]` config (profile.*). The secret-bearing
9540
+ // ops carry NO `mcpExposed` flag → they are MCP-default-denied (AC5/AC7); read
9541
+ // ops are also unary CLI/RPC/HTTP (no `mcpExposed`), keeping the MCP surface
9542
+ // curated. INPUT + OUTPUT contracts live in contracts/operations/entities.ts.
9543
+
9544
+ // account.* — the credential pool (the rename/successor of the secret-bearing
9545
+ // `llm` pool ops). `account.add` is SECRET-BEARING (cli-only; MCP-denied).
9546
+ {
9547
+ gateway: 'mutate',
9548
+ domain: 'account',
9549
+ operation: 'add',
9550
+ description:
9551
+ 'account.add (mutate) — store a pooled provider credential. SECRET-BEARING (cli-only): accepts an API key / OAuth token and persists it encrypted; returns the NON-SECRET redacted view (tokenPreview last-4 ONLY — never the raw secret).',
9552
+ tier: 2,
9553
+ idempotent: false,
9554
+ sessionRequired: false,
9555
+ requiredParams: ['provider', 'token'],
9556
+ params: [
9557
+ {
9558
+ name: 'provider',
9559
+ type: 'string',
9560
+ required: true,
9561
+ description: 'LLM provider transport key (e.g. anthropic, openai).',
9562
+ cli: { positional: true },
9563
+ },
9564
+ {
9565
+ name: 'token',
9566
+ type: 'string',
9567
+ required: true,
9568
+ description: 'API key or OAuth bearer token to persist. SECRET — never echoed.',
9569
+ },
9570
+ {
9571
+ name: 'label',
9572
+ type: 'string',
9573
+ required: false,
9574
+ description: "Account label, unique within the provider (default: 'default').",
9575
+ },
9576
+ {
9577
+ name: 'baseUrl',
9578
+ type: 'string',
9579
+ required: false,
9580
+ description: 'Optional override for the provider base URL.',
9581
+ },
9582
+ {
9583
+ name: 'authType',
9584
+ type: 'string',
9585
+ required: false,
9586
+ description: "Explicit auth-type override ('api_key' | 'oauth' | 'aws_sdk').",
9587
+ },
9588
+ {
9589
+ name: 'priority',
9590
+ type: 'number',
9591
+ required: false,
9592
+ description: 'Optional priority override (lower wins).',
9593
+ },
9594
+ ] satisfies ParamDef[],
9595
+ },
9596
+ {
9597
+ gateway: 'query',
9598
+ domain: 'account',
9599
+ operation: 'list',
9600
+ description:
9601
+ 'account.list (query) — list pooled accounts as NON-SECRET redacted views (provider/label/authType/tokenPreview). The decrypted secret is NEVER surfaced.',
9602
+ tier: 2,
9603
+ idempotent: true,
9604
+ sessionRequired: false,
9605
+ requiredParams: [],
9606
+ params: [
9607
+ {
9608
+ name: 'provider',
9609
+ type: 'string',
9610
+ required: false,
9611
+ description: 'Optional provider filter; lists all providers when omitted.',
9612
+ },
9613
+ ] satisfies ParamDef[],
9614
+ },
9615
+ {
9616
+ gateway: 'mutate',
9617
+ domain: 'account',
9618
+ operation: 'remove',
9619
+ description:
9620
+ 'account.remove (mutate) — delete a (provider, label) account from the pool. Returns the {count, deleted} envelope (count=0 when absent — idempotent).',
9621
+ tier: 2,
9622
+ idempotent: true,
9623
+ sessionRequired: false,
9624
+ requiredParams: ['provider', 'label'],
9625
+ params: [
9626
+ {
9627
+ name: 'provider',
9628
+ type: 'string',
9629
+ required: true,
9630
+ description: 'LLM provider transport key.',
9631
+ cli: { positional: true },
9632
+ },
9633
+ {
9634
+ name: 'label',
9635
+ type: 'string',
9636
+ required: true,
9637
+ description: 'Account label to remove.',
9638
+ },
9639
+ ] satisfies ParamDef[],
9640
+ },
9641
+
9642
+ // provider.* — the declarative `providers` table (#1039) + alias resolution.
9643
+ {
9644
+ gateway: 'query',
9645
+ domain: 'provider',
9646
+ operation: 'list',
9647
+ description:
9648
+ 'provider.list (query) — list every declarative provider as a NON-SECRET view (id/displayName/aliases/authMethods/modelsDevId) from the providers table.',
9649
+ tier: 1,
9650
+ idempotent: true,
9651
+ sessionRequired: false,
9652
+ requiredParams: [],
9653
+ params: [] satisfies ParamDef[],
9654
+ },
9655
+ {
9656
+ gateway: 'query',
9657
+ domain: 'provider',
9658
+ operation: 'show',
9659
+ description:
9660
+ 'provider.show (query) — resolve ONE provider by id OR case-insensitive alias against the declarative alias index; returns the provider view + the id/alias matched.',
9661
+ tier: 1,
9662
+ idempotent: true,
9663
+ sessionRequired: false,
9664
+ requiredParams: ['provider'],
9665
+ params: [
9666
+ {
9667
+ name: 'provider',
9668
+ type: 'string',
9669
+ required: true,
9670
+ description: 'Provider id OR a case-insensitive alias.',
9671
+ cli: { positional: true },
9672
+ },
9673
+ ] satisfies ParamDef[],
9674
+ },
9675
+ {
9676
+ gateway: 'mutate',
9677
+ domain: 'provider',
9678
+ operation: 'connect',
9679
+ description:
9680
+ 'provider.connect (mutate) — connect a provider by storing a token as one of its accounts (token-direct). SECRET-BEARING (cli-only): resolves the provider (id/alias) then delegates to the secret-bearing account write; returns the NON-SECRET account identity (never the raw token). OAuth flows route through `cleo service`.',
9681
+ tier: 2,
9682
+ idempotent: false,
9683
+ sessionRequired: false,
9684
+ requiredParams: ['provider'],
9685
+ params: [
9686
+ {
9687
+ name: 'provider',
9688
+ type: 'string',
9689
+ required: true,
9690
+ description: 'Provider id or alias to connect.',
9691
+ cli: { positional: true },
9692
+ },
9693
+ {
9694
+ name: 'token',
9695
+ type: 'string',
9696
+ required: false,
9697
+ description: 'Direct API key / bearer token to store (token-direct mode). SECRET.',
9698
+ },
9699
+ {
9700
+ name: 'label',
9701
+ type: 'string',
9702
+ required: false,
9703
+ description: "Account label to create (default: 'default').",
9704
+ },
9705
+ {
9706
+ name: 'authType',
9707
+ type: 'string',
9708
+ required: false,
9709
+ description: "Explicit auth-type override ('api_key' | 'oauth' | 'aws_sdk').",
9710
+ },
9711
+ ] satisfies ParamDef[],
9712
+ },
9713
+
9714
+ // model.* — the models.dev catalog (`models_catalog` table, #1037). QUERY-ONLY.
9715
+ {
9716
+ gateway: 'query',
9717
+ domain: 'model',
9718
+ operation: 'query',
9719
+ description:
9720
+ 'model.query (query) — read the models.dev catalog (models_catalog table) newest-first by release date; optionally filtered by provider (models.dev id) and capped by --limit. Returns NON-SECRET catalog views.',
9721
+ tier: 1,
9722
+ idempotent: true,
9723
+ sessionRequired: false,
9724
+ requiredParams: [],
9725
+ params: [
9726
+ {
9727
+ name: 'provider',
9728
+ type: 'string',
9729
+ required: false,
9730
+ description:
9731
+ 'Optional provider filter (models.dev id); queries all providers when omitted.',
9732
+ },
9733
+ {
9734
+ name: 'limit',
9735
+ type: 'number',
9736
+ required: false,
9737
+ description: 'Optional cap on the number of rows returned (newest-first).',
9738
+ },
9739
+ ] satisfies ParamDef[],
9740
+ },
9741
+ {
9742
+ gateway: 'query',
9743
+ domain: 'model',
9744
+ operation: 'show',
9745
+ description:
9746
+ 'model.show (query) — resolve ONE catalog model by id; returns {found, model} (found=false when the id is absent — not an error).',
9747
+ tier: 1,
9748
+ idempotent: true,
9749
+ sessionRequired: false,
9750
+ requiredParams: ['model'],
9751
+ params: [
9752
+ {
9753
+ name: 'model',
9754
+ type: 'string',
9755
+ required: true,
9756
+ description: 'Model id (catalog key) to resolve.',
9757
+ cli: { positional: true },
9758
+ },
9759
+ ] satisfies ParamDef[],
9760
+ },
9761
+
9762
+ // profile.* — the named binding account+model[+params+role], persisted into
9763
+ // `llm.profiles[name]` (the resolver-consumed config SSoT).
9764
+ {
9765
+ gateway: 'mutate',
9766
+ domain: 'profile',
9767
+ operation: 'create',
9768
+ description:
9769
+ 'profile.create (mutate) — bind an account + model into a named, addressable profile. Validates the binding (the pinned account must exist; the model must be in the catalog) then persists into llm.profiles[name]. Returns {count, created, profile}.',
9770
+ tier: 2,
9771
+ idempotent: false,
9772
+ sessionRequired: false,
9773
+ requiredParams: ['name', 'provider', 'model'],
9774
+ params: [
9775
+ {
9776
+ name: 'name',
9777
+ type: 'string',
9778
+ required: true,
9779
+ description: 'Profile name (the addressable handle).',
9780
+ cli: { positional: true },
9781
+ },
9782
+ {
9783
+ name: 'provider',
9784
+ type: 'string',
9785
+ required: true,
9786
+ description: 'Provider transport the bound account belongs to.',
9787
+ },
9788
+ {
9789
+ name: 'model',
9790
+ type: 'string',
9791
+ required: true,
9792
+ description: 'Model id to bind (validated against the catalog).',
9793
+ },
9794
+ {
9795
+ name: 'label',
9796
+ type: 'string',
9797
+ required: false,
9798
+ description: 'Account label to pin (the credential binding). Validated to exist.',
9799
+ },
9800
+ {
9801
+ name: 'role',
9802
+ type: 'string',
9803
+ required: false,
9804
+ description: 'Optional role this profile occupies (extraction | consolidation | …).',
9805
+ },
9806
+ ] satisfies ParamDef[],
9807
+ },
9808
+ {
9809
+ gateway: 'query',
9810
+ domain: 'profile',
9811
+ operation: 'list',
9812
+ description:
9813
+ 'profile.list (query) — list every named profile from llm.profiles (name + provider + model + optional credentialLabel).',
9814
+ tier: 2,
9815
+ idempotent: true,
9816
+ sessionRequired: false,
9817
+ requiredParams: [],
9818
+ params: [] satisfies ParamDef[],
9819
+ },
9820
+ {
9821
+ gateway: 'mutate',
9822
+ domain: 'profile',
9823
+ operation: 'pin',
9824
+ description:
9825
+ 'profile.pin (mutate) — pin a role to a named profile (llm.roles[role].profile). The role must be a valid background role and the profile must already exist.',
9826
+ tier: 2,
9827
+ idempotent: true,
9828
+ sessionRequired: false,
9829
+ requiredParams: ['name', 'role'],
9830
+ params: [
9831
+ {
9832
+ name: 'name',
9833
+ type: 'string',
9834
+ required: true,
9835
+ description: 'Profile name to pin (must exist).',
9836
+ cli: { positional: true },
9837
+ },
9838
+ {
9839
+ name: 'role',
9840
+ type: 'string',
9841
+ required: true,
9842
+ description: 'Role to pin to this profile (extraction | consolidation | derivation | …).',
9843
+ },
9844
+ ] satisfies ParamDef[],
9845
+ },
9846
+ {
9847
+ gateway: 'mutate',
9848
+ domain: 'profile',
9849
+ operation: 'use',
9850
+ description:
9851
+ 'profile.use (mutate) — set a named profile as the global default binding (llm.defaultProfile). The profile must already exist.',
9852
+ tier: 2,
9853
+ idempotent: true,
9854
+ sessionRequired: false,
9855
+ requiredParams: ['name'],
9856
+ params: [
9857
+ {
9858
+ name: 'name',
9859
+ type: 'string',
9860
+ required: true,
9861
+ description: 'Profile name to mark as default (must exist).',
9862
+ cli: { positional: true },
9863
+ },
9864
+ ] satisfies ParamDef[],
9865
+ },
8840
9866
  ];