@cleocode/contracts 2026.5.134 → 2026.6.0

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 (61) hide show
  1. package/dist/__tests__/operations-registry.test.js +13 -0
  2. package/dist/__tests__/operations-registry.test.js.map +1 -1
  3. package/dist/__tests__/skill-executor-contract.test.d.ts +14 -0
  4. package/dist/__tests__/skill-executor-contract.test.d.ts.map +1 -0
  5. package/dist/__tests__/skill-executor-contract.test.js +85 -0
  6. package/dist/__tests__/skill-executor-contract.test.js.map +1 -0
  7. package/dist/backup-manifest.d.ts +1 -1
  8. package/dist/brain-graph.d.ts +2 -2
  9. package/dist/brain-graph.d.ts.map +1 -1
  10. package/dist/config.d.ts +71 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/db-inventory.json +4 -4
  13. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  14. package/dist/dispatch/operations-registry.js +44 -0
  15. package/dist/dispatch/operations-registry.js.map +1 -1
  16. package/dist/exit-codes.d.ts +20 -1
  17. package/dist/exit-codes.d.ts.map +1 -1
  18. package/dist/exit-codes.js +19 -0
  19. package/dist/exit-codes.js.map +1 -1
  20. package/dist/index.d.ts +4 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/migration-parity.d.ts +174 -0
  25. package/dist/migration-parity.d.ts.map +1 -0
  26. package/dist/migration-parity.js +39 -0
  27. package/dist/migration-parity.js.map +1 -0
  28. package/dist/operations/admin.d.ts +4 -2
  29. package/dist/operations/admin.d.ts.map +1 -1
  30. package/dist/operations/brain.d.ts +2 -2
  31. package/dist/operations/brain.d.ts.map +1 -1
  32. package/dist/operations/llm.d.ts +6 -3
  33. package/dist/operations/llm.d.ts.map +1 -1
  34. package/dist/operations/tasks.d.ts +10 -0
  35. package/dist/operations/tasks.d.ts.map +1 -1
  36. package/dist/operations/tasks.js.map +1 -1
  37. package/dist/sub-accessors.d.ts +4 -4
  38. package/dist/sub-accessors.d.ts.map +1 -1
  39. package/dist/sub-accessors.js +1 -1
  40. package/dist/tools/skill-executor.d.ts +156 -0
  41. package/dist/tools/skill-executor.d.ts.map +1 -0
  42. package/dist/tools/skill-executor.js +47 -0
  43. package/dist/tools/skill-executor.js.map +1 -0
  44. package/package.json +6 -2
  45. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  46. package/src/__tests__/operations-registry.test.ts +14 -0
  47. package/src/__tests__/skill-executor-contract.test.ts +104 -0
  48. package/src/backup-manifest.ts +1 -1
  49. package/src/brain-graph.ts +2 -2
  50. package/src/config.ts +73 -1
  51. package/src/db-inventory.json +4 -4
  52. package/src/dispatch/operations-registry.ts +45 -0
  53. package/src/exit-codes.ts +20 -0
  54. package/src/index.ts +11 -1
  55. package/src/migration-parity.ts +178 -0
  56. package/src/operations/admin.ts +4 -2
  57. package/src/operations/brain.ts +2 -2
  58. package/src/operations/llm.ts +12 -4
  59. package/src/operations/tasks.ts +10 -0
  60. package/src/sub-accessors.ts +5 -5
  61. package/src/tools/skill-executor.ts +183 -0
@@ -50,4 +50,18 @@ describe('operations-registry', () => {
50
50
  const result = defineDomain('tasks', ops);
51
51
  expect(result).toBe(ops);
52
52
  });
53
+
54
+ it('registers tasks.relates.add-batch as a mutate op (T11575)', () => {
55
+ // Regression guard for T11575: the handler + CLI command shipped but the
56
+ // OperationDef was missing, so the dispatcher rejected every call with
57
+ // E_INVALID_OPERATION before it reached the handler.
58
+ const op = OPERATIONS.find((o) => o.domain === 'tasks' && o.operation === 'relates.add-batch');
59
+ expect(op).toBeDefined();
60
+ expect(op?.gateway).toBe('mutate');
61
+ // The CLI accepts either { relations:[...] } or { edges:[...] }, so neither
62
+ // is individually required at the dispatch boundary.
63
+ expect(op?.requiredParams).toEqual([]);
64
+ const paramNames = (op?.params ?? []).map((p) => p.name);
65
+ expect(paramNames).toEqual(expect.arrayContaining(['relations', 'edges', 'dryRun']));
66
+ });
53
67
  });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Contract tests for the `SkillExecutor` DIP seam
