@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.
- package/dist/__tests__/operations-registry.test.js +13 -0
- package/dist/__tests__/operations-registry.test.js.map +1 -1
- package/dist/__tests__/skill-executor-contract.test.d.ts +14 -0
- package/dist/__tests__/skill-executor-contract.test.d.ts.map +1 -0
- package/dist/__tests__/skill-executor-contract.test.js +85 -0
- package/dist/__tests__/skill-executor-contract.test.js.map +1 -0
- package/dist/backup-manifest.d.ts +1 -1
- package/dist/brain-graph.d.ts +2 -2
- package/dist/brain-graph.d.ts.map +1 -1
- package/dist/config.d.ts +71 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/db-inventory.json +4 -4
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +44 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/exit-codes.d.ts +20 -1
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +19 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/migration-parity.d.ts +174 -0
- package/dist/migration-parity.d.ts.map +1 -0
- package/dist/migration-parity.js +39 -0
- package/dist/migration-parity.js.map +1 -0
- package/dist/operations/admin.d.ts +4 -2
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/brain.d.ts +2 -2
- package/dist/operations/brain.d.ts.map +1 -1
- package/dist/operations/llm.d.ts +6 -3
- package/dist/operations/llm.d.ts.map +1 -1
- package/dist/operations/tasks.d.ts +10 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/sub-accessors.d.ts +4 -4
- package/dist/sub-accessors.d.ts.map +1 -1
- package/dist/sub-accessors.js +1 -1
- package/dist/tools/skill-executor.d.ts +156 -0
- package/dist/tools/skill-executor.d.ts.map +1 -0
- package/dist/tools/skill-executor.js +47 -0
- package/dist/tools/skill-executor.js.map +1 -0
- package/package.json +6 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/operations-registry.test.ts +14 -0
- package/src/__tests__/skill-executor-contract.test.ts +104 -0
- package/src/backup-manifest.ts +1 -1
- package/src/brain-graph.ts +2 -2
- package/src/config.ts +73 -1
- package/src/db-inventory.json +4 -4
- package/src/dispatch/operations-registry.ts +45 -0
- package/src/exit-codes.ts +20 -0
- package/src/index.ts +11 -1
- package/src/migration-parity.ts +178 -0
- package/src/operations/admin.ts +4 -2
- package/src/operations/brain.ts +2 -2
- package/src/operations/llm.ts +12 -4
- package/src/operations/tasks.ts +10 -0
- package/src/sub-accessors.ts +5 -5
- 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
|
+
});
|
package/src/backup-manifest.ts
CHANGED
|
@@ -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:
|
|
79
|
+
* For signaldock.db: AGENT_REGISTRY_SCHEMA_VERSION constant.
|
|
80
80
|
*/
|
|
81
81
|
schemaVersion: string;
|
|
82
82
|
/**
|
package/src/brain-graph.ts
CHANGED
|
@@ -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' | '
|
|
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
|
-
* - `"
|
|
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]` → `
|
|
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. */
|
package/src/db-inventory.json
CHANGED
|
@@ -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/
|
|
91
|
-
"migrationsDir": "packages/core/migrations/drizzle-
|
|
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/
|
|
104
|
-
"migrationsDir": "packages/core/migrations/drizzle-
|
|
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
|
+
}
|
package/src/operations/admin.ts
CHANGED
|
@@ -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.
|
package/src/operations/brain.ts
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
*
|
|
56
56
|
* @task T962 / T968
|
|
57
57
|
*/
|
|
58
|
-
export type BrainSubstrateName = 'memory' | 'nexus' | 'tasks' | 'conduit' | '
|
|
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
|
-
* "
|
|
78
|
+
* "agent-registry:agent-cleo-prime"
|
|
79
79
|
*
|
|
80
80
|
* @remarks
|
|
81
81
|
* The substrate prefix (before the first `:`) MUST match one of
|
package/src/operations/llm.ts
CHANGED
|
@@ -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. `
|
|
209
|
-
* 3. `
|
|
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
|
-
|
|
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)`.
|
package/src/operations/tasks.ts
CHANGED
|
@@ -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.
|
package/src/sub-accessors.ts
CHANGED
|
@@ -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
|
-
* -
|
|
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
|
-
//
|
|
152
|
+
// AgentRegistrySubAccessor
|
|
153
153
|
// ---------------------------------------------------------------------------
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
156
|
+
* AgentRegistrySubAccessor — typed interface for signaldock.db (global agent identity).
|
|
157
157
|
*
|
|
158
|
-
* Minimal surface for T9188; full signaldock API lives in
|
|
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
|
|
163
|
+
export interface AgentRegistrySubAccessor {
|
|
164
164
|
/**
|
|
165
165
|
* Check whether signaldock.db is open and accessible.
|
|
166
166
|
*
|