@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.
- package/dist/__tests__/operation-def.test.js +4 -1
- package/dist/__tests__/operation-def.test.js.map +1 -1
- package/dist/__tests__/provider-def.test.d.ts +12 -0
- package/dist/__tests__/provider-def.test.d.ts.map +1 -0
- package/dist/__tests__/provider-def.test.js +84 -0
- package/dist/__tests__/provider-def.test.js.map +1 -0
- package/dist/__tests__/service-provider.test.d.ts +17 -0
- package/dist/__tests__/service-provider.test.d.ts.map +1 -0
- package/dist/__tests__/service-provider.test.js +108 -0
- package/dist/__tests__/service-provider.test.js.map +1 -0
- package/dist/boundary.d.ts +52 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +23 -0
- package/dist/boundary.js.map +1 -1
- package/dist/branch-lock.d.ts +4 -0
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js.map +1 -1
- package/dist/conduit.d.ts +171 -0
- package/dist/conduit.d.ts.map +1 -1
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +22 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config.d.ts +109 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts +21 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts.map +1 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +84 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -0
- package/dist/dispatch/identity.d.ts +1 -1
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +20 -0
- package/dist/dispatch/identity.js.map +1 -1
- package/dist/dispatch/operation-def.d.ts +20 -0
- package/dist/dispatch/operation-def.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +1030 -46
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/lease-ipc/__tests__/freeze.test.js +92 -0
- package/dist/lease-ipc/__tests__/freeze.test.js.map +1 -1
- package/dist/lease-ipc/index.d.ts +2 -2
- package/dist/lease-ipc/index.d.ts.map +1 -1
- package/dist/lease-ipc/index.js +1 -1
- package/dist/lease-ipc/index.js.map +1 -1
- package/dist/lease-ipc/messages.d.ts +167 -3
- package/dist/lease-ipc/messages.d.ts.map +1 -1
- package/dist/lease-ipc/messages.js +93 -0
- package/dist/lease-ipc/messages.js.map +1 -1
- package/dist/llm/catalog-schema.d.ts +242 -0
- package/dist/llm/catalog-schema.d.ts.map +1 -0
- package/dist/llm/catalog-schema.js +153 -0
- package/dist/llm/catalog-schema.js.map +1 -0
- package/dist/llm/catalog-schema.json +139 -0
- package/dist/llm/index.d.ts +15 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +14 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/oauth.d.ts +16 -2
- package/dist/llm/oauth.d.ts.map +1 -1
- package/dist/llm/onboarding.d.ts +126 -0
- package/dist/llm/onboarding.d.ts.map +1 -0
- package/dist/llm/onboarding.js +34 -0
- package/dist/llm/onboarding.js.map +1 -0
- package/dist/llm/provider-def.d.ts +254 -0
- package/dist/llm/provider-def.d.ts.map +1 -0
- package/dist/llm/provider-def.js +93 -0
- package/dist/llm/provider-def.js.map +1 -0
- package/dist/llm/system-resolver.d.ts +42 -0
- package/dist/llm/system-resolver.d.ts.map +1 -1
- package/dist/operations/admin.d.ts +124 -1
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/entities.d.ts +110 -0
- package/dist/operations/entities.d.ts.map +1 -0
- package/dist/operations/entities.js +695 -0
- package/dist/operations/entities.js.map +1 -0
- package/dist/operations/llm.d.ts +16 -1
- package/dist/operations/llm.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.js +230 -0
- package/dist/operations/output-contracts-data.js.map +1 -1
- package/dist/operations/service.d.ts +68 -0
- package/dist/operations/service.d.ts.map +1 -0
- package/dist/operations/service.js +336 -0
- package/dist/operations/service.js.map +1 -0
- package/dist/operations/tasks.d.ts +109 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/worktree.d.ts +11 -1
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/playbook.d.ts +56 -0
- package/dist/playbook.d.ts.map +1 -1
- package/dist/release/plan.d.ts +5 -5
- package/dist/spawn.d.ts +70 -0
- package/dist/spawn.d.ts.map +1 -1
- package/dist/spawn.js +1 -0
- package/dist/spawn.js.map +1 -1
- package/dist/vault/service-provider.d.ts +343 -0
- package/dist/vault/service-provider.d.ts.map +1 -0
- package/dist/vault/service-provider.js +857 -0
- package/dist/vault/service-provider.js.map +1 -0
- package/dist/workgraph.d.ts +12 -12
- package/package.json +10 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/operation-def.test.ts +6 -1
- package/src/__tests__/provider-def.test.ts +93 -0
- package/src/__tests__/service-provider.test.ts +129 -0
- package/src/boundary.ts +69 -0
- package/src/branch-lock.ts +4 -0
- package/src/conduit.ts +200 -0
- package/src/config/index.ts +35 -0
- package/src/config.ts +116 -1
- package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +99 -0
- package/src/dispatch/identity.ts +20 -0
- package/src/dispatch/operation-def.ts +21 -0
- package/src/dispatch/operations-registry.ts +1062 -36
- package/src/index.ts +147 -2
- package/src/lease-ipc/__tests__/freeze.test.ts +96 -0
- package/src/lease-ipc/index.ts +12 -0
- package/src/lease-ipc/messages.ts +117 -0
- package/src/llm/catalog-schema.json +139 -0
- package/src/llm/catalog-schema.ts +184 -0
- package/src/llm/index.ts +29 -0
- package/src/llm/oauth.ts +16 -2
- package/src/llm/onboarding.ts +131 -0
- package/src/llm/provider-def.ts +291 -0
- package/src/llm/system-resolver.ts +45 -0
- package/src/operations/admin.ts +144 -0
- package/src/operations/entities.ts +758 -0
- package/src/operations/llm.ts +16 -0
- package/src/operations/output-contracts-data.ts +264 -0
- package/src/operations/service.ts +360 -0
- package/src/operations/tasks.ts +119 -0
- package/src/operations/worktree.ts +11 -0
- package/src/playbook.ts +56 -0
- package/src/spawn.ts +79 -0
- package/src/vault/service-provider.ts +1137 -0
package/src/operations/llm.ts
CHANGED
|
@@ -255,6 +255,7 @@ export type ResolutionSource =
|
|
|
255
255
|
| 'default'
|
|
256
256
|
| 'default-profile'
|
|
257
257
|
| 'registered-default'
|
|
258
|
+
| 'cross-provider'
|
|
258
259
|
| 'implicit-fallback';
|
|
259
260
|
|
|
260
261
|
/**
|
|
@@ -349,6 +350,21 @@ export interface ResolveLLMForRoleOptions {
|
|
|
349
350
|
* @task T11748
|
|
350
351
|
*/
|
|
351
352
|
systemKey?: string;
|
|
353
|
+
/**
|
|
354
|
+
* Optional named profile (a key of `llm.profiles`) that pins this resolution
|
|
355
|
+
* to a specific provider/model/credential, winning over the role tier
|
|
356
|
+
* (T11759 · M4).
|
|
357
|
+
*
|
|
358
|
+
* When set and the profile resolves, it is the HIGHEST-priority tier of
|
|
359
|
+
* {@link selectProviderModel} — above `roles[role]` and the `systems[key]`
|
|
360
|
+
* override — so an explicit caller pin (e.g. a `.cantbook` stage's
|
|
361
|
+
* `profile:`) cannot be silently overridden by background role config. When
|
|
362
|
+
* the named profile is absent or structurally incomplete, resolution falls
|
|
363
|
+
* through to the normal chain unchanged. Direct role callers leave it unset.
|
|
364
|
+
*
|
|
365
|
+
* @task T11759
|
|
366
|
+
*/
|
|
367
|
+
profileOverride?: string;
|
|
352
368
|
}
|
|
353
369
|
|
|
354
370
|
// ============================================================================
|
|
@@ -24,10 +24,30 @@
|
|
|
24
24
|
* @task T11692 — DHQ-057: per-operation output schema SSoT
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import {
|
|
28
|
+
accountAddOutputContract,
|
|
29
|
+
accountListOutputContract,
|
|
30
|
+
accountRemoveOutputContract,
|
|
31
|
+
modelQueryOutputContract,
|
|
32
|
+
modelShowOutputContract,
|
|
33
|
+
profileCreateOutputContract,
|
|
34
|
+
profileListOutputContract,
|
|
35
|
+
profilePinOutputContract,
|
|
36
|
+
profileUseOutputContract,
|
|
37
|
+
providerConnectOutputContract,
|
|
38
|
+
providerListOutputContract,
|
|
39
|
+
providerShowOutputContract,
|
|
40
|
+
} from './entities.js';
|
|
27
41
|
import type {
|
|
28
42
|
OperationOutputContract,
|
|
29
43
|
OperationOutputContractRegistry,
|
|
30
44
|
} from './output-contract.js';
|
|
45
|
+
import {
|
|
46
|
+
serviceConnectOutputContract,
|
|
47
|
+
serviceListOutputContract,
|
|
48
|
+
serviceRevokeOutputContract,
|
|
49
|
+
serviceStatusOutputContract,
|
|
50
|
+
} from './service.js';
|
|
31
51
|
|
|
32
52
|
// ---------------------------------------------------------------------------
|
|
33
53
|
// tasks.show — the operation that bit us (DHQ-057 reproduction)
|
|
@@ -334,6 +354,224 @@ const tasksCompleteOutputContract: OperationOutputContract = {
|
|
|
334
354
|
fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
|
|
335
355
|
};
|
|
336
356
|
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// tasks.reorder-rank / tasks.bulk-move / tasks.assignee — bulk Kanban mutate
|
|
359
|
+
// ops (T11786 · epic T11556). These ops have NO MUTATE_PROJECTION_PLANS entry,
|
|
360
|
+
// so their raw core result is returned under `envelope.data` unchanged.
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* OUTPUT contract for `tasks.reorder-rank`.
|
|
365
|
+
*
|
|
366
|
+
* Grounded in `TasksReorderRankResult`: `{ ranked, skipped, count }`. `ranked`
|
|
367
|
+
* is the new top-to-bottom order; `skipped` lists request IDs that did not
|
|
368
|
+
* resolve to a task.
|
|
369
|
+
*/
|
|
370
|
+
const tasksReorderRankOutputContract: OperationOutputContract = {
|
|
371
|
+
operation: 'tasks.reorder-rank',
|
|
372
|
+
shapeNote:
|
|
373
|
+
'Re-ranked IDs (new order) at /data/ranked; unresolved request IDs at /data/skipped; count at /data/count.',
|
|
374
|
+
dataSchema: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
required: ['ranked', 'skipped', 'count'],
|
|
377
|
+
additionalProperties: true,
|
|
378
|
+
properties: {
|
|
379
|
+
ranked: {
|
|
380
|
+
type: 'array',
|
|
381
|
+
description: 'Task IDs whose position was written, in the new top-to-bottom order.',
|
|
382
|
+
items: { type: 'string' },
|
|
383
|
+
},
|
|
384
|
+
skipped: {
|
|
385
|
+
type: 'array',
|
|
386
|
+
description: 'Request IDs that did not resolve to a task (no-op for these).',
|
|
387
|
+
items: { type: 'string' },
|
|
388
|
+
},
|
|
389
|
+
count: { type: 'number', description: 'Number of tasks re-ranked.' },
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
fieldPointers: ['/data/ranked/0', '/data/skipped/0', '/data/count'],
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* OUTPUT contract for `tasks.bulk-move`.
|
|
397
|
+
*
|
|
398
|
+
* Grounded in `TasksBulkMoveResult`: `{ moved, status?, pipelineStage?, count }`.
|
|
399
|
+
*/
|
|
400
|
+
const tasksBulkMoveOutputContract: OperationOutputContract = {
|
|
401
|
+
operation: 'tasks.bulk-move',
|
|
402
|
+
shapeNote:
|
|
403
|
+
'Moved task IDs at /data/moved (all-or-nothing); applied status/stage at /data/status and /data/pipelineStage; count at /data/count.',
|
|
404
|
+
dataSchema: {
|
|
405
|
+
type: 'object',
|
|
406
|
+
required: ['moved', 'count'],
|
|
407
|
+
additionalProperties: true,
|
|
408
|
+
properties: {
|
|
409
|
+
moved: {
|
|
410
|
+
type: 'array',
|
|
411
|
+
description: 'Task IDs successfully moved (atomic — empty only when count is 0).',
|
|
412
|
+
items: { type: 'string' },
|
|
413
|
+
},
|
|
414
|
+
status: { type: 'string', description: 'The status applied, when supplied.' },
|
|
415
|
+
pipelineStage: { type: 'string', description: 'The pipeline stage applied, when supplied.' },
|
|
416
|
+
count: { type: 'number', description: 'Number of tasks moved.' },
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
fieldPointers: ['/data/moved/0', '/data/status', '/data/pipelineStage', '/data/count'],
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* OUTPUT contract for `tasks.assignee`.
|
|
424
|
+
*
|
|
425
|
+
* Grounded in `TasksAssigneeResult`: `{ taskId, assignee, assigned }`. `assignee`
|
|
426
|
+
* is null when cleared; `assigned` is the boolean verb result.
|
|
427
|
+
*/
|
|
428
|
+
const tasksAssigneeOutputContract: OperationOutputContract = {
|
|
429
|
+
operation: 'tasks.assignee',
|
|
430
|
+
shapeNote:
|
|
431
|
+
'The new assignee is at /data/assignee (null when cleared); /data/assigned is true on set, false on clear.',
|
|
432
|
+
dataSchema: {
|
|
433
|
+
type: 'object',
|
|
434
|
+
required: ['taskId', 'assignee', 'assigned'],
|
|
435
|
+
additionalProperties: true,
|
|
436
|
+
properties: {
|
|
437
|
+
taskId: { type: 'string', description: 'The task ID whose assignee changed.' },
|
|
438
|
+
assignee: {
|
|
439
|
+
type: ['string', 'null'],
|
|
440
|
+
description: 'The new assignee value (null when cleared).',
|
|
441
|
+
},
|
|
442
|
+
assigned: { type: 'boolean', description: 'True when an assignee was set; false on clear.' },
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
fieldPointers: ['/data/taskId', '/data/assignee', '/data/assigned'],
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// admin.config.* — config-as-domain (T11917 · M5/AC3 · ConfigManifest cascade)
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* OUTPUT contract for `admin.config.get`.
|
|
454
|
+
*
|
|
455
|
+
* Grounded in `AdminConfigGetResult`: `{ key, scope, value, found }`. The
|
|
456
|
+
* resolved value lives at `/data/value`; `found` disambiguates a present `null`
|
|
457
|
+
* value from an absent key.
|
|
458
|
+
*/
|
|
459
|
+
const adminConfigGetOutputContract: OperationOutputContract = {
|
|
460
|
+
operation: 'admin.config.get',
|
|
461
|
+
shapeNote:
|
|
462
|
+
'The resolved value is at /data/value (may be null). /data/found is false when the key is absent.',
|
|
463
|
+
dataSchema: {
|
|
464
|
+
type: 'object',
|
|
465
|
+
required: ['key', 'scope', 'value', 'found'],
|
|
466
|
+
additionalProperties: true,
|
|
467
|
+
properties: {
|
|
468
|
+
key: { type: 'string', description: 'The dot-notation key that was resolved.' },
|
|
469
|
+
scope: {
|
|
470
|
+
type: 'string',
|
|
471
|
+
description: 'Cascade slice the value was resolved against.',
|
|
472
|
+
enum: ['global', 'project', 'merged'],
|
|
473
|
+
},
|
|
474
|
+
value: { description: 'Resolved value, or null when the key is absent.' },
|
|
475
|
+
found: { type: 'boolean', description: 'True IFF the key resolved to a defined value.' },
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
fieldPointers: ['/data/key', '/data/scope', '/data/value', '/data/found'],
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* OUTPUT contract for `admin.config.list`.
|
|
483
|
+
*
|
|
484
|
+
* Grounded in `AdminConfigListResult`: `{ scope, config, keys }`. The full
|
|
485
|
+
* resolved config object is at `/data/config`; flattened keys are at
|
|
486
|
+
* `/data/keys`.
|
|
487
|
+
*/
|
|
488
|
+
const adminConfigListOutputContract: OperationOutputContract = {
|
|
489
|
+
operation: 'admin.config.list',
|
|
490
|
+
shapeNote:
|
|
491
|
+
'The full resolved config is at /data/config (an object); flattened dot-notation keys are at /data/keys (an array).',
|
|
492
|
+
dataSchema: {
|
|
493
|
+
type: 'object',
|
|
494
|
+
required: ['scope', 'config', 'keys'],
|
|
495
|
+
additionalProperties: true,
|
|
496
|
+
properties: {
|
|
497
|
+
scope: {
|
|
498
|
+
type: 'string',
|
|
499
|
+
description: 'Cascade slice the config was resolved against.',
|
|
500
|
+
enum: ['global', 'project', 'merged'],
|
|
501
|
+
},
|
|
502
|
+
config: {
|
|
503
|
+
type: 'object',
|
|
504
|
+
description: 'Full resolved config object for the slice.',
|
|
505
|
+
additionalProperties: true,
|
|
506
|
+
},
|
|
507
|
+
keys: {
|
|
508
|
+
type: 'array',
|
|
509
|
+
description: 'Flattened dot-notation keys present in the resolved config.',
|
|
510
|
+
items: { type: 'string' },
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
fieldPointers: ['/data/scope', '/data/keys/0', '/data/keys'],
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* OUTPUT contract for `admin.config.validate`.
|
|
519
|
+
*
|
|
520
|
+
* Grounded in `AdminConfigValidateResult`: `{ scope, ok, issues }`. `ok` is the
|
|
521
|
+
* pass/fail verdict; `issues` carries human-readable rejection reasons.
|
|
522
|
+
*/
|
|
523
|
+
const adminConfigValidateOutputContract: OperationOutputContract = {
|
|
524
|
+
operation: 'admin.config.validate',
|
|
525
|
+
shapeNote:
|
|
526
|
+
'The pass/fail verdict is at /data/ok; rejection reasons are at /data/issues (empty when ok=true).',
|
|
527
|
+
dataSchema: {
|
|
528
|
+
type: 'object',
|
|
529
|
+
required: ['scope', 'ok', 'issues'],
|
|
530
|
+
additionalProperties: true,
|
|
531
|
+
properties: {
|
|
532
|
+
scope: {
|
|
533
|
+
type: 'string',
|
|
534
|
+
description: 'Scope that was validated.',
|
|
535
|
+
enum: ['global', 'project'],
|
|
536
|
+
},
|
|
537
|
+
ok: { type: 'boolean', description: 'True IFF every gate passed.' },
|
|
538
|
+
issues: {
|
|
539
|
+
type: 'array',
|
|
540
|
+
description: 'Human-readable schema issues. Empty when ok=true.',
|
|
541
|
+
items: { type: 'string' },
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
fieldPointers: ['/data/scope', '/data/ok', '/data/issues/0', '/data/issues'],
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* OUTPUT contract for `admin.config.unset`.
|
|
550
|
+
*
|
|
551
|
+
* Grounded in `AdminConfigUnsetResult`: `{ key, scope, removed }`. `removed` is
|
|
552
|
+
* `true` IFF the key existed and was deleted (idempotent: `false` when absent).
|
|
553
|
+
*/
|
|
554
|
+
const adminConfigUnsetOutputContract: OperationOutputContract = {
|
|
555
|
+
operation: 'admin.config.unset',
|
|
556
|
+
shapeNote:
|
|
557
|
+
'/data/removed is true IFF a value was deleted; false when the key was already absent (idempotent).',
|
|
558
|
+
dataSchema: {
|
|
559
|
+
type: 'object',
|
|
560
|
+
required: ['key', 'scope', 'removed'],
|
|
561
|
+
additionalProperties: true,
|
|
562
|
+
properties: {
|
|
563
|
+
key: { type: 'string', description: 'The dot-notation key that was targeted.' },
|
|
564
|
+
scope: {
|
|
565
|
+
type: 'string',
|
|
566
|
+
description: 'Scope the key was removed from.',
|
|
567
|
+
enum: ['project', 'global'],
|
|
568
|
+
},
|
|
569
|
+
removed: { type: 'boolean', description: 'True IFF a value was actually deleted.' },
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
fieldPointers: ['/data/key', '/data/scope', '/data/removed'],
|
|
573
|
+
};
|
|
574
|
+
|
|
337
575
|
// ---------------------------------------------------------------------------
|
|
338
576
|
// Registry
|
|
339
577
|
// ---------------------------------------------------------------------------
|
|
@@ -353,4 +591,30 @@ export const OUTPUT_CONTRACTS: OperationOutputContractRegistry = {
|
|
|
353
591
|
'tasks.add-batch': tasksAddBatchOutputContract,
|
|
354
592
|
'tasks.update': tasksUpdateOutputContract,
|
|
355
593
|
'tasks.complete': tasksCompleteOutputContract,
|
|
594
|
+
// T11786 (epic T11556) — bulk Kanban mutate ops.
|
|
595
|
+
'tasks.reorder-rank': tasksReorderRankOutputContract,
|
|
596
|
+
'tasks.bulk-move': tasksBulkMoveOutputContract,
|
|
597
|
+
'tasks.assignee': tasksAssigneeOutputContract,
|
|
598
|
+
'admin.config.get': adminConfigGetOutputContract,
|
|
599
|
+
'admin.config.list': adminConfigListOutputContract,
|
|
600
|
+
'admin.config.validate': adminConfigValidateOutputContract,
|
|
601
|
+
'admin.config.unset': adminConfigUnsetOutputContract,
|
|
602
|
+
// service-vault CLI verbs (T11941 · epic T11765 · M2-W4)
|
|
603
|
+
'service.connect': serviceConnectOutputContract,
|
|
604
|
+
'service.list': serviceListOutputContract,
|
|
605
|
+
'service.revoke': serviceRevokeOutputContract,
|
|
606
|
+
'service.status': serviceStatusOutputContract,
|
|
607
|
+
// 5-entity provider-experience ops (T11700 · epic T11666)
|
|
608
|
+
'account.add': accountAddOutputContract,
|
|
609
|
+
'account.list': accountListOutputContract,
|
|
610
|
+
'account.remove': accountRemoveOutputContract,
|
|
611
|
+
'provider.list': providerListOutputContract,
|
|
612
|
+
'provider.show': providerShowOutputContract,
|
|
613
|
+
'provider.connect': providerConnectOutputContract,
|
|
614
|
+
'model.query': modelQueryOutputContract,
|
|
615
|
+
'model.show': modelShowOutputContract,
|
|
616
|
+
'profile.create': profileCreateOutputContract,
|
|
617
|
+
'profile.list': profileListOutputContract,
|
|
618
|
+
'profile.pin': profilePinOutputContract,
|
|
619
|
+
'profile.use': profileUseOutputContract,
|
|
356
620
|
};
|
|
@@ -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
|
+
};
|