3
+ * (`@cleocode/contracts/tools/skill-executor`, P1 · T11476).
4
+ *
5
+ * Pins the dispatcher-facing abstraction so the concrete in-process
6
+ * `SkillExecutorAdapter` (T11477) binds to a stable, runtime-decoupled shape.
7
+ * Types-only module — these are type-level assertions exercised via a fake.
8
+ *
9
+ * @epic T11391
10
+ * @task T11476
11
+ * @saga T11387
12
+ */
13
+
14
+ import { describe, expect, it } from 'vitest';
15
+ import type {
16
+ ExecuteShellResult,
17
+ PathExistsResult,
18
+ ReadFileResult,
19
+ WriteFileResult,
20
+ } from '../tools/atomic.js';
21
+ import type {
22
+ GuardedToolSurface,
23
+ SkillExecuteInput,
24
+ SkillExecuteResult,
25
+ SkillExecuteStatus,
26
+ SkillExecutor,
27
+ } from '../tools/skill-executor.js';
28
+
29
+ /** Minimal in-test guarded surface — every method routes to a typed stub. */
30
+ const fakeTools: GuardedToolSurface = {
31
+ async readFileText(input): Promise<ReadFileResult> {
32
+ return { path: input.path, content: '' };
33
+ },
34
+ async readJson<T>(_path: string): Promise<T> {
35
+ return {} as T;
36
+ },
37
+ async writeFileAtomic(input): Promise<WriteFileResult> {
38
+ return { path: input.path, bytesWritten: 0 };
39
+ },
40
+ async pathExists(_input): Promise<PathExistsResult> {
41
+ return { exists: false };
42
+ },
43
+ async executeShell(_input): Promise<ExecuteShellResult> {
44
+ return { stdout: '', stderr: '', code: 0 };
45
+ },
46
+ async runGit(_input): Promise<ExecuteShellResult> {
47
+ return { stdout: '', stderr: '', code: 0 };
48
+ },
49
+ };
50
+
51
+ /** A fake SkillExecutor — proves the abstraction is implementable by injection. */
52
+ const fakeExecutor: SkillExecutor = {
53
+ async execute(input: SkillExecuteInput): Promise<SkillExecuteResult> {
54
+ if (input.skillId === 'boom') {
55
+ return { status: 'failure', output: {}, error: 'kaboom' };
56
+ }
57
+ return { status: 'success', output: { ranSkill: input.skillId } };
58
+ },
59
+ };
60
+
61
+ describe('SkillExecutor DIP seam (AC1)', () => {
62
+ it('execute() accepts { skillId, context, tools } and resolves a terminal envelope', async () => {
63
+ const result = await fakeExecutor.execute({
64
+ skillId: 'noop',
65
+ context: { foo: 'bar' },
66
+ tools: fakeTools,
67
+ });
68
+ expect(result.status).toBe<SkillExecuteStatus>('success');
69
+ expect(result.output).toEqual({ ranSkill: 'noop' });
70
+ expect(result.error).toBeUndefined();
71
+ });
72
+
73
+ it('surfaces a failure envelope with status + error', async () => {
74
+ const result = await fakeExecutor.execute({
75
+ skillId: 'boom',
76
+ context: {},
77
+ tools: fakeTools,
78
+ });
79
+ expect(result.status).toBe<SkillExecuteStatus>('failure');
80
+ expect(result.error).toBe('kaboom');
81
+ expect(result.output).toEqual({});
82
+ });
83
+ });
84
+
85
+ describe('GuardedToolSurface dependency (AC2)', () => {
86
+ it('the injected tools surface exposes the guarded atomic primitives', async () => {
87
+ const input: SkillExecuteInput = { skillId: 's', context: {}, tools: fakeTools };
88
+ const read = await input.tools.readFileText({ path: '/abs/x.ts' });
89
+ expect(read.path).toBe('/abs/x.ts');
90
+ const wrote = await input.tools.writeFileAtomic({ path: '/abs/y.ts', content: 'z' });
91
+ expect(wrote.bytesWritten).toBe(0);
92
+ const shell = await input.tools.executeShell({ command: 'true' });
93
+ expect(shell.code).toBe(0);
94
+ const git = await input.tools.runGit({ args: ['status'] });
95
+ expect(git.code).toBe(0);
96
+ });
97
+ });
98
+
99
+ describe('result-status taxonomy', () => {
100
+ it('SkillExecuteStatus is exactly success | failure', () => {
101
+ const statuses: SkillExecuteStatus[] = ['success', 'failure'];
102
+ expect(statuses).toEqual(['success', 'failure']);
103
+ });
104
+ });
@@ -76,7 +76,7 @@ export interface BackupDatabaseEntry {
76
76
  /**
77
77
  * Latest applied migration identifier.
78
78
  * For Drizzle-managed DBs: folderMillis string (e.g. "20260327000000").
79
- * For signaldock.db: SIGNALDOCK_SCHEMA_VERSION constant.
79
+ * For signaldock.db: AGENT_REGISTRY_SCHEMA_VERSION constant.
80
80
  */
81
81
  schemaVersion: string;
82
82
  /**
@@ -62,7 +62,7 @@ export type BrainNodeKind =
62
62
  * that bridge runtime output to the wire format translate between the two
63
63
  * naming planes at the adapter boundary.
64
64
  */
65
- export type BrainSubstrate = 'brain' | 'nexus' | 'tasks' | 'conduit' | 'signaldock';
65
+ export type BrainSubstrate = 'brain' | 'nexus' | 'tasks' | 'conduit' | 'agent-registry';
66
66
 
67
67
  // ---------------------------------------------------------------------------
68
68
  // Canonical graph node
@@ -77,7 +77,7 @@ export type BrainSubstrate = 'brain' | 'nexus' | 'tasks' | 'conduit' | 'signaldo
77
77
  * - `"nexus:sym-123"` — a nexus code symbol
78
78
  * - `"tasks:T949"` — a CLEO task
79
79
  * - `"conduit:msg-7f3a2b1c"` — a conduit message
80
- * - `"signaldock:agent-cleo-prime"` — a SignalDock agent
80
+ * - `"agent-registry:agent-cleo-prime"` — an Agent Registry agent
81
81
  *
82
82
  * @remarks
83
83
  * This is the shape produced by all substrate adapters in `@cleocode/brain`
package/src/config.ts CHANGED
@@ -451,6 +451,58 @@ export interface LlmRoleConfig {
451
451
  * inherits the default credential resolution order.
452
452
  */
453
453
  credentialLabel?: string;
454
+ /**
455
+ * Optional reference to a named profile in {@link LlmConfig.profiles}. When
456
+ * set, the named profile's `provider` / `model` / `credentialLabel` take
457
+ * precedence over this entry's inline `provider` / `model` — letting many
458
+ * roles share one reusable provider+model definition (hermes-agent's
459
+ * profile-mapping model).
460
+ *
461
+ * @task T11617
462
+ */
463
+ profile?: string;
464
+ }
465
+
466
+ /**
467
+ * Tuning parameters carried by a named LLM profile.
468
+ *
469
+ * @task T11617
470
+ */
471
+ export interface LlmProfileTuning {
472
+ /** Default maximum tokens to generate for calls bound to this profile. */
473
+ maxTokens?: number;
474
+ /** Default sampling temperature (0.0–1.0) for calls bound to this profile. */
475
+ temperature?: number;
476
+ }
477
+
478
+ /**
479
+ * A named, reusable LLM profile.
480
+ *
481
+ * A profile names a `provider` + `model` (+ optional credential label and
482
+ * tuning params) ONCE so that multiple call-sites/roles can bind to it by name
483
+ * instead of repeating the inline tuple. This is the "Profile" abstraction
484
+ * modeled on hermes-agent's `auxiliary.<task>` provider bindings — the user
485
+ * selects which provider fulfills a profile, and roles map to profiles.
486
+ *
487
+ * Profiles live in {@link LlmConfig.profiles}; roles reference them via
488
+ * {@link LlmRoleConfig.profile} and the default binding via
489
+ * {@link LlmConfig.defaultProfile}.
490
+ *
491
+ * @task T11617
492
+ */
493
+ export interface LlmProfileConfig {
494
+ /** LLM provider transport this profile resolves to. */
495
+ provider: LlmProviderTransport;
496
+ /** Full model identifier for the selected provider. */
497
+ model: string;
498
+ /**
499
+ * Optional credential label pinning this profile to a specific credential
500
+ * pool entry (e.g. `'codex-cli'` for the OpenAI Codex OAuth token). When
501
+ * omitted, the standard priority-based credential resolution applies.
502
+ */
503
+ credentialLabel?: string;
504
+ /** Optional tuning parameters (max tokens, temperature). */
505
+ params?: LlmProfileTuning;
454
506
  }
455
507
 
456
508
  /**
@@ -482,11 +534,31 @@ export interface LlmConfig {
482
534
  /**
483
535
  * Per-role LLM overrides. Each role optionally pins to a credential label.
484
536
  *
485
- * Resolution order: `roles[role]` → `default` → implicit fallback.
537
+ * Resolution order: `roles[role].profile` → `roles[role]` (inline) →
538
+ * `defaultProfile` → `default` → implicit fallback.
486
539
  *
487
540
  * @task T-LLM-CRED-CENTRALIZATION Phase 4 (T9306)
488
541
  */
489
542
  roles?: Partial<Record<RoleName, LlmRoleConfig>>;
543
+ /**
544
+ * Named, reusable LLM profiles keyed by profile name. Roles bind to a
545
+ * profile via {@link LlmRoleConfig.profile}; the unscoped default binding is
546
+ * {@link LlmConfig.defaultProfile}. Modeled on hermes-agent's
547
+ * provider→profile mapping for "what to use when".
548
+ *
549
+ * @task T11617
550
+ */
551
+ profiles?: Record<string, LlmProfileConfig>;
552
+ /**
553
+ * Name of the profile (a key of {@link LlmConfig.profiles}) used for any
554
+ * role that does NOT pin its own provider/model/profile. This is the
555
+ * user-selectable default binding for background roles — it is NEVER a
556
+ * hardcoded provider in code; the owner re-selects it with
557
+ * `cleo llm bind --default <profileName>`.
558
+ *
559
+ * @task T11617
560
+ */
561
+ defaultProfile?: string;
490
562
  }
