@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OperationOutputContract — schema-first OUTPUT contract for CLEO operations.
|
|
3
|
+
*
|
|
4
|
+
* This module is the SINGLE SOURCE OF TRUTH for the per-operation OUTPUT
|
|
5
|
+
* (result) shape introspection surface (DHQ-057 · T11692 · EP-DHQ-CORE-FIXES
|
|
6
|
+
* T11679 · SG-CORE-SELF-TOOLING T11480). It is the OUTPUT-side mirror of
|
|
7
|
+
* {@link OperationInputContract}.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this exists
|
|
10
|
+
*
|
|
11
|
+
* Before this contract, {@link OperationDef} declared INPUT (`params`) but had
|
|
12
|
+
* NO machine-readable description of the OUTPUT envelope's `data` payload.
|
|
13
|
+
* Consequently:
|
|
14
|
+
*
|
|
15
|
+
* - `cleo show T123 --field /data/title` returned `E_FIELD_NOT_FOUND` because
|
|
16
|
+
* the real shape is `/data/task/title` — agents had no way to predict the
|
|
17
|
+
* envelope `data` shape and `--field` JSON pointers were guesswork.
|
|
18
|
+
* - Every consumer (CLI `--field`, REST clients, the SDK) hand-sniffed the
|
|
19
|
+
* result per verb instead of resolving against a declared contract.
|
|
20
|
+
*
|
|
21
|
+
* An {@link OperationOutputContract} pairs a stable operation identifier with
|
|
22
|
+
* the JSON Schema (draft-07) document describing the envelope's `data` payload
|
|
23
|
+
* AND the curated set of valid `--field` JSON pointers (rooted at `/data`) that
|
|
24
|
+
* an agent can use without trial and error.
|
|
25
|
+
*
|
|
26
|
+
* ZERO runtime dependencies: `contracts` is a leaf package. JSON Schema
|
|
27
|
+
* documents are modeled as the loose {@link JsonSchema} alias (re-used from the
|
|
28
|
+
* input-contract module) so this module does not pull in a JSON Schema typings
|
|
29
|
+
* package.
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
* @module @cleocode/contracts/operations/output-contract
|
|
33
|
+
*
|
|
34
|
+
* @see {@link https://json-schema.org/draft-07/schema}
|
|
35
|
+
* @see OperationInputContract — the INPUT-side mirror of this contract
|
|
36
|
+
*
|
|
37
|
+
* @epic T11679
|
|
38
|
+
* @task T11692 — DHQ-057: per-operation output schema SSoT
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { JsonSchema } from './input-contract.js';
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// OperationOutputContract
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Schema-first OUTPUT (result) contract for a single CLEO operation.
|
|
49
|
+
*
|
|
50
|
+
* Pairs a stable operation identifier with the JSON Schema draft-07 document
|
|
51
|
+
* that describes the LAFS envelope's `data` payload, plus the curated list of
|
|
52
|
+
* valid `--field` JSON pointers an agent can rely on.
|
|
53
|
+
*
|
|
54
|
+
* The `dataSchema` describes the shape of `envelope.data` — i.e. the value the
|
|
55
|
+
* dispatch handler returns, BEFORE the LAFS `{ success, data, meta }` wrapper
|
|
56
|
+
* is applied. For example, `tasks.show` returns
|
|
57
|
+
* `{ task, view, attachments, acRows?, relations? }`, so its `dataSchema` has
|
|
58
|
+
* `task` as a required property and `/data/task/title` is the canonical pointer
|
|
59
|
+
* for the task title — NOT `/data/title`.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const showOutput: OperationOutputContract = {
|
|
64
|
+
* operation: 'tasks.show',
|
|
65
|
+
* dataSchema: {
|
|
66
|
+
* type: 'object',
|
|
67
|
+
* required: ['task'],
|
|
68
|
+
* properties: {
|
|
69
|
+
* task: { type: 'object', description: 'Full task record' },
|
|
70
|
+
* view: { type: ['object', 'null'] },
|
|
71
|
+
* attachments: { type: 'array' },
|
|
72
|
+
* },
|
|
73
|
+
* },
|
|
74
|
+
* fieldPointers: ['/data/task/id', '/data/task/title', '/data/task/status'],
|
|
75
|
+
* };
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export interface OperationOutputContract {
|
|
79
|
+
/**
|
|
80
|
+
* Stable, fully-qualified operation identifier in `<domain>.<verb>` form
|
|
81
|
+
* (e.g. `'tasks.show'`, `'tasks.add-batch'`). Used as the registry key in
|
|
82
|
+
* {@link OperationOutputContractRegistry}.
|
|
83
|
+
*/
|
|
84
|
+
operation: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* JSON Schema draft-07 document describing the shape of the LAFS envelope's
|
|
88
|
+
* `data` payload returned by this operation.
|
|
89
|
+
*
|
|
90
|
+
* This is the shape of `envelope.data` — the dispatch handler's return value
|
|
91
|
+
* BEFORE the `{ success, data, meta }` wrapper. Pointers used by `--field`
|
|
92
|
+
* are therefore rooted at `/data` (e.g. `/data/task/title` resolves against
|
|
93
|
+
* `dataSchema.properties.task.properties.title`).
|
|
94
|
+
*/
|
|
95
|
+
dataSchema: JsonSchema;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Curated, copy-pasteable list of valid `--field` JSON pointers an agent can
|
|
99
|
+
* use without trial and error. Each pointer is rooted at the envelope root
|
|
100
|
+
* (`/data/...`), matching the resolution semantics of `cleo <op> --field`.
|
|
101
|
+
*
|
|
102
|
+
* This is the direct remediation for the `--field /data/title` →
|
|
103
|
+
* `E_FIELD_NOT_FOUND` class of failures: instead of guessing, an agent runs
|
|
104
|
+
* `cleo show --describe` and reads the exact pointers that resolve.
|
|
105
|
+
*
|
|
106
|
+
* Conventionally lists the high-value scalar/leaf pointers (ids, titles,
|
|
107
|
+
* statuses, counts) — not every possible deep pointer.
|
|
108
|
+
*/
|
|
109
|
+
fieldPointers: string[];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Optional one-line note clarifying non-obvious shape facts (e.g. "task body
|
|
113
|
+
* is nested under `task`, not the root"). Surfaced in `--describe` output to
|
|
114
|
+
* pre-empt the exact mistakes this contract is designed to prevent.
|
|
115
|
+
*/
|
|
116
|
+
shapeNote?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Registry
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Registry of every {@link OperationOutputContract} known to the CLEO runtime,
|
|
125
|
+
* keyed by the contract's `operation` identifier (e.g. `'tasks.show'`).
|
|
126
|
+
*
|
|
127
|
+
* The OUTPUT-side mirror of `OperationInputContractRegistry`. Populated
|
|
128
|
+
* incrementally — high-traffic operations first. A `null` lookup (operation
|
|
129
|
+
* absent from the registry) is expected and MUST NOT error: callers render a
|
|
130
|
+
* structured "no output contract yet" payload instead.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const registry: OperationOutputContractRegistry = {
|
|
135
|
+
* 'tasks.show': showOutputContract,
|
|
136
|
+
* 'tasks.list': listOutputContract,
|
|
137
|
+
* };
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export type OperationOutputContractRegistry = Record<string, OperationOutputContract>;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OUTPUT_CONTRACTS data — the proof-set of per-operation OUTPUT contracts.
|
|
3
|
+
*
|
|
4
|
+
* SSoT for the OUTPUT (result) shape of CLEO's highest-traffic operations
|
|
5
|
+
* (DHQ-057 · T11692). Each entry is an {@link OperationOutputContract}: a
|
|
6
|
+
* JSON Schema draft-07 document for the LAFS envelope's `data` payload plus
|
|
7
|
+
* the curated list of valid `--field` JSON pointers.
|
|
8
|
+
*
|
|
9
|
+
* Populated INCREMENTALLY — high-traffic operations first. This is the OUTPUT
|
|
10
|
+
* mirror of `INPUT_CONTRACTS` (which lives in core). The data lives here in
|
|
11
|
+
* `contracts` (a leaf package, zero runtime deps) so every consumer — CLI
|
|
12
|
+
* `--describe`, the SDK `describeOperation`, REST clients — resolves against
|
|
13
|
+
* one source of truth.
|
|
14
|
+
*
|
|
15
|
+
* Each `dataSchema` is grounded in the concrete result interface declared in
|
|
16
|
+
* `./tasks.js` (e.g. {@link TasksShowResult}, {@link MinimalMutateEnvelope}) so
|
|
17
|
+
* the schema can never drift from the typed shape without a compile-time touch
|
|
18
|
+
* to this file.
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
* @module @cleocode/contracts/operations/output-contracts-data
|
|
22
|
+
*
|
|
23
|
+
* @epic T11679
|
|
24
|
+
* @task T11692 — DHQ-057: per-operation output schema SSoT
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
OperationOutputContract,
|
|
29
|
+
OperationOutputContractRegistry,
|
|
30
|
+
} from './output-contract.js';
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// tasks.show — the operation that bit us (DHQ-057 reproduction)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* OUTPUT contract for `tasks.show`.
|
|
38
|
+
*
|
|
39
|
+
* Grounded in {@link TasksShowResult}: `{ task, view, attachments, acRows?,
|
|
40
|
+
* relations? }`. The task body is nested under `task` — so the canonical
|
|
41
|
+
* pointer for the title is `/data/task/title`, NOT `/data/title`. This is the
|
|
42
|
+
* exact shape whose absence produced `cleo show --field /data/title` →
|
|
43
|
+
* `E_FIELD_NOT_FOUND`.
|
|
44
|
+
*/
|
|
45
|
+
const tasksShowOutputContract: OperationOutputContract = {
|
|
46
|
+
operation: 'tasks.show',
|
|
47
|
+
shapeNote:
|
|
48
|
+
'The task record is nested under `task` — use /data/task/<field>, not /data/<field>. ' +
|
|
49
|
+
'`view` may be null. `acRows` and `relations` are conditional.',
|
|
50
|
+
dataSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
required: ['task', 'view', 'attachments'],
|
|
53
|
+
additionalProperties: true,
|
|
54
|
+
properties: {
|
|
55
|
+
task: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
description: 'Full task record (id, title, status, priority, type, parentId, ...).',
|
|
58
|
+
required: ['id', 'title', 'status'],
|
|
59
|
+
properties: {
|
|
60
|
+
id: { type: 'string' },
|
|
61
|
+
title: { type: 'string' },
|
|
62
|
+
status: { type: 'string' },
|
|
63
|
+
priority: { type: 'string' },
|
|
64
|
+
type: { type: 'string' },
|
|
65
|
+
parentId: { type: ['string', 'null'] },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
view: {
|
|
69
|
+
type: ['object', 'null'],
|
|
70
|
+
description:
|
|
71
|
+
'Canonical task view projection produced by computeTaskView. Null when unavailable.',
|
|
72
|
+
},
|
|
73
|
+
attachments: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
description: 'Docs attachments linked to this task. Always an array (empty when none).',
|
|
76
|
+
},
|
|
77
|
+
acRows: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
description: 'Acceptance-criterion rows (id, alias AC<n>, ordinal, text). Optional.',
|
|
80
|
+
},
|
|
81
|
+
relations: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description: 'Expanded relation/doc lists. Present only with --relations.',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
fieldPointers: [
|
|
88
|
+
'/data/task/id',
|
|
89
|
+
'/data/task/title',
|
|
90
|
+
'/data/task/status',
|
|
91
|
+
'/data/task/priority',
|
|
92
|
+
'/data/task/type',
|
|
93
|
+
'/data/task/parentId',
|
|
94
|
+
'/data/view/id',
|
|
95
|
+
'/data/view/title',
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// tasks.list — array-of-tasks plus counts
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* OUTPUT contract for `tasks.list`.
|
|
105
|
+
*
|
|
106
|
+
* Grounded in {@link TasksListResult}: `{ tasks: TaskOp[], total, filtered }`.
|
|
107
|
+
*/
|
|
108
|
+
const tasksListOutputContract: OperationOutputContract = {
|
|
109
|
+
operation: 'tasks.list',
|
|
110
|
+
shapeNote: 'Rows are under /data/tasks (an array); counts are /data/total and /data/filtered.',
|
|
111
|
+
dataSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
required: ['tasks', 'total', 'filtered'],
|
|
114
|
+
additionalProperties: true,
|
|
115
|
+
properties: {
|
|
116
|
+
tasks: {
|
|
117
|
+
type: 'array',
|
|
118
|
+
description: 'Task rows matching the filters.',
|
|
119
|
+
items: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
id: { type: 'string' },
|
|
123
|
+
title: { type: 'string' },
|
|
124
|
+
status: { type: 'string' },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
total: { type: 'number', description: 'Total tasks before filtering.' },
|
|
129
|
+
filtered: { type: 'number', description: 'Number of tasks after filters applied.' },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
fieldPointers: [
|
|
133
|
+
'/data/tasks/0/id',
|
|
134
|
+
'/data/tasks/0/title',
|
|
135
|
+
'/data/tasks/0/status',
|
|
136
|
+
'/data/total',
|
|
137
|
+
'/data/filtered',
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// tasks.find — { results: MinimalTask[], total, query, searchType }
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* OUTPUT contract for `tasks.find`.
|
|
147
|
+
*
|
|
148
|
+
* Grounded in the actual return shape from `findTasks()` (find.ts:503-505)
|
|
149
|
+
* and the MVI projection plan (mvi-projection.ts:330 `path: 'results'`):
|
|
150
|
+
* `{ results: MinimalTask[], total: number, query: string, searchType: string }`.
|
|
151
|
+
*
|
|
152
|
+
* The `data` payload is a **wrapper object** (NOT a bare array). Results live
|
|
153
|
+
* under `/data/results`; the total count is at `/data/total`.
|
|
154
|
+
*/
|
|
155
|
+
const tasksFindOutputContract: OperationOutputContract = {
|
|
156
|
+
operation: 'tasks.find',
|
|
157
|
+
shapeNote:
|
|
158
|
+
'Results are wrapped: /data/results (array of matches), /data/total (count). ' +
|
|
159
|
+
'Use /data/results/0/id — NOT /data/0/id.',
|
|
160
|
+
dataSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
required: ['results', 'total'],
|
|
163
|
+
additionalProperties: true,
|
|
164
|
+
properties: {
|
|
165
|
+
results: {
|
|
166
|
+
type: 'array',
|
|
167
|
+
description: 'Array of minimal task matches.',
|
|
168
|
+
items: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
required: ['id', 'title'],
|
|
171
|
+
properties: {
|
|
172
|
+
id: { type: 'string' },
|
|
173
|
+
title: { type: 'string' },
|
|
174
|
+
status: { type: 'string' },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
total: { type: 'number', description: 'Total matching tasks.' },
|
|
179
|
+
query: { type: 'string', description: 'The query string that was searched.' },
|
|
180
|
+
searchType: { type: 'string', description: 'Kind of search performed (fts, semantic, ...).' },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
fieldPointers: [
|
|
184
|
+
'/data/results/0/id',
|
|
185
|
+
'/data/results/0/title',
|
|
186
|
+
'/data/results/0/status',
|
|
187
|
+
'/data/total',
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Mutation envelope shape (T9931 / T10608) — shared by add / add-batch /
|
|
193
|
+
// update / complete / delete.
|
|
194
|
+
//
|
|
195
|
+
// DEFAULT (minimal) shape — what `--field` resolves against — is
|
|
196
|
+
// `MinimalMutateEnvelope` from `mutate-projection.ts`:
|
|
197
|
+
// { count, created: string[], updated: string[], deleted: string[], ids: string[], ... }
|
|
198
|
+
//
|
|
199
|
+
// `created`, `updated`, and `deleted` contain BARE TASK ID STRINGS, not
|
|
200
|
+
// objects. `/data/created/0` resolves to a string like "T11692" directly.
|
|
201
|
+
// The object-array shape (`TaskMutationEnvelope`) is the `--full` shape only.
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The shared JSON Schema for the minimal mutate projection envelope
|
|
206
|
+
* (`MinimalMutateEnvelope` — T9931 / mutate-projection.ts). `created`,
|
|
207
|
+
* `updated`, and `deleted` are always present arrays of **bare task ID strings**
|
|
208
|
+
* (not objects). The gateway populates only the relevant bucket. Pointers:
|
|
209
|
+
* - `/data/created/0` → first created task ID string (e.g. "T11692")
|
|
210
|
+
* - `/data/updated/0` → first updated task ID string
|
|
211
|
+
* - `/data/deleted/0` → first deleted task ID string
|
|
212
|
+
*
|
|
213
|
+
* This is the DEFAULT shape (applies unless caller passes `--full`).
|
|
214
|
+
*/
|
|
215
|
+
const TASK_MUTATION_DATA_SCHEMA = {
|
|
216
|
+
type: 'object',
|
|
217
|
+
required: ['count', 'created', 'updated', 'deleted'],
|
|
218
|
+
additionalProperties: true,
|
|
219
|
+
properties: {
|
|
220
|
+
count: { type: 'number', description: 'Number of records the mutation affected.' },
|
|
221
|
+
created: {
|
|
222
|
+
type: 'array',
|
|
223
|
+
description:
|
|
224
|
+
'Task IDs created by the mutation (bare strings, e.g. "T11692"). ' +
|
|
225
|
+
'Empty for update/delete-only mutations.',
|
|
226
|
+
items: { type: 'string' },
|
|
227
|
+
},
|
|
228
|
+
updated: {
|
|
229
|
+
type: 'array',
|
|
230
|
+
description:
|
|
231
|
+
'Task IDs updated by the mutation (bare strings). ' +
|
|
232
|
+
'Empty for create/delete-only mutations.',
|
|
233
|
+
items: { type: 'string' },
|
|
234
|
+
},
|
|
235
|
+
deleted: {
|
|
236
|
+
type: 'array',
|
|
237
|
+
description:
|
|
238
|
+
'Task IDs deleted by the mutation (bare strings). ' +
|
|
239
|
+
'Empty for create/update-only mutations.',
|
|
240
|
+
items: { type: 'string' },
|
|
241
|
+
},
|
|
242
|
+
ids: {
|
|
243
|
+
type: 'array',
|
|
244
|
+
description: 'Deprecated alias for the non-empty bucket. Prefer created/updated/deleted.',
|
|
245
|
+
items: { type: 'string' },
|
|
246
|
+
},
|
|
247
|
+
dryRun: { type: 'boolean', description: 'True when this was a preview-only mutation.' },
|
|
248
|
+
status: { type: 'string', description: 'Post-mutation task status (add/update/complete).' },
|
|
249
|
+
},
|
|
250
|
+
} as const;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* OUTPUT contract for `tasks.add`.
|
|
254
|
+
*
|
|
255
|
+
* Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the created
|
|
256
|
+
* task ID lands as a bare string in `created[0]`.
|
|
257
|
+
* Use `/data/created/0` (a string like "T11692"), NOT `/data/created/0/id`.
|
|
258
|
+
*/
|
|
259
|
+
const tasksAddOutputContract: OperationOutputContract = {
|
|
260
|
+
operation: 'tasks.add',
|
|
261
|
+
shapeNote:
|
|
262
|
+
'The created task ID (bare string) is at /data/created/0 — NOT /data/created/0/id. ' +
|
|
263
|
+
'Example: /data/created/0 → "T11692".',
|
|
264
|
+
dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
|
|
265
|
+
fieldPointers: ['/data/created/0', '/data/count'],
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* OUTPUT contract for `tasks.add-batch`.
|
|
270
|
+
*
|
|
271
|
+
* Grounded in `MinimalMutateEnvelope` (mutate-projection.ts). For dry-run,
|
|
272
|
+
* `wouldCreate` and `insertedCount` are projected to the **root** of the
|
|
273
|
+
* envelope data (NOT under `dryRunSummary`) — confirmed in
|
|
274
|
+
* mutate-projection.ts lines 224/232.
|
|
275
|
+
*/
|
|
276
|
+
const tasksAddBatchOutputContract: OperationOutputContract = {
|
|
277
|
+
operation: 'tasks.add-batch',
|
|
278
|
+
shapeNote:
|
|
279
|
+
'Atomic batch insert. Each created task ID (bare string) is in /data/created (array). ' +
|
|
280
|
+
'Dry-run projections are at root: /data/wouldCreate and /data/insertedCount (=0). ' +
|
|
281
|
+
'NOT under /data/dryRunSummary.',
|
|
282
|
+
dataSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
required: ['count', 'created', 'updated', 'deleted'],
|
|
285
|
+
additionalProperties: true,
|
|
286
|
+
properties: {
|
|
287
|
+
...TASK_MUTATION_DATA_SCHEMA.properties,
|
|
288
|
+
wouldCreate: {
|
|
289
|
+
type: 'number',
|
|
290
|
+
description: 'Dry-run: predicted write count. Present only when dryRun=true.',
|
|
291
|
+
},
|
|
292
|
+
insertedCount: {
|
|
293
|
+
type: 'number',
|
|
294
|
+
description: 'Dry-run: always 0 (no DB write). Present only when dryRun=true.',
|
|
295
|
+
},
|
|
296
|
+
wouldAffect: {
|
|
297
|
+
type: 'number',
|
|
298
|
+
description: 'Dry-run: generic affected count. Present only when dryRun=true.',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
fieldPointers: ['/data/created/0', '/data/count', '/data/wouldCreate', '/data/insertedCount'],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* OUTPUT contract for `tasks.update` (and the `complete` alias, which is an
|
|
307
|
+
* update to `status: done`).
|
|
308
|
+
*
|
|
309
|
+
* Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the changed
|
|
310
|
+
* task ID lands as a bare string in `updated[0]`.
|
|
311
|
+
* Use `/data/updated/0` (a string like "T11692"), NOT `/data/updated/0/id`.
|
|
312
|
+
*/
|
|
313
|
+
const tasksUpdateOutputContract: OperationOutputContract = {
|
|
314
|
+
operation: 'tasks.update',
|
|
315
|
+
shapeNote:
|
|
316
|
+
'The updated task ID (bare string) is at /data/updated/0 — NOT /data/updated/0/id. ' +
|
|
317
|
+
'Use /data/status for the post-mutation status.',
|
|
318
|
+
dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
|
|
319
|
+
fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* OUTPUT contract for `tasks.complete`.
|
|
324
|
+
*
|
|
325
|
+
* Identical envelope to `tasks.update` — completion is a status mutation that
|
|
326
|
+
* lands the task ID in `updated[0]` as a bare string.
|
|
327
|
+
*/
|
|
328
|
+
const tasksCompleteOutputContract: OperationOutputContract = {
|
|
329
|
+
operation: 'tasks.complete',
|
|
330
|
+
shapeNote:
|
|
331
|
+
'Completion is a status mutation — the task ID (bare string) is at /data/updated/0 ' +
|
|
332
|
+
'(status=done). Use /data/status for the post-mutation status.',
|
|
333
|
+
dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
|
|
334
|
+
fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// Registry
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* SSoT registry of per-operation OUTPUT contracts, keyed by the canonical
|
|
343
|
+
* `<domain>.<verb>` operation id. Extend this map as operations are migrated
|
|
344
|
+
* to the schema-first OUTPUT surface (high-traffic first).
|
|
345
|
+
*
|
|
346
|
+
* @task T11692
|
|
347
|
+
*/
|
|
348
|
+
export const OUTPUT_CONTRACTS: OperationOutputContractRegistry = {
|
|
349
|
+
'tasks.show': tasksShowOutputContract,
|
|
350
|
+
'tasks.list': tasksListOutputContract,
|
|
351
|
+
'tasks.find': tasksFindOutputContract,
|
|
352
|
+
'tasks.add': tasksAddOutputContract,
|
|
353
|
+
'tasks.add-batch': tasksAddBatchOutputContract,
|
|
354
|
+
'tasks.update': tasksUpdateOutputContract,
|
|
355
|
+
'tasks.complete': tasksCompleteOutputContract,
|
|
356
|
+
};
|
package/src/operations/tasks.ts
CHANGED
|
@@ -959,8 +959,18 @@ export interface TasksSyncLinksRemoveResult {
|
|
|
959
959
|
export interface TasksCancelParams {
|
|
960
960
|
taskId: string;
|
|
961
961
|
reason?: string;
|
|
962
|
-
/**
|
|
963
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Explicit child handling mode. Defaults to 'block' so propagation is never
|
|
964
|
+
* implicit. `reparent` moves the children under {@link reparentTo}; the
|
|
965
|
+
* legacy `orphan`/detach mode was removed by T11811 (it manufactured the
|
|
966
|
+
* exact orphan the containment guard rejects).
|
|
967
|
+
*/
|
|
968
|
+
children?: 'block' | 'cascade' | 'reparent';
|
|
969
|
+
/**
|
|
970
|
+
* Target parent ID for the `reparent` strategy (T11811). REQUIRED when
|
|
971
|
+
* `children='reparent'`.
|
|
972
|
+
*/
|
|
973
|
+
reparentTo?: string;
|
|
964
974
|
/** Operator waiver for large cascade cancellation. */
|
|
965
975
|
force?: boolean;
|
|
966
976
|
/** Large-subtree cascade guard threshold. Defaults to 10 descendants. */
|
|
@@ -992,8 +1002,8 @@ export interface TasksCancelResult {
|
|
|
992
1002
|
*/
|
|
993
1003
|
alreadyCancelled?: boolean;
|
|
994
1004
|
/** Explicit child handling mode applied by the operation. */
|
|
995
|
-
childStrategy?: 'block' | 'cascade' | '
|
|
996
|
-
/** Child or descendant tasks affected by cascade/
|
|
1005
|
+
childStrategy?: 'block' | 'cascade' | 'reparent';
|
|
1006
|
+
/** Child or descendant tasks affected by cascade/reparent handling. */
|
|
997
1007
|
affectedTasks?: string[];
|
|
998
1008
|
/** Count of affected child/descendant tasks. */
|
|
999
1009
|
affectedCount?: number;
|