@cleocode/contracts 2026.6.6 → 2026.6.8
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.map +1 -1
- package/dist/db-recovery.d.ts +63 -0
- package/dist/db-recovery.d.ts.map +1 -1
- package/dist/dispatch/operation-def.d.ts +42 -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 +7 -1
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/docs/provenance.d.ts +6 -2
- package/dist/docs/provenance.d.ts.map +1 -1
- package/dist/docs/provenance.js +1 -0
- package/dist/docs/provenance.js.map +1 -1
- package/dist/docs/read.d.ts +148 -0
- package/dist/docs/read.d.ts.map +1 -0
- package/dist/docs/read.js +57 -0
- package/dist/docs/read.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/provider-profile.d.ts +54 -0
- package/dist/llm/provider-profile.d.ts.map +1 -1
- package/dist/operations/__tests__/output-contracts.test.d.ts +16 -0
- package/dist/operations/__tests__/output-contracts.test.d.ts.map +1 -0
- package/dist/operations/__tests__/output-contracts.test.js +103 -0
- package/dist/operations/__tests__/output-contracts.test.js.map +1 -0
- package/dist/operations/docs.d.ts +13 -3
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/output-contract.d.ts +127 -0
- package/dist/operations/output-contract.d.ts.map +1 -0
- package/dist/operations/output-contract.js +41 -0
- package/dist/operations/output-contract.js.map +1 -0
- package/dist/operations/output-contracts-data.d.ts +35 -0
- package/dist/operations/output-contracts-data.d.ts.map +1 -0
- package/dist/operations/output-contracts-data.js +328 -0
- package/dist/operations/output-contracts-data.js.map +1 -0
- package/dist/operations/tasks.d.ts +14 -4
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/operation-def.test.ts +5 -0
- package/src/db-recovery.ts +65 -0
- package/src/dispatch/operation-def.ts +44 -0
- package/src/dispatch/operations-registry.ts +7 -1
- package/src/docs/provenance.ts +4 -1
- package/src/docs/read.ts +131 -0
- package/src/index.ts +19 -0
- package/src/llm/provider-profile.ts +59 -0
- package/src/operations/__tests__/output-contracts.test.ts +125 -0
- package/src/operations/docs.ts +13 -3
- package/src/operations/output-contract.ts +140 -0
- package/src/operations/output-contracts-data.ts +356 -0
- package/src/operations/tasks.ts +14 -4
|
@@ -21,6 +21,8 @@ import { describe, expect, it } from 'vitest';
|
|
|
21
21
|
import type { CanonicalDomain, Gateway, Tier } from '../dispatch/identity.js';
|
|
22
22
|
import { CANONICAL_DOMAINS } from '../dispatch/identity.js';
|
|
23
23
|
import type { OperationDef, Resolution } from '../dispatch/operation-def.js';
|
|
24
|
+
import type { OperationInputContract } from '../operations/input-contract.js';
|
|
25
|
+
import type { OperationOutputContract } from '../operations/output-contract.js';
|
|
24
26
|
import type { ParamDef } from '../operations/params.js';
|
|
25
27
|
|
|
26
28
|
// ─── Compile-time structural-equality helpers ───────────────────────
|
|
@@ -66,6 +68,9 @@ type _OperationDefShape = {
|
|
|
66
68
|
requiredParams: string[];
|
|
67
69
|
params?: ParamDef[];
|
|
68
70
|
mcpExposed?: boolean;
|
|
71
|
+
// T11692 (DHQ-033 / DHQ-057) — optional schema-first input/output contracts.
|
|
72
|
+
inputSchema?: OperationInputContract<unknown>;
|
|
73
|
+
outputSchema?: OperationOutputContract;
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
type _AssertOperationDefPinned = AssertEquals1<Equals<OperationDef, _OperationDefShape>>;
|
package/src/db-recovery.ts
CHANGED
|
@@ -142,3 +142,68 @@ export interface BackupRecoverResult {
|
|
|
142
142
|
*/
|
|
143
143
|
dryRun: boolean;
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Per-role outcome of a `cleo doctor repair` pass (T11829 · DHQ-060).
|
|
148
|
+
*
|
|
149
|
+
* `doctor repair` first PROBES each role's live DB via `PRAGMA quick_check`
|
|
150
|
+
* (the same probe the recovery pipeline runs on snapshot candidates) and only
|
|
151
|
+
* invokes the {@link recoverMalformedDb} → quarantine → restore-from-snapshot →
|
|
152
|
+
* verify pipeline for roles that are actually malformed. This record captures
|
|
153
|
+
* what was observed and done for one role.
|
|
154
|
+
*
|
|
155
|
+
* @task T11829
|
|
156
|
+
* @epic T11833
|
|
157
|
+
* @saga T11242
|
|
158
|
+
* @public
|
|
159
|
+
*/
|
|
160
|
+
export interface DoctorRepairRoleResult {
|
|
161
|
+
/** Canonical role inspected. */
|
|
162
|
+
role: DbRole;
|
|
163
|
+
/** Absolute path to the role's live DB file (resolved from `DB_INVENTORY`). */
|
|
164
|
+
dbPath: string;
|
|
165
|
+
/** `true` when the live DB file exists on disk. */
|
|
166
|
+
present: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* `true` when the live DB passed `PRAGMA quick_check` (or was absent) — i.e.
|
|
169
|
+
* NO repair was needed. `false` when corruption was detected.
|
|
170
|
+
*/
|
|
171
|
+
healthy: boolean;
|
|
172
|
+
/**
|
|
173
|
+
* `'skipped'` — healthy or absent, nothing done;
|
|
174
|
+
* `'would-repair'` — `--dry-run`, corruption detected, repair NOT performed;
|
|
175
|
+
* `'repaired'` — corruption detected and snapshot restore succeeded;
|
|
176
|
+
* `'failed'` — corruption detected but recovery could not complete.
|
|
177
|
+
*/
|
|
178
|
+
action: 'skipped' | 'would-repair' | 'repaired' | 'failed';
|
|
179
|
+
/** Absolute path to the snapshot restored (or that would be), when applicable. */
|
|
180
|
+
restoredFrom: string | null;
|
|
181
|
+
/** Absolute path to the quarantined corrupt DB, when a repair was performed. */
|
|
182
|
+
quarantinedTo: string | null;
|
|
183
|
+
/** Approximate data-loss window in hours, when a snapshot timestamp was parsed. */
|
|
184
|
+
dataLossWindowHours: number | null;
|
|
185
|
+
/** Human-readable detail (probe outcome, error reason, or restore summary). */
|
|
186
|
+
detail: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Aggregate result of a `cleo doctor repair` invocation across one or more roles
|
|
191
|
+
* (T11829 · DHQ-060).
|
|
192
|
+
*
|
|
193
|
+
* @task T11829
|
|
194
|
+
* @epic T11833
|
|
195
|
+
* @saga T11242
|
|
196
|
+
* @public
|
|
197
|
+
*/
|
|
198
|
+
export interface DoctorRepairResult {
|
|
199
|
+
/** `true` when the run was a `--dry-run` (no files mutated). */
|
|
200
|
+
dryRun: boolean;
|
|
201
|
+
/** Per-role outcomes. */
|
|
202
|
+
roles: DoctorRepairRoleResult[];
|
|
203
|
+
/** Count of roles found malformed (`!healthy`). */
|
|
204
|
+
malformedCount: number;
|
|
205
|
+
/** Count of roles actually repaired this run. */
|
|
206
|
+
repairedCount: number;
|
|
207
|
+
/** Count of roles where a needed repair FAILED. */
|
|
208
|
+
failedCount: number;
|
|
209
|
+
}
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
* @since SG-ARCH-SOLID Saga T9831 · E-CONTRACTS-FOUNDATION T9832 · T9954 (Phase 0b)
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import type { OperationInputContract } from '../operations/input-contract.js';
|
|
32
|
+
import type { OperationOutputContract } from '../operations/output-contract.js';
|
|
31
33
|
import type { ParamDef } from '../operations/params.js';
|
|
32
34
|
import type { CanonicalDomain, Gateway, Tier } from './identity.js';
|
|
33
35
|
|
|
@@ -92,6 +94,48 @@ export interface OperationDef {
|
|
|
92
94
|
* @task T11448
|
|
93
95
|
*/
|
|
94
96
|
mcpExposed?: boolean;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Schema-first INPUT contract for this operation (DHQ-033 / T11692).
|
|
100
|
+
*
|
|
101
|
+
* Declares the accepted request payload shape as a JSON Schema draft-07
|
|
102
|
+
* document with worked examples. When present, the dispatch/CLI layer
|
|
103
|
+
* validates the incoming payload against `inputSchema.schema` and rejects
|
|
104
|
+
* unknown keys LOUDLY (the schemas set `additionalProperties: false`), so a
|
|
105
|
+
* typo'd or unsupported field — e.g. passing `relates` to `tasks.add-batch`,
|
|
106
|
+
* which only accepts `depends` — fails fast with a typed {@link ValidationError}
|
|
107
|
+
* instead of being silently dropped.
|
|
108
|
+
*
|
|
109
|
+
* **Optional / incremental**: absent means "no declared input contract yet".
|
|
110
|
+
* The canonical SSoT registry of contracts is keyed by `<domain>.<operation>`
|
|
111
|
+
* (INPUT_CONTRACTS in core); this inline field lets a definition carry its
|
|
112
|
+
* own contract and is resolved by the SDK `describeOperation` when set.
|
|
113
|
+
*
|
|
114
|
+
* @see OperationInputContract
|
|
115
|
+
* @task T11692 — DHQ-033 input shape SSoT
|
|
116
|
+
*/
|
|
117
|
+
inputSchema?: OperationInputContract<unknown>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Schema-first OUTPUT contract for this operation (DHQ-057 / T11692).
|
|
121
|
+
*
|
|
122
|
+
* Declares the shape of the LAFS envelope's `data` payload as a JSON Schema
|
|
123
|
+
* draft-07 document, PLUS the curated list of valid `--field` JSON pointers.
|
|
124
|
+
* This is the SSoT that lets an agent predict the result shape instead of
|
|
125
|
+
* hand-sniffing it per verb — e.g. it encodes that `tasks.show` returns the
|
|
126
|
+
* task under `task`, so the correct pointer is `/data/task/title` and NOT
|
|
127
|
+
* `/data/title` (the latter is the exact `E_FIELD_NOT_FOUND` failure this
|
|
128
|
+
* field exists to prevent).
|
|
129
|
+
*
|
|
130
|
+
* **Optional / incremental**: absent means "no declared output contract yet".
|
|
131
|
+
* The canonical SSoT registry is keyed by `<domain>.<operation>`
|
|
132
|
+
* (OUTPUT_CONTRACTS in contracts); this inline field lets a definition carry
|
|
133
|
+
* its own contract and is resolved by the SDK `describeOperation` when set.
|
|
134
|
+
*
|
|
135
|
+
* @see OperationOutputContract
|
|
136
|
+
* @task T11692 — DHQ-057 output shape SSoT
|
|
137
|
+
*/
|
|
138
|
+
outputSchema?: OperationOutputContract;
|
|
95
139
|
}
|
|
96
140
|
|
|
97
141
|
// ── Resolution ───────────────────────────────────────────────────────
|
|
@@ -7117,7 +7117,7 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
7117
7117
|
domain: 'docs',
|
|
7118
7118
|
operation: 'add',
|
|
7119
7119
|
description:
|
|
7120
|
-
'docs.add (mutate) — attach a local file or
|
|
7120
|
+
'docs.add (mutate) — attach a local file, URL, or inline content to a CLEO owner entity (task, session, observation)',
|
|
7121
7121
|
tier: 1,
|
|
7122
7122
|
idempotent: false,
|
|
7123
7123
|
sessionRequired: false,
|
|
@@ -7141,6 +7141,12 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
7141
7141
|
required: false,
|
|
7142
7142
|
description: 'Remote URL to attach',
|
|
7143
7143
|
},
|
|
7144
|
+
{
|
|
7145
|
+
name: 'content',
|
|
7146
|
+
type: 'string' as const,
|
|
7147
|
+
required: false,
|
|
7148
|
+
description: 'Inline document body (T10965); mutually exclusive with file/url',
|
|
7149
|
+
},
|
|
7144
7150
|
{
|
|
7145
7151
|
name: 'desc',
|
|
7146
7152
|
type: 'string' as const,
|
package/src/docs/provenance.ts
CHANGED
|
@@ -74,6 +74,7 @@ export const PROVENANCE_NODE_KINDS = [
|
|
|
74
74
|
* - `related-task` — child task is related (non-parent) to parent
|
|
75
75
|
* - `linked-decision` — node references a BRAIN decision
|
|
76
76
|
* - `derived-from` — node was derived from a prior artifact
|
|
77
|
+
* - `shares-topic` — two docs co-occur on a canonical topic slug (T11826)
|
|
77
78
|
*
|
|
78
79
|
* @task T10166
|
|
79
80
|
*/
|
|
@@ -83,7 +84,8 @@ export type ProvenanceEdgeRelation =
|
|
|
83
84
|
| 'superseded-by'
|
|
84
85
|
| 'related-task'
|
|
85
86
|
| 'linked-decision'
|
|
86
|
-
| 'derived-from'
|
|
87
|
+
| 'derived-from'
|
|
88
|
+
| 'shares-topic';
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* Frozen const array backing {@link ProvenanceEdgeRelation} — used by the
|
|
@@ -98,6 +100,7 @@ export const PROVENANCE_EDGE_RELATIONS = [
|
|
|
98
100
|
'related-task',
|
|
99
101
|
'linked-decision',
|
|
100
102
|
'derived-from',
|
|
103
|
+
'shares-topic',
|
|
101
104
|
] as const satisfies ReadonlyArray<ProvenanceEdgeRelation>;
|
|
102
105
|
|
|
103
106
|
/**
|
package/src/docs/read.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `docs.read` core-SDK contract — the envelope-typed surface an external
|
|
3
|
+
* consumer (the Obsidian plugin, `cleo docs view`) calls to render a single
|
|
4
|
+
* CLEO doc directly from `cleo.db`, the SOLE doc authority (saga T11778).
|
|
5
|
+
*
|
|
6
|
+
* The response carries the doc **body** plus its full provenance **frontmatter**
|
|
7
|
+
* (slug, version anchors, supersession pointers, related tasks, topics). Binary
|
|
8
|
+
* or non-UTF-8 bodies are surfaced as base64 (T11825 AC2) so a consumer can
|
|
9
|
+
* render images/PDFs/files stored as blobs without a second round-trip.
|
|
10
|
+
*
|
|
11
|
+
* Part of the T10400 core-SDK API standard — this is a typed contract, not an
|
|
12
|
+
* ad-hoc shape (T11825 AC3).
|
|
13
|
+
*
|
|
14
|
+
* @task T11825 (Epic T11781 / Saga T11778)
|
|
15
|
+
* @adr ADR-078 — Docs SSoT as provenance graph
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provenance frontmatter for a doc, sourced from the `attachments` columns that
|
|
22
|
+
* back the docs SSoT. Mirrors what a published doc's YAML frontmatter would
|
|
23
|
+
* carry, but read live from the DB rather than a file.
|
|
24
|
+
*
|
|
25
|
+
* @task T11825
|
|
26
|
+
*/
|
|
27
|
+
export interface DocFrontmatter {
|
|
28
|
+
/** Stable human-readable slug (`attachments.slug`). */
|
|
29
|
+
readonly slug: string;
|
|
30
|
+
/** Doc-kind taxonomy tag (e.g. `adr`, `spec`), or null for untyped blobs. */
|
|
31
|
+
readonly kind: string | null;
|
|
32
|
+
/** Display title (falls back to slug). */
|
|
33
|
+
readonly title: string | null;
|
|
34
|
+
/** One-sentence human summary, distinct from the body. */
|
|
35
|
+
readonly summary: string | null;
|
|
36
|
+
/** Document workflow state (`draft | active | superseded | …`). */
|
|
37
|
+
readonly lifecycleStatus: string;
|
|
38
|
+
/** Sequential doc-version counter for this slug (`attachments.doc_version`). */
|
|
39
|
+
readonly docVersion: number;
|
|
40
|
+
/** CLEO release version that wrote this row (`attachments.owner_version`). */
|
|
41
|
+
readonly ownerVersion: string | null;
|
|
42
|
+
/** Slug of the doc this one supersedes, or null. */
|
|
43
|
+
readonly supersedes: string | null;
|
|
44
|
+
/** Slug of the doc that supersedes this one, or null. */
|
|
45
|
+
readonly supersededBy: string | null;
|
|
46
|
+
/** Canonical topic slugs this doc carries (`attachments.topics`). */
|
|
47
|
+
readonly topics: readonly string[];
|
|
48
|
+
/** `T####` task ids this doc relates to (`attachments.related_tasks`). */
|
|
49
|
+
readonly relatedTasks: readonly string[];
|
|
50
|
+
/** SHA-256 content address of the body. */
|
|
51
|
+
readonly sha256: string;
|
|
52
|
+
/** ISO-8601 creation instant. */
|
|
53
|
+
readonly createdAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The body payload of a doc read.
|
|
58
|
+
*
|
|
59
|
+
* Exactly one of {@link text} / {@link base64} is populated: UTF-8-decodable
|
|
60
|
+
* content is surfaced as `text` with `encoding: 'utf-8'`; binary content is
|
|
61
|
+
* surfaced as `base64` with `encoding: 'base64'`.
|
|
62
|
+
*
|
|
63
|
+
* @task T11825
|
|
64
|
+
*/
|
|
65
|
+
export interface DocBody {
|
|
66
|
+
/** How {@link DocReadResponse.body} content is encoded. */
|
|
67
|
+
readonly encoding: 'utf-8' | 'base64';
|
|
68
|
+
/** UTF-8 body text, present when `encoding === 'utf-8'`. */
|
|
69
|
+
readonly text?: string;
|
|
70
|
+
/** Base64-encoded body bytes, present when `encoding === 'base64'`. */
|
|
71
|
+
readonly base64?: string;
|
|
72
|
+
/** Decoded byte length of the body. */
|
|
73
|
+
readonly sizeBytes: number;
|
|
74
|
+
/** IANA MIME type, when known. */
|
|
75
|
+
readonly mimeType: string | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The full `docs.read` response envelope payload.
|
|
80
|
+
*
|
|
81
|
+
* @task T11825
|
|
82
|
+
*/
|
|
83
|
+
export interface DocReadResponse {
|
|
84
|
+
/** Provenance frontmatter. */
|
|
85
|
+
readonly frontmatter: DocFrontmatter;
|
|
86
|
+
/** Rendered body (text or base64). */
|
|
87
|
+
readonly body: DocBody;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Zod schema for {@link DocFrontmatter}. */
|
|
91
|
+
export const docFrontmatterSchema = z.object({
|
|
92
|
+
slug: z.string(),
|
|
93
|
+
kind: z.string().nullable(),
|
|
94
|
+
title: z.string().nullable(),
|
|
95
|
+
summary: z.string().nullable(),
|
|
96
|
+
lifecycleStatus: z.string(),
|
|
97
|
+
docVersion: z.number().int(),
|
|
98
|
+
ownerVersion: z.string().nullable(),
|
|
99
|
+
supersedes: z.string().nullable(),
|
|
100
|
+
supersededBy: z.string().nullable(),
|
|
101
|
+
topics: z.array(z.string()).readonly(),
|
|
102
|
+
relatedTasks: z.array(z.string()).readonly(),
|
|
103
|
+
sha256: z.string(),
|
|
104
|
+
createdAt: z.string(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Zod schema for {@link DocBody}. */
|
|
108
|
+
export const docBodySchema = z.object({
|
|
109
|
+
encoding: z.enum(['utf-8', 'base64']),
|
|
110
|
+
text: z.string().optional(),
|
|
111
|
+
base64: z.string().optional(),
|
|
112
|
+
sizeBytes: z.number().int().nonnegative(),
|
|
113
|
+
mimeType: z.string().nullable(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** Zod schema for {@link DocReadResponse}. */
|
|
117
|
+
export const docReadResponseSchema = z.object({
|
|
118
|
+
frontmatter: docFrontmatterSchema,
|
|
119
|
+
body: docBodySchema,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Type guard for {@link DocReadResponse}.
|
|
124
|
+
*
|
|
125
|
+
* @param value - Unknown value to validate.
|
|
126
|
+
* @returns True when `value` conforms to the `docs.read` response schema.
|
|
127
|
+
* @task T11825
|
|
128
|
+
*/
|
|
129
|
+
export function isDocReadResponse(value: unknown): value is DocReadResponse {
|
|
130
|
+
return docReadResponseSchema.safeParse(value).success;
|
|
131
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -346,6 +346,8 @@ export type {
|
|
|
346
346
|
BackupRecoverResult,
|
|
347
347
|
DbRecoveredRowCounts,
|
|
348
348
|
DbRecoveryResult,
|
|
349
|
+
DoctorRepairResult,
|
|
350
|
+
DoctorRepairRoleResult,
|
|
349
351
|
} from './db-recovery.js';
|
|
350
352
|
// === Dependency Registry Contracts ===
|
|
351
353
|
export type {
|
|
@@ -398,6 +400,14 @@ export {
|
|
|
398
400
|
provenanceSessionNodeSchema,
|
|
399
401
|
provenanceTaskNodeSchema,
|
|
400
402
|
} from './docs/provenance.js';
|
|
403
|
+
// === docs.read core-SDK Contract (T11825) ===
|
|
404
|
+
export type { DocBody, DocFrontmatter, DocReadResponse } from './docs/read.js';
|
|
405
|
+
export {
|
|
406
|
+
docBodySchema,
|
|
407
|
+
docFrontmatterSchema,
|
|
408
|
+
docReadResponseSchema,
|
|
409
|
+
isDocReadResponse,
|
|
410
|
+
} from './docs/read.js';
|
|
401
411
|
// === DocsAccessor Contracts (T9063) ===
|
|
402
412
|
export type {
|
|
403
413
|
DocExportFormat,
|
|
@@ -1318,6 +1328,15 @@ export type {
|
|
|
1318
1328
|
OrchestrateReportGroup,
|
|
1319
1329
|
OrchestrateReportParams,
|
|
1320
1330
|
} from './operations/orchestrate.js';
|
|
1331
|
+
// === Operation Output Contracts (T11692 / DHQ-057 — per-op result shape SSoT) ===
|
|
1332
|
+
// OUTPUT-side mirror of the input contracts. Surfaces the LAFS envelope `data`
|
|
1333
|
+
// shape + valid --field JSON pointers so agents stop guessing (e.g. the real
|
|
1334
|
+
// pointer is /data/task/title, not /data/title).
|
|
1335
|
+
export type {
|
|
1336
|
+
OperationOutputContract,
|
|
1337
|
+
OperationOutputContractRegistry,
|
|
1338
|
+
} from './operations/output-contract.js';
|
|
1339
|
+
export { OUTPUT_CONTRACTS } from './operations/output-contracts-data.js';
|
|
1321
1340
|
// ParamDef contract — re-exported at top level (SSoT for all operation param descriptors)
|
|
1322
1341
|
export type {
|
|
1323
1342
|
CittyArgDef,
|
|
@@ -14,6 +14,17 @@ import type { ModelTransport, StoredAuthTypeWire } from '../operations/llm.js';
|
|
|
14
14
|
import type { TransportMessage, TransportTool } from './normalized-response.js';
|
|
15
15
|
import type { ProviderOAuthConfig } from './oauth.js';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Coarse provider capability tier used by the system-of-use router (E9).
|
|
19
|
+
*
|
|
20
|
+
* Ordered most→least capable: `frontier` (flagship reasoning), `standard`
|
|
21
|
+
* (general-purpose), `fast` (cheap/low-latency aux), `local` (on-device, e.g.
|
|
22
|
+
* Ollama). Sourced from the models.dev catalog (E8) when generated.
|
|
23
|
+
*
|
|
24
|
+
* @task T11756
|
|
25
|
+
*/
|
|
26
|
+
export type ProviderTier = 'frontier' | 'standard' | 'fast' | 'local';
|
|
27
|
+
|
|
17
28
|
/**
|
|
18
29
|
* Describes a single LLM provider — its auth capabilities, base URL,
|
|
19
30
|
* default model, and optional live model-discovery hook.
|
|
@@ -54,9 +65,57 @@ export interface ProviderProfile {
|
|
|
54
65
|
* Alternate names that resolve to this profile. Resolution is
|
|
55
66
|
* case-insensitive. Alias conflicts with another profile's primary name
|
|
56
67
|
* cause a `TypeError` at registration time.
|
|
68
|
+
*
|
|
69
|
+
* The canonical alias map (`codex`→`openai`, `google`→`gemini`, …) is declared
|
|
70
|
+
* here as DATA so the resolver never branches on provider name in code
|
|
71
|
+
* (T11756 AC2 · hermes `ProviderProfile.aliases` parity).
|
|
57
72
|
*/
|
|
58
73
|
aliases?: ReadonlyArray<string>;
|
|
59
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Coarse capability tier for system-of-use routing (E9). Sourced from the
|
|
77
|
+
* models.dev catalog capabilities when generated (E8 join), hand-set for
|
|
78
|
+
* builtins. Absent = unknown (the router treats it as `standard`).
|
|
79
|
+
*
|
|
80
|
+
* Hermes derives this from `model_metadata`; CLEO carries it as profile DATA
|
|
81
|
+
* so `resolveLLMForSystem` can pick the right tier without a code branch.
|
|
82
|
+
*
|
|
83
|
+
* @task T11756
|
|
84
|
+
*/
|
|
85
|
+
readonly tier?: ProviderTier;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Cheap/fast auxiliary model for low-stakes calls (title generation, summaries,
|
|
89
|
+
* the warm/aux tier). Hermes `ProviderProfile.default_aux_model` parity. Absent
|
|
90
|
+
* (or empty) → fall back to {@link defaultModel}.
|
|
91
|
+
*
|
|
92
|
+
* @task T11756
|
|
93
|
+
*/
|
|
94
|
+
readonly defaultAuxModel?: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Default `maxTokens` for this provider when the caller omits it. Hermes
|
|
98
|
+
* `ProviderProfile.default_max_tokens` parity. Absent → the transport's own
|
|
99
|
+
* default applies.
|
|
100
|
+
*
|
|
101
|
+
* @task T11756
|
|
102
|
+
*/
|
|
103
|
+
readonly defaultMaxTokens?: number;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Temperature handling this provider REQUIRES. Hermes
|
|
107
|
+
* `ProviderProfile.fixed_temperature` parity:
|
|
108
|
+
* - a `number` — the provider only accepts this exact temperature (e.g. the
|
|
109
|
+
* OpenAI o-series require `1`); the transport MUST send it and ignore the
|
|
110
|
+
* caller's override.
|
|
111
|
+
* - `'omit'` — the provider rejects a `temperature` field entirely; the
|
|
112
|
+
* transport MUST NOT send one.
|
|
113
|
+
* - absent — the caller's temperature (or the transport default) applies.
|
|
114
|
+
*
|
|
115
|
+
* @task T11756
|
|
116
|
+
*/
|
|
117
|
+
readonly fixedTemperature?: number | 'omit';
|
|
118
|
+
|
|
60
119
|
/**
|
|
61
120
|
* HTTP headers sent with every request to this provider.
|
|
62
121
|
* Merged into the SDK client's `defaultHeaders`.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the per-operation OUTPUT contract SSoT (T11692 / DHQ-057).
|
|
3
|
+
*
|
|
4
|
+
* Pins the proof-set of OUTPUT contracts and — critically — encodes the exact
|
|
5
|
+
* shape fact that produced the bug: `tasks.show` returns the task under `task`,
|
|
6
|
+
* so the canonical pointer is `/data/task/title`, NOT `/data/title`.
|
|
7
|
+
*
|
|
8
|
+
* Also validates that mutation contracts reflect the MinimalMutateEnvelope
|
|
9
|
+
* shape (string[] ids, NOT object arrays) and that tasks.find uses the
|
|
10
|
+
* { results, total } wrapper (NOT a bare array).
|
|
11
|
+
*
|
|
12
|
+
* @task T11692
|
|
13
|
+
* @epic T11679
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from 'vitest';
|
|
17
|
+
import type { OperationOutputContract } from '../output-contract.js';
|
|
18
|
+
import { OUTPUT_CONTRACTS } from '../output-contracts-data.js';
|
|
19
|
+
|
|
20
|
+
describe('operations/output-contracts (T11692 · DHQ-057)', () => {
|
|
21
|
+
it('registers the high-traffic proof-set operations', () => {
|
|
22
|
+
for (const op of [
|
|
23
|
+
'tasks.show',
|
|
24
|
+
'tasks.list',
|
|
25
|
+
'tasks.find',
|
|
26
|
+
'tasks.add',
|
|
27
|
+
'tasks.add-batch',
|
|
28
|
+
'tasks.update',
|
|
29
|
+
'tasks.complete',
|
|
30
|
+
]) {
|
|
31
|
+
expect(OUTPUT_CONTRACTS[op], `missing output contract for ${op}`).toBeDefined();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('keys every entry by its own operation id', () => {
|
|
36
|
+
for (const [key, contract] of Object.entries(OUTPUT_CONTRACTS)) {
|
|
37
|
+
expect(contract.operation).toBe(key);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('tasks.show — the operation that bit us', () => {
|
|
42
|
+
const show = OUTPUT_CONTRACTS['tasks.show'] as OperationOutputContract;
|
|
43
|
+
|
|
44
|
+
it('encodes that the task is nested under `task` (data schema)', () => {
|
|
45
|
+
const schema = show.dataSchema as {
|
|
46
|
+
required?: string[];
|
|
47
|
+
properties?: Record<string, { properties?: Record<string, unknown> }>;
|
|
48
|
+
};
|
|
49
|
+
expect(schema.required).toContain('task');
|
|
50
|
+
// The title lives under task.properties.title — NOT at the data root.
|
|
51
|
+
expect(schema.properties?.task?.properties).toHaveProperty('title');
|
|
52
|
+
expect(schema.properties).not.toHaveProperty('title');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('lists /data/task/title as a valid --field pointer and NOT /data/title', () => {
|
|
56
|
+
expect(show.fieldPointers).toContain('/data/task/title');
|
|
57
|
+
expect(show.fieldPointers).not.toContain('/data/title');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('carries a shapeNote steering agents away from /data/<field>', () => {
|
|
61
|
+
expect(show.shapeNote).toBeTruthy();
|
|
62
|
+
expect(show.shapeNote).toContain('/data/task/');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('notes that view may be null', () => {
|
|
66
|
+
expect(show.shapeNote).toContain('null');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('mutation envelopes — MinimalMutateEnvelope (string[] ids, T9931)', () => {
|
|
71
|
+
it('tasks.add surfaces the created task ID as a bare string at /data/created/0', () => {
|
|
72
|
+
const add = OUTPUT_CONTRACTS['tasks.add'] as OperationOutputContract;
|
|
73
|
+
// Correct: /data/created/0 → "T11692" (string), NOT /data/created/0/id
|
|
74
|
+
expect(add.fieldPointers).toContain('/data/created/0');
|
|
75
|
+
expect(add.fieldPointers).not.toContain('/data/created/0/id');
|
|
76
|
+
// Schema must reflect string[] items, not object[]
|
|
77
|
+
const schema = add.dataSchema as {
|
|
78
|
+
required?: string[];
|
|
79
|
+
properties?: Record<string, { items?: { type?: string } }>;
|
|
80
|
+
};
|
|
81
|
+
expect(schema.required).toEqual(
|
|
82
|
+
expect.arrayContaining(['count', 'created', 'updated', 'deleted']),
|
|
83
|
+
);
|
|
84
|
+
expect(schema.properties?.created?.items?.type).toBe('string');
|
|
85
|
+
expect(schema.properties?.updated?.items?.type).toBe('string');
|
|
86
|
+
expect(schema.properties?.deleted?.items?.type).toBe('string');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('tasks.update / tasks.complete surface the changed task ID at /data/updated/0 (string)', () => {
|
|
90
|
+
for (const op of ['tasks.update', 'tasks.complete']) {
|
|
91
|
+
const c = OUTPUT_CONTRACTS[op] as OperationOutputContract;
|
|
92
|
+
// Correct: /data/updated/0 → "T11692" (string), NOT /data/updated/0/id
|
|
93
|
+
expect(c.fieldPointers).toContain('/data/updated/0');
|
|
94
|
+
expect(c.fieldPointers).not.toContain('/data/updated/0/id');
|
|
95
|
+
const schema = c.dataSchema as {
|
|
96
|
+
properties?: Record<string, { items?: { type?: string } }>;
|
|
97
|
+
};
|
|
98
|
+
expect(schema.properties?.updated?.items?.type).toBe('string');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('tasks.add-batch dry-run pointers point to root /data/wouldCreate and /data/insertedCount', () => {
|
|
103
|
+
const batch = OUTPUT_CONTRACTS['tasks.add-batch'] as OperationOutputContract;
|
|
104
|
+
// Correct: root-level, NOT /data/dryRunSummary/wouldCreate
|
|
105
|
+
expect(batch.fieldPointers).toContain('/data/wouldCreate');
|
|
106
|
+
expect(batch.fieldPointers).toContain('/data/insertedCount');
|
|
107
|
+
expect(batch.fieldPointers).not.toContain('/data/dryRunSummary/wouldCreate');
|
|
108
|
+
expect(batch.fieldPointers).not.toContain('/data/dryRunSummary/insertedCount');
|
|
109
|
+
// shapeNote should clarify they are NOT under dryRunSummary
|
|
110
|
+
expect(batch.shapeNote).toContain('/data/wouldCreate');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('tasks.find data is a wrapper object with /data/results (array) and /data/total', () => {
|
|
115
|
+
const find = OUTPUT_CONTRACTS['tasks.find'] as OperationOutputContract;
|
|
116
|
+
// Correct: wrapper object, NOT a bare array
|
|
117
|
+
expect((find.dataSchema as { type?: string }).type).toBe('object');
|
|
118
|
+
// Correct pointers
|
|
119
|
+
expect(find.fieldPointers).toContain('/data/results/0/id');
|
|
120
|
+
expect(find.fieldPointers).toContain('/data/total');
|
|
121
|
+
// Wrong pointers must NOT appear
|
|
122
|
+
expect(find.fieldPointers).not.toContain('/data/0/id');
|
|
123
|
+
expect(find.shapeNote).toContain('/data/results');
|
|
124
|
+
});
|
|
125
|
+
});
|
package/src/operations/docs.ts
CHANGED
|
@@ -383,15 +383,25 @@ export interface DocsGenerateResult {
|
|
|
383
383
|
/**
|
|
384
384
|
* Parameters for `docs.add`.
|
|
385
385
|
*
|
|
386
|
-
* Exactly one of `file` or `
|
|
386
|
+
* Exactly one of `file`, `url`, or `content` must be provided.
|
|
387
387
|
*/
|
|
388
388
|
export interface DocsAddParams {
|
|
389
389
|
/** Owner entity ID (task, session, observation, etc.) — required. */
|
|
390
390
|
ownerId: string;
|
|
391
|
-
/** Local file path to attach (mutually exclusive with `url`). */
|
|
391
|
+
/** Local file path to attach (mutually exclusive with `url` and `content`). */
|
|
392
392
|
file?: string;
|
|
393
|
-
/** URL to attach as reference (mutually exclusive with `file`). */
|
|
393
|
+
/** URL to attach as reference (mutually exclusive with `file` and `content`). */
|
|
394
394
|
url?: string;
|
|
395
|
+
/**
|
|
396
|
+
* Inline document body authored directly on the command line or piped via
|
|
397
|
+
* stdin (`--content -`). Mutually exclusive with `file` and `url`. Stored
|
|
398
|
+
* as a `text/markdown` blob exactly as a file-sourced add would be —
|
|
399
|
+
* including the manifest.db mirror, slug reservation, memory observation,
|
|
400
|
+
* and audit trail — so a doc can be created without a pre-existing file.
|
|
401
|
+
*
|
|
402
|
+
* @task T10965
|
|
403
|
+
*/
|
|
404
|
+
content?: string;
|
|
395
405
|
/** Optional human-readable description. */
|
|
396
406
|
desc?: string;
|
|
397
407
|
/** Optional comma-separated labels for categorization. */
|