491
563
 
492
564
  /** SignalDock transport mode. */
@@ -87,8 +87,8 @@
87
87
  "role": "signaldock-project",
88
88
  "tier": "project",
89
89
  "filePathTemplate": "<projectRoot>/.cleo/signaldock.db",
90
- "drizzleSchemaPath": "packages/core/src/store/schema/signaldock-schema.ts",
91
- "migrationsDir": "packages/core/migrations/drizzle-signaldock/",
90
+ "drizzleSchemaPath": "packages/core/src/store/schema/agent-registry-schema.ts",
91
+ "migrationsDir": "packages/core/migrations/drizzle-agent-registry/",
92
92
  "ownerPackage": "@cleocode/core",
93
93
  "openedVia": "HISTORICAL — project-tier signaldock.db was migrated into conduit.db post-T310/ADR-037. No live opener; charter row retained for migration provenance and legacy backup detection.",
94
94
  "concurrency": "single-writer",
@@ -100,8 +100,8 @@
100
100
  "role": "signaldock-global",
101
101
  "tier": "global",
102
102
  "filePathTemplate": "$XDG_DATA_HOME/cleo/signaldock.db",
103
- "drizzleSchemaPath": "packages/core/src/store/schema/signaldock-schema.ts",
104
- "migrationsDir": "packages/core/migrations/drizzle-signaldock/",
103
+ "drizzleSchemaPath": "packages/core/src/store/schema/agent-registry-schema.ts",
104
+ "migrationsDir": "packages/core/migrations/drizzle-agent-registry/",
105
105
  "ownerPackage": "@cleocode/core",
106
106
  "openedVia": "openCleoDb('signaldock')",
107
107
  "concurrency": "single-writer",
@@ -2676,6 +2676,51 @@ export const OPERATIONS: OperationDef[] = [
2676
2676
  },
2677
2677
  ] satisfies ParamDef[],
2678
2678
  },
2679
+ {
2680
+ // T11575 — the handler (dispatch/domains/tasks.ts 'relates.add-batch') and
2681
+ // CLI command (cli/commands/relates.ts add-batch) shipped, but no
2682
+ // OperationDef existed here, so `resolve()` returned undefined and the
2683
+ // dispatcher rejected the call with E_INVALID_OPERATION before it ever
2684
+ // reached the handler. Registering the op (gateway:'mutate') closes the gap.
2685
+ gateway: 'mutate',
2686
+ domain: 'tasks',
2687
+ operation: 'relates.add-batch',
2688
+ description:
2689
+ 'tasks.relates.add-batch (mutate) — bulk-add advisory relation edges from a JSON payload ({relations:[...]} or {edges:[...]}); use dryRun to preview without writing.',
2690
+ tier: 1,
2691
+ idempotent: false,
2692
+ sessionRequired: false,
2693
+ // Neither `relations` nor `edges` is individually required — the handler
2694
+ // accepts either shape and validates emptiness in core — so no param is
2695
+ // declared required at the dispatch boundary.
2696
+ requiredParams: [],
2697
+ params: [
2698
+ {
2699
+ name: 'relations',
2700
+ type: 'array',
2701
+ required: false,
2702
+ description: 'Array of relation edges ({from|taskId, to|relatedId, type, reason})',
2703
+ },
2704
+ {
2705
+ name: 'edges',
2706
+ type: 'array',
2707
+ required: false,
2708
+ description: 'Alias for `relations` — array of relation edges',
2709
+ },
2710
+ {
2711
+ name: 'dryRun',
2712
+ type: 'boolean',
2713
+ required: false,
2714
+ description: 'Validate and report what would be created without writing',
2715
+ },
2716
+ {
2717
+ name: 'reasonWaiver',
2718
+ type: 'string',
2719
+ required: false,
2720
+ description: 'Explicit audit waiver text for edges without per-edge reasons',
2721
+ },
2722
+ ] satisfies ParamDef[],
2723
+ },
2679
2724
  {
2680
2725
  gateway: 'mutate',
2681
2726
  domain: 'tasks',
package/src/exit-codes.ts CHANGED
@@ -247,6 +247,26 @@ export enum ExitCode {
247
247
  * @adr ADR-079-r4
248
248
  */
249
249
  AC_COVERAGE_INCOMPLETE = 108,
250
+
251
+ /**
252
+ * E_CANCELLED_CHILD_NO_WAIVER — `cleo complete <parentId>` was rejected
253
+ * because the parent has one or more `cancelled` children that have NOT been
254
+ * waived or replaced. A cancelled child represents work that was NOT done;
255
+ * silently treating it as satisfied lets a parent complete as if cancelled
256
+ * work had shipped (PM-Core V2 design-point 4 — agent-trust completion).
257
+ *
258
+ * `error.details.cancelledChildIds` lists the offending children. Recovery:
259
+ * 1. Record an audited waiver for the cancelled children via
260
+ * `cleo complete <parentId> --waive-cancelled-children "<reason>"`
261
+ * (logged to `.cleo/audit/cancelled-child-waiver.jsonl`).
262
+ * 2. Re-open and finish the work (the child stops being cancelled).
263
+ * 3. Replace the cancelled work with a new done child and waive the
264
+ * original, citing the replacement task id in the reason.
265
+ *
266
+ * @codeName E_CANCELLED_CHILD_NO_WAIVER
267
+ * @saga T10538 (PM-Core V2 agent-trust)
268
+ */
269
+ CANCELLED_CHILD_NO_WAIVER = 109,
250
270
  }
251
271
 
252
272
  // ---------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -263,6 +263,8 @@ export type {
263
263
  LifecycleEnforcementMode,
264
264
  LlmConfig,
265
265
  LlmDefaultConfig,
266
+ LlmProfileConfig,
267
+ LlmProfileTuning,
266
268
  LlmProviderEntry,
267
269
  LlmProviderTransport,
268
270
  LlmRoleConfig,
@@ -829,6 +831,14 @@ export type {
829
831
  PatternRecord,
830
832
  SessionSummary,
831
833
  } from './memory.js';
834
+ export type {
835
+ MigrationEnumDrift,
836
+ MigrationForeignKeyViolation,
837
+ MigrationTableParity,
838
+ VerifyMigrationResult,
839
+ } from './migration-parity.js';
840
+ // === Migration Parity (T11551 — DHQ-045 exodus zero-loss durable guard) ===
841
+ export { MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT } from './migration-parity.js';
832
842
  // === MVI progressive-disclosure primitive (T11349 · Epic T11285 · Saga T11283) ===
833
843
  export type {
834
844
  ExpansionHint,
@@ -1939,12 +1949,12 @@ export {
1939
1949
  } from './status-registry.js';
1940
1950
  // === Sub-Accessor Contracts (T9188) ===
1941
1951
  export type {
1952
+ AgentRegistrySubAccessor,
1942
1953
  BrainAccessor,
1943
1954
  BrainMemoryHit,
1944
1955
  BrainObserveParams,
1945
1956
  ConduitAccessor,
1946
1957
  NexusAccessor,
1947
- SignaldockAccessor,
1948
1958
  TelemetryAccessor,
1949
1959
  } from './sub-accessors.js';
1950
1960
  export type {
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Migration-parity contracts — the structured result of a generic
3
+ * source→target SQLite migration equivalence check.
4
+ *
5
+ * These types describe the output of the reusable CORE `verifyMigration()`
6
+ * primitive (T11551 · DHQ-045), the durable guard that prevents the exodus
7
+ * dual-DB migration from silently losing rows ever again.
8
+ *
9
+ * ## Why a structured contract
10
+ *
11
+ * The exodus zero-loss campaign (T11531/46/47/48/49/T11550) lost ~805K rows
12
+ * as-shipped because the original verify path returned a loose boolean and a
13
+ * free-text error. A typed parity report makes each failure class
14
+ * machine-inspectable:
15
+ *
16
+ * - **Row-count parity** — per (source-table → target-table) row counts.
17
+ * - **Foreign-key integrity** — the `PRAGMA foreign_key_check` rows on the
18
+ * consolidated target (genuine data orphans, not copy-order artifacts).
19
+ * - **Content checksum** — an ordered canonical-JSON digest per table, over
20
+ * the intersection of source/target columns, so content drift is caught
21
+ * even when counts match.
22
+ * - **Enum/type drift** — values present in a source column that are NOT in
23
+ * the target column's CHECK enum. This is the exact failure class that
24
+ * `INSERT OR IGNORE` used to swallow silently.
25
+ *
26
+ * @task T11551 (DHQ-045 — exodus zero-loss durable guard)
27
+ * @epic T10878
28
+ * @saga T11242
29
+ * @adr ADR-068, ADR-069
30
+ * @public
31
+ */
32
+
33
+ /**
34
+ * Per-table row-count + content-checksum parity entry.
35
+ *
36
+ * Produced for every legacy source table that maps to a consolidated target
37
+ * table. `countMatch` and `hashMatch` together decide whether the table copied
38
+ * losslessly: a `false` on either is a parity failure.
39
+ *
40
+ * @public
41
+ */
42
+ export interface MigrationTableParity {
43
+ /** Physical table name in the legacy source DB. */
44
+ readonly sourceTable: string;
45
+ /** Physical table name in the consolidated target DB (after name-mapping). */
46
+ readonly targetTable: string;
47
+ /**
48
+ * Logical scope label for the target table (`'project'` | `'global'` for
49
+ * exodus; an opaque string for other migrations).
50
+ */
51
+ readonly scope: string;
52
+ /** Row count in the source legacy table. */
53
+ readonly sourceCount: number;
54
+ /** Row count in the consolidated target table. */
55
+ readonly targetCount: number;
56
+ /** Ordered canonical-JSON SHA-256 digest (32 hex) of the source rows. */
57
+ readonly sourceHash: string;
58
+ /** Ordered canonical-JSON SHA-256 digest (32 hex) of the target rows. */
59
+ readonly targetHash: string;
60
+ /** `true` when `sourceCount === targetCount`. */
61
+ readonly countMatch: boolean;
62
+ /** `true` when `sourceHash === targetHash`. */
63
+ readonly hashMatch: boolean;
64
+ }
65
+
66
+ /**
67
+ * A single orphan row surfaced by `PRAGMA foreign_key_check` on the
68
+ * consolidated target after migration.
69
+ *
70
+ * Each entry mirrors one row of the `PRAGMA foreign_key_check` result: the
71
+ * child `table`, the offending `rowid`, the referenced `parent` table, and the
72
+ * `fkid` (the index of the foreign-key constraint within that table).
73
+ *
74
+ * A non-empty list means the migrated data has referential orphans — NOT
75
+ * copy-order artifacts (the migration copies with `foreign_keys=OFF` and checks
76
+ * afterward). The parity gate fails only on orphans the migration *introduced*
77
+ * (those absent from the source); pre-existing source orphans are carried
78
+ * forward losslessly and tolerated (T11572).
79
+ *
80
+ * @public
81
+ */
82
+ export interface MigrationForeignKeyViolation {
83
+ /** Child table containing the orphan row. */
84
+ readonly table: string;
85
+ /** `rowid` of the orphan row (or `null` for WITHOUT ROWID tables). */
86
+ readonly rowid: number | null;
87
+ /** Referenced parent table that has no matching row. */
88
+ readonly parent: string;
89
+ /** Index of the violated foreign-key constraint within `table`. */
90
+ readonly fkid: number;
91
+ }
92
+
93
+ /**
94
+ * An enum/type-drift finding — a value present in a source column that the
95
+ * target column's CHECK constraint enum does NOT accept.
96
+ *
97
+ * This is the exact class that `INSERT OR IGNORE` silently dropped during the
98
+ * un-hardened exodus migration. The migration layer normalises known drift
99
+ * (legacy enum aliases, epoch→ISO timestamps) before insert; any value that
100
+ * still cannot be coerced surfaces here so the gate fails loudly rather than
101
+ * dropping rows.
102
+ *
103
+ * @public
104
+ */
105
+ export interface MigrationEnumDrift {
106
+ /** Physical target table name whose column has the CHECK enum. */
107
+ readonly targetTable: string;
108
+ /** Column name carrying the CHECK enum on the target. */
109
+ readonly column: string;
110
+ /**
111
+ * Up to {@link MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT} distinct source values that
112
+ * are NOT members of the target enum. Truncated for legibility; `driftCount`
113
+ * is the true total of out-of-enum source rows.
114
+ */
115
+ readonly offendingValues: readonly string[];
116
+ /** The canonical enum members accepted by the target CHECK constraint. */
117
+ readonly allowedValues: readonly string[];
118
+ /** Total number of source rows whose value is outside the target enum. */
119
+ readonly driftCount: number;
120
+ }
121
+
122
+ /**
123
+ * Maximum number of distinct offending values captured per
124
+ * {@link MigrationEnumDrift} entry, to keep the report bounded.
125
+ *
126
+ * @public
127
+ */
128
+ export const MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT = 20 as const;
129
+
130
+ /**
131
+ * Aggregate result of {@link MigrationTableParity} / FK / enum-drift checks for
132
+ * a single source→target migration verification pass.
133
+ *
134
+ * `ok === true` IFF every table has `countMatch && hashMatch`, the
135
+ * `introducedForeignKeyViolations` list is empty (pre-existing source orphans do
136
+ * NOT fail the strict check — T11572), AND no {@link MigrationEnumDrift} was
137
+ * detected. When `ok === false`, `error` is ALWAYS populated with a
138
+ * human-readable failure summary (the false-pass guard from T11531).
139
+ *
140
+ * @public
141
+ */
142
+ export interface VerifyMigrationResult {
143
+ /** `true` only when every parity, FK, and enum-drift check passed. */
144
+ readonly ok: boolean;
145
+ /** Per-table row-count + checksum parity entries. */
146
+ readonly tables: readonly MigrationTableParity[];
147
+ /**
148
+ * ALL orphan rows from `PRAGMA foreign_key_check` on the consolidated target —
149
+ * both pre-existing (already orphaned in the SOURCE) and migration-introduced.
150
+ *
151
+ * NOTE: this list is NOT, on its own, a data-loss signal. A legacy source DB
152
+ * can carry pre-existing orphans (rows whose parent was deleted before the
153
+ * migration); those copy through faithfully (zero loss) and are NOT a migration
154
+ * failure. Use {@link introducedForeignKeyViolations} for the data-loss gate.
155
+ */
156
+ readonly foreignKeyViolations: readonly MigrationForeignKeyViolation[];
157
+ /**
158
+ * Orphans present on the consolidated TARGET that the SOURCE did NOT already
159
+ * have — i.e. referential integrity the migration *worsened* (a parent row the
160
+ * migration dropped). This is the FK class that indicates genuine data loss and
161
+ * is the one the cutover parity gate fails on. Empty when the migration only
162
+ * carried forward pre-existing source orphans (T11572).
163
+ */
164
+ readonly introducedForeignKeyViolations: readonly MigrationForeignKeyViolation[];
165
+ /**
166
+ * Orphans that ALREADY existed in the SOURCE before migration (a data-hygiene
167
+ * pre-condition, not a migration defect). Carried forward as-is (zero loss);
168
+ * surfaced as a WARN for a follow-up clean-up, never an abort (T11572).
169
+ */
170
+ readonly preExistingForeignKeyViolations: readonly MigrationForeignKeyViolation[];
171
+ /** Enum/type-drift findings (source values outside the target CHECK enum). */
172
+ readonly enumDrift: readonly MigrationEnumDrift[];
173
+ /**
174
+ * Human-readable failure summary, populated whenever `ok === false`.
175
+ * `undefined` on success.
176
+ */
177
+ readonly error?: string;
178
+ }
@@ -30,9 +30,11 @@
30
30
 
31
31
  /**
32
32
  * Known transport categories for token recording.
33
- * Mirrors `TokenTransport` from `@cleocode/core`.
33
+ * Mirrors `TokenTransport` from `@cleocode/core`. `'mcp'` is a first-class
34
+ * origin (MCP-gateway requests); it is preserved verbatim, never coerced to
35
+ * `'agent'` (T11649).
34
36
  */
35
- export type AdminTokenTransport = 'cli' | 'api' | 'agent' | 'unknown';
37
+ export type AdminTokenTransport = 'cli' | 'api' | 'agent' | 'mcp' | 'unknown';
36
38
 
37
39
  /**
38
40
  * Token measurement method classification.
@@ -55,7 +55,7 @@
55
55
  *
56
56
  * @task T962 / T968
57
57
  */
58
- export type BrainSubstrateName = 'memory' | 'nexus' | 'tasks' | 'conduit' | 'signaldock';
58
+ export type BrainSubstrateName = 'memory' | 'nexus' | 'tasks' | 'conduit' | 'agent-registry';
59
59
 
60
60
  /**
61
61
  * Concrete node type within a substrate (e.g. `observation`, `symbol`,
@@ -75,7 +75,7 @@ export type BrainNodeType = string;
75
75
  * "memory:O-mo4abc123"
76
76
  * "nexus:packages/core/src/store/sqlite-data-accessor.ts::createSqliteDataAccessor"
77
77
  * "conduit:msg-7f3a2b1c"
78
- * "signaldock:agent-cleo-prime"
78
+ * "agent-registry:agent-cleo-prime"
79
79
  *
80
80
  * @remarks
81
81
  * The substrate prefix (before the first `:`) MUST match one of
@@ -205,14 +205,22 @@ export interface CredentialResultWire {
205
205
  *
206
206
  * Resolution chain order:
207
207
  * 1. `role` — `config.llm.roles[role]` (explicit override)
208
- * 2. `default` — `config.llm.default` (canonical default)
209
- * 3. `implicit-fallback` — hard-coded fallback inside the resolver
208
+ * 2. `profile` — `config.llm.profiles[roles[role].profile]`
209
+ * 3. `default` — `config.llm.default` (canonical default)
210
+ * 4. `default-profile` — `config.llm.profiles[config.llm.defaultProfile]`
211
+ * 5. `implicit-fallback` — hard-coded fallback inside the resolver
210
212
  *
211
213
  * Useful for `cleo llm whoami` diagnostics.
212
214
  *
213
215
  * @task T9306
214
- */
215
- export type ResolutionSource = 'role' | 'default' | 'implicit-fallback';
216
+ * @task T11617 (`profile`, `default-profile`)
217
+ */
218
+ export type ResolutionSource =
219
+ | 'role'
220
+ | 'profile'
221
+ | 'default'
222
+ | 'default-profile'
223
+ | 'implicit-fallback';
216
224
 
217
225
  /**
218
226
  * Result envelope returned by `resolveLLMForRole(role)`.
@@ -1449,6 +1449,16 @@ export interface TasksCompleteQueryParams {
1449
1449
  * @task T10509
1450
1450
  */
1451
1451
  waiveReason?: string;
1452
+ /**
1453
+ * Reason for waiving the `E_CANCELLED_CHILD_NO_WAIVER` gate (PM-Core V2
1454
+ * design-point 4). When the parent has `cancelled` children, completion is
1455
+ * blocked unless this waiver is supplied — a cancelled child is abandoned
1456
+ * work and must not silently satisfy parent completion. Recorded to
1457
+ * `.cleo/audit/cancelled-child-waiver.jsonl`.
1458
+ *
1459
+ * @saga T10538 (PM-Core V2 agent-trust)
1460
+ */
1461
+ cancelledChildWaiverReason?: string;
1452
1462
  }
1453
1463
  /**
1454
1464
  * Result of `tasks.complete` — completion confirmation with unblocked tasks.
@@ -8,7 +8,7 @@
8
8
  * - BrainAccessor → brain.db (memory observations, semantic graph)
9
9
  * - ConduitAccessor → conduit.db (project-scoped messaging)
10
10
  * - NexusAccessor → nexus.db (code intelligence graph)
11
- * - SignaldockAccessor → signaldock.db (global agent identity)
11
+ * - AgentRegistrySubAccessor → signaldock.db (global agent identity)
12
12
  * - TelemetryAccessor → (future — telemetry collection)
13
13
  *
14
14
  * See DocsAccessor in docs-accessor.ts for the docs/llmtxt sub-accessor.
@@ -149,18 +149,18 @@ export interface NexusAccessor {
149
149
  }
150
150
 
151
151
  // ---------------------------------------------------------------------------
152
- // SignaldockAccessor
152
+ // AgentRegistrySubAccessor
153
153
  // ---------------------------------------------------------------------------
154
154
 
155
155
  /**
156
- * SignaldockAccessor — typed interface for signaldock.db (global agent identity).
156
+ * AgentRegistrySubAccessor — typed interface for signaldock.db (global agent identity).
157
157
  *
158
- * Minimal surface for T9188; full signaldock API lives in signaldock-sqlite.ts.
158
+ * Minimal surface for T9188; full signaldock API lives in agent-registry-store.ts.
159
159
  *
160
160
  * @task T9188
161
161
  * @epic T9048
162
162
  */
163
- export interface SignaldockAccessor {
163
+ export interface AgentRegistrySubAccessor {
164
164
  /**
165
165
  * Check whether signaldock.db is open and accessible.
166
166
  *