@cleocode/contracts 2026.6.5 → 2026.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/__tests__/operation-def.test.js.map +1 -1
  2. package/dist/dispatch/operation-def.d.ts +42 -0
  3. package/dist/dispatch/operation-def.d.ts.map +1 -1
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/llm/provider-profile.d.ts +54 -0
  9. package/dist/llm/provider-profile.d.ts.map +1 -1
  10. package/dist/llm/resolved-descriptor.d.ts +111 -0
  11. package/dist/llm/resolved-descriptor.d.ts.map +1 -0
  12. package/dist/llm/resolved-descriptor.js +30 -0
  13. package/dist/llm/resolved-descriptor.js.map +1 -0
  14. package/dist/operations/__tests__/output-contracts.test.d.ts +16 -0
  15. package/dist/operations/__tests__/output-contracts.test.d.ts.map +1 -0
  16. package/dist/operations/__tests__/output-contracts.test.js +103 -0
  17. package/dist/operations/__tests__/output-contracts.test.js.map +1 -0
  18. package/dist/operations/output-contract.d.ts +127 -0
  19. package/dist/operations/output-contract.d.ts.map +1 -0
  20. package/dist/operations/output-contract.js +41 -0
  21. package/dist/operations/output-contract.js.map +1 -0
  22. package/dist/operations/output-contracts-data.d.ts +35 -0
  23. package/dist/operations/output-contracts-data.d.ts.map +1 -0
  24. package/dist/operations/output-contracts-data.js +328 -0
  25. package/dist/operations/output-contracts-data.js.map +1 -0
  26. package/dist/operations/tasks.d.ts +14 -4
  27. package/dist/operations/tasks.d.ts.map +1 -1
  28. package/dist/operations/tasks.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/__tests__/operation-def.test.ts +5 -0
  31. package/src/dispatch/operation-def.ts +44 -0
  32. package/src/index.ts +11 -0
  33. package/src/llm/provider-profile.ts +59 -0
  34. package/src/llm/resolved-descriptor.ts +113 -0
  35. package/src/operations/__tests__/output-contracts.test.ts +125 -0
  36. package/src/operations/output-contract.ts +140 -0
  37. package/src/operations/output-contracts-data.ts +356 -0
  38. package/src/operations/tasks.ts +14 -4
@@ -0,0 +1,103 @@
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
+ import { describe, expect, it } from 'vitest';
16
+ import { OUTPUT_CONTRACTS } from '../output-contracts-data.js';
17
+ describe('operations/output-contracts (T11692 · DHQ-057)', () => {
18
+ it('registers the high-traffic proof-set operations', () => {
19
+ for (const op of [
20
+ 'tasks.show',
21
+ 'tasks.list',
22
+ 'tasks.find',
23
+ 'tasks.add',
24
+ 'tasks.add-batch',
25
+ 'tasks.update',
26
+ 'tasks.complete',
27
+ ]) {
28
+ expect(OUTPUT_CONTRACTS[op], `missing output contract for ${op}`).toBeDefined();
29
+ }
30
+ });
31
+ it('keys every entry by its own operation id', () => {
32
+ for (const [key, contract] of Object.entries(OUTPUT_CONTRACTS)) {
33
+ expect(contract.operation).toBe(key);
34
+ }
35
+ });
36
+ describe('tasks.show — the operation that bit us', () => {
37
+ const show = OUTPUT_CONTRACTS['tasks.show'];
38
+ it('encodes that the task is nested under `task` (data schema)', () => {
39
+ const schema = show.dataSchema;
40
+ expect(schema.required).toContain('task');
41
+ // The title lives under task.properties.title — NOT at the data root.
42
+ expect(schema.properties?.task?.properties).toHaveProperty('title');
43
+ expect(schema.properties).not.toHaveProperty('title');
44
+ });
45
+ it('lists /data/task/title as a valid --field pointer and NOT /data/title', () => {
46
+ expect(show.fieldPointers).toContain('/data/task/title');
47
+ expect(show.fieldPointers).not.toContain('/data/title');
48
+ });
49
+ it('carries a shapeNote steering agents away from /data/<field>', () => {
50
+ expect(show.shapeNote).toBeTruthy();
51
+ expect(show.shapeNote).toContain('/data/task/');
52
+ });
53
+ it('notes that view may be null', () => {
54
+ expect(show.shapeNote).toContain('null');
55
+ });
56
+ });
57
+ describe('mutation envelopes — MinimalMutateEnvelope (string[] ids, T9931)', () => {
58
+ it('tasks.add surfaces the created task ID as a bare string at /data/created/0', () => {
59
+ const add = OUTPUT_CONTRACTS['tasks.add'];
60
+ // Correct: /data/created/0 → "T11692" (string), NOT /data/created/0/id
61
+ expect(add.fieldPointers).toContain('/data/created/0');
62
+ expect(add.fieldPointers).not.toContain('/data/created/0/id');
63
+ // Schema must reflect string[] items, not object[]
64
+ const schema = add.dataSchema;
65
+ expect(schema.required).toEqual(expect.arrayContaining(['count', 'created', 'updated', 'deleted']));
66
+ expect(schema.properties?.created?.items?.type).toBe('string');
67
+ expect(schema.properties?.updated?.items?.type).toBe('string');
68
+ expect(schema.properties?.deleted?.items?.type).toBe('string');
69
+ });
70
+ it('tasks.update / tasks.complete surface the changed task ID at /data/updated/0 (string)', () => {
71
+ for (const op of ['tasks.update', 'tasks.complete']) {
72
+ const c = OUTPUT_CONTRACTS[op];
73
+ // Correct: /data/updated/0 → "T11692" (string), NOT /data/updated/0/id
74
+ expect(c.fieldPointers).toContain('/data/updated/0');
75
+ expect(c.fieldPointers).not.toContain('/data/updated/0/id');
76
+ const schema = c.dataSchema;
77
+ expect(schema.properties?.updated?.items?.type).toBe('string');
78
+ }
79
+ });
80
+ it('tasks.add-batch dry-run pointers point to root /data/wouldCreate and /data/insertedCount', () => {
81
+ const batch = OUTPUT_CONTRACTS['tasks.add-batch'];
82
+ // Correct: root-level, NOT /data/dryRunSummary/wouldCreate
83
+ expect(batch.fieldPointers).toContain('/data/wouldCreate');
84
+ expect(batch.fieldPointers).toContain('/data/insertedCount');
85
+ expect(batch.fieldPointers).not.toContain('/data/dryRunSummary/wouldCreate');
86
+ expect(batch.fieldPointers).not.toContain('/data/dryRunSummary/insertedCount');
87
+ // shapeNote should clarify they are NOT under dryRunSummary
88
+ expect(batch.shapeNote).toContain('/data/wouldCreate');
89
+ });
90
+ });
91
+ it('tasks.find data is a wrapper object with /data/results (array) and /data/total', () => {
92
+ const find = OUTPUT_CONTRACTS['tasks.find'];
93
+ // Correct: wrapper object, NOT a bare array
94
+ expect(find.dataSchema.type).toBe('object');
95
+ // Correct pointers
96
+ expect(find.fieldPointers).toContain('/data/results/0/id');
97
+ expect(find.fieldPointers).toContain('/data/total');
98
+ // Wrong pointers must NOT appear
99
+ expect(find.fieldPointers).not.toContain('/data/0/id');
100
+ expect(find.shapeNote).toContain('/data/results');
101
+ });
102
+ });
103
+ //# sourceMappingURL=output-contracts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-contracts.test.js","sourceRoot":"","sources":["../../../src/operations/__tests__/output-contracts.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
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
+ import type { JsonSchema } from './input-contract.js';
41
+ /**
42
+ * Schema-first OUTPUT (result) contract for a single CLEO operation.
43
+ *
44
+ * Pairs a stable operation identifier with the JSON Schema draft-07 document
45
+ * that describes the LAFS envelope's `data` payload, plus the curated list of
46
+ * valid `--field` JSON pointers an agent can rely on.
47
+ *
48
+ * The `dataSchema` describes the shape of `envelope.data` — i.e. the value the
49
+ * dispatch handler returns, BEFORE the LAFS `{ success, data, meta }` wrapper
50
+ * is applied. For example, `tasks.show` returns
51
+ * `{ task, view, attachments, acRows?, relations? }`, so its `dataSchema` has
52
+ * `task` as a required property and `/data/task/title` is the canonical pointer
53
+ * for the task title — NOT `/data/title`.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const showOutput: OperationOutputContract = {
58
+ * operation: 'tasks.show',
59
+ * dataSchema: {
60
+ * type: 'object',
61
+ * required: ['task'],
62
+ * properties: {
63
+ * task: { type: 'object', description: 'Full task record' },
64
+ * view: { type: ['object', 'null'] },
65
+ * attachments: { type: 'array' },
66
+ * },
67
+ * },
68
+ * fieldPointers: ['/data/task/id', '/data/task/title', '/data/task/status'],
69
+ * };
70
+ * ```
71
+ */
72
+ export interface OperationOutputContract {
73
+ /**
74
+ * Stable, fully-qualified operation identifier in `<domain>.<verb>` form
75
+ * (e.g. `'tasks.show'`, `'tasks.add-batch'`). Used as the registry key in
76
+ * {@link OperationOutputContractRegistry}.
77
+ */
78
+ operation: string;
79
+ /**
80
+ * JSON Schema draft-07 document describing the shape of the LAFS envelope's
81
+ * `data` payload returned by this operation.
82
+ *
83
+ * This is the shape of `envelope.data` — the dispatch handler's return value
84
+ * BEFORE the `{ success, data, meta }` wrapper. Pointers used by `--field`
85
+ * are therefore rooted at `/data` (e.g. `/data/task/title` resolves against
86
+ * `dataSchema.properties.task.properties.title`).
87
+ */
88
+ dataSchema: JsonSchema;
89
+ /**
90
+ * Curated, copy-pasteable list of valid `--field` JSON pointers an agent can
91
+ * use without trial and error. Each pointer is rooted at the envelope root
92
+ * (`/data/...`), matching the resolution semantics of `cleo <op> --field`.
93
+ *
94
+ * This is the direct remediation for the `--field /data/title` →
95
+ * `E_FIELD_NOT_FOUND` class of failures: instead of guessing, an agent runs
96
+ * `cleo show --describe` and reads the exact pointers that resolve.
97
+ *
98
+ * Conventionally lists the high-value scalar/leaf pointers (ids, titles,
99
+ * statuses, counts) — not every possible deep pointer.
100
+ */
101
+ fieldPointers: string[];
102
+ /**
103
+ * Optional one-line note clarifying non-obvious shape facts (e.g. "task body
104
+ * is nested under `task`, not the root"). Surfaced in `--describe` output to
105
+ * pre-empt the exact mistakes this contract is designed to prevent.
106
+ */
107
+ shapeNote?: string;
108
+ }
109
+ /**
110
+ * Registry of every {@link OperationOutputContract} known to the CLEO runtime,
111
+ * keyed by the contract's `operation` identifier (e.g. `'tasks.show'`).
112
+ *
113
+ * The OUTPUT-side mirror of `OperationInputContractRegistry`. Populated
114
+ * incrementally — high-traffic operations first. A `null` lookup (operation
115
+ * absent from the registry) is expected and MUST NOT error: callers render a
116
+ * structured "no output contract yet" payload instead.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const registry: OperationOutputContractRegistry = {
121
+ * 'tasks.show': showOutputContract,
122
+ * 'tasks.list': listOutputContract,
123
+ * };
124
+ * ```
125
+ */
126
+ export type OperationOutputContractRegistry = Record<string, OperationOutputContract>;
127
+ //# sourceMappingURL=output-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-contract.d.ts","sourceRoot":"","sources":["../../src/operations/output-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAMtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;;;;OAQG;IACH,UAAU,EAAE,UAAU,CAAC;IAEvB;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IAExB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,+BAA+B,GAAG,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC"}
@@ -0,0 +1,41 @@
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
+ export {};
41
+ //# sourceMappingURL=output-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-contract.js","sourceRoot":"","sources":["../../src/operations/output-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG"}
@@ -0,0 +1,35 @@
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
+ import type { OperationOutputContractRegistry } from './output-contract.js';
27
+ /**
28
+ * SSoT registry of per-operation OUTPUT contracts, keyed by the canonical
29
+ * `<domain>.<verb>` operation id. Extend this map as operations are migrated
30
+ * to the schema-first OUTPUT surface (high-traffic first).
31
+ *
32
+ * @task T11692
33
+ */
34
+ export declare const OUTPUT_CONTRACTS: OperationOutputContractRegistry;
35
+ //# sourceMappingURL=output-contracts-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-contracts-data.d.ts","sourceRoot":"","sources":["../../src/operations/output-contracts-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAEV,+BAA+B,EAChC,MAAM,sBAAsB,CAAC;AAuT9B;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,+BAQ9B,CAAC"}
@@ -0,0 +1,328 @@
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
+ // tasks.show — the operation that bit us (DHQ-057 reproduction)
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * OUTPUT contract for `tasks.show`.
31
+ *
32
+ * Grounded in {@link TasksShowResult}: `{ task, view, attachments, acRows?,
33
+ * relations? }`. The task body is nested under `task` — so the canonical
34
+ * pointer for the title is `/data/task/title`, NOT `/data/title`. This is the
35
+ * exact shape whose absence produced `cleo show --field /data/title` →
36
+ * `E_FIELD_NOT_FOUND`.
37
+ */
38
+ const tasksShowOutputContract = {
39
+ operation: 'tasks.show',
40
+ shapeNote: 'The task record is nested under `task` — use /data/task/<field>, not /data/<field>. ' +
41
+ '`view` may be null. `acRows` and `relations` are conditional.',
42
+ dataSchema: {
43
+ type: 'object',
44
+ required: ['task', 'view', 'attachments'],
45
+ additionalProperties: true,
46
+ properties: {
47
+ task: {
48
+ type: 'object',
49
+ description: 'Full task record (id, title, status, priority, type, parentId, ...).',
50
+ required: ['id', 'title', 'status'],
51
+ properties: {
52
+ id: { type: 'string' },
53
+ title: { type: 'string' },
54
+ status: { type: 'string' },
55
+ priority: { type: 'string' },
56
+ type: { type: 'string' },
57
+ parentId: { type: ['string', 'null'] },
58
+ },
59
+ },
60
+ view: {
61
+ type: ['object', 'null'],
62
+ description: 'Canonical task view projection produced by computeTaskView. Null when unavailable.',
63
+ },
64
+ attachments: {
65
+ type: 'array',
66
+ description: 'Docs attachments linked to this task. Always an array (empty when none).',
67
+ },
68
+ acRows: {
69
+ type: 'array',
70
+ description: 'Acceptance-criterion rows (id, alias AC<n>, ordinal, text). Optional.',
71
+ },
72
+ relations: {
73
+ type: 'object',
74
+ description: 'Expanded relation/doc lists. Present only with --relations.',
75
+ },
76
+ },
77
+ },
78
+ fieldPointers: [
79
+ '/data/task/id',
80
+ '/data/task/title',
81
+ '/data/task/status',
82
+ '/data/task/priority',
83
+ '/data/task/type',
84
+ '/data/task/parentId',
85
+ '/data/view/id',
86
+ '/data/view/title',
87
+ ],
88
+ };
89
+ // ---------------------------------------------------------------------------
90
+ // tasks.list — array-of-tasks plus counts
91
+ // ---------------------------------------------------------------------------
92
+ /**
93
+ * OUTPUT contract for `tasks.list`.
94
+ *
95
+ * Grounded in {@link TasksListResult}: `{ tasks: TaskOp[], total, filtered }`.
96
+ */
97
+ const tasksListOutputContract = {
98
+ operation: 'tasks.list',
99
+ shapeNote: 'Rows are under /data/tasks (an array); counts are /data/total and /data/filtered.',
100
+ dataSchema: {
101
+ type: 'object',
102
+ required: ['tasks', 'total', 'filtered'],
103
+ additionalProperties: true,
104
+ properties: {
105
+ tasks: {
106
+ type: 'array',
107
+ description: 'Task rows matching the filters.',
108
+ items: {
109
+ type: 'object',
110
+ properties: {
111
+ id: { type: 'string' },
112
+ title: { type: 'string' },
113
+ status: { type: 'string' },
114
+ },
115
+ },
116
+ },
117
+ total: { type: 'number', description: 'Total tasks before filtering.' },
118
+ filtered: { type: 'number', description: 'Number of tasks after filters applied.' },
119
+ },
120
+ },
121
+ fieldPointers: [
122
+ '/data/tasks/0/id',
123
+ '/data/tasks/0/title',
124
+ '/data/tasks/0/status',
125
+ '/data/total',
126
+ '/data/filtered',
127
+ ],
128
+ };
129
+ // ---------------------------------------------------------------------------
130
+ // tasks.find — { results: MinimalTask[], total, query, searchType }
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * OUTPUT contract for `tasks.find`.
134
+ *
135
+ * Grounded in the actual return shape from `findTasks()` (find.ts:503-505)
136
+ * and the MVI projection plan (mvi-projection.ts:330 `path: 'results'`):
137
+ * `{ results: MinimalTask[], total: number, query: string, searchType: string }`.
138
+ *
139
+ * The `data` payload is a **wrapper object** (NOT a bare array). Results live
140
+ * under `/data/results`; the total count is at `/data/total`.
141
+ */
142
+ const tasksFindOutputContract = {
143
+ operation: 'tasks.find',
144
+ shapeNote: 'Results are wrapped: /data/results (array of matches), /data/total (count). ' +
145
+ 'Use /data/results/0/id — NOT /data/0/id.',
146
+ dataSchema: {
147
+ type: 'object',
148
+ required: ['results', 'total'],
149
+ additionalProperties: true,
150
+ properties: {
151
+ results: {
152
+ type: 'array',
153
+ description: 'Array of minimal task matches.',
154
+ items: {
155
+ type: 'object',
156
+ required: ['id', 'title'],
157
+ properties: {
158
+ id: { type: 'string' },
159
+ title: { type: 'string' },
160
+ status: { type: 'string' },
161
+ },
162
+ },
163
+ },
164
+ total: { type: 'number', description: 'Total matching tasks.' },
165
+ query: { type: 'string', description: 'The query string that was searched.' },
166
+ searchType: { type: 'string', description: 'Kind of search performed (fts, semantic, ...).' },
167
+ },
168
+ },
169
+ fieldPointers: [
170
+ '/data/results/0/id',
171
+ '/data/results/0/title',
172
+ '/data/results/0/status',
173
+ '/data/total',
174
+ ],
175
+ };
176
+ // ---------------------------------------------------------------------------
177
+ // Mutation envelope shape (T9931 / T10608) — shared by add / add-batch /
178
+ // update / complete / delete.
179
+ //
180
+ // DEFAULT (minimal) shape — what `--field` resolves against — is
181
+ // `MinimalMutateEnvelope` from `mutate-projection.ts`:
182
+ // { count, created: string[], updated: string[], deleted: string[], ids: string[], ... }
183
+ //
184
+ // `created`, `updated`, and `deleted` contain BARE TASK ID STRINGS, not
185
+ // objects. `/data/created/0` resolves to a string like "T11692" directly.
186
+ // The object-array shape (`TaskMutationEnvelope`) is the `--full` shape only.
187
+ // ---------------------------------------------------------------------------
188
+ /**
189
+ * The shared JSON Schema for the minimal mutate projection envelope
190
+ * (`MinimalMutateEnvelope` — T9931 / mutate-projection.ts). `created`,
191
+ * `updated`, and `deleted` are always present arrays of **bare task ID strings**
192
+ * (not objects). The gateway populates only the relevant bucket. Pointers:
193
+ * - `/data/created/0` → first created task ID string (e.g. "T11692")
194
+ * - `/data/updated/0` → first updated task ID string
195
+ * - `/data/deleted/0` → first deleted task ID string
196
+ *
197
+ * This is the DEFAULT shape (applies unless caller passes `--full`).
198
+ */
199
+ const TASK_MUTATION_DATA_SCHEMA = {
200
+ type: 'object',
201
+ required: ['count', 'created', 'updated', 'deleted'],
202
+ additionalProperties: true,
203
+ properties: {
204
+ count: { type: 'number', description: 'Number of records the mutation affected.' },
205
+ created: {
206
+ type: 'array',
207
+ description: 'Task IDs created by the mutation (bare strings, e.g. "T11692"). ' +
208
+ 'Empty for update/delete-only mutations.',
209
+ items: { type: 'string' },
210
+ },
211
+ updated: {
212
+ type: 'array',
213
+ description: 'Task IDs updated by the mutation (bare strings). ' +
214
+ 'Empty for create/delete-only mutations.',
215
+ items: { type: 'string' },
216
+ },
217
+ deleted: {
218
+ type: 'array',
219
+ description: 'Task IDs deleted by the mutation (bare strings). ' +
220
+ 'Empty for create/update-only mutations.',
221
+ items: { type: 'string' },
222
+ },
223
+ ids: {
224
+ type: 'array',
225
+ description: 'Deprecated alias for the non-empty bucket. Prefer created/updated/deleted.',
226
+ items: { type: 'string' },
227
+ },
228
+ dryRun: { type: 'boolean', description: 'True when this was a preview-only mutation.' },
229
+ status: { type: 'string', description: 'Post-mutation task status (add/update/complete).' },
230
+ },
231
+ };
232
+ /**
233
+ * OUTPUT contract for `tasks.add`.
234
+ *
235
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the created
236
+ * task ID lands as a bare string in `created[0]`.
237
+ * Use `/data/created/0` (a string like "T11692"), NOT `/data/created/0/id`.
238
+ */
239
+ const tasksAddOutputContract = {
240
+ operation: 'tasks.add',
241
+ shapeNote: 'The created task ID (bare string) is at /data/created/0 — NOT /data/created/0/id. ' +
242
+ 'Example: /data/created/0 → "T11692".',
243
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
244
+ fieldPointers: ['/data/created/0', '/data/count'],
245
+ };
246
+ /**
247
+ * OUTPUT contract for `tasks.add-batch`.
248
+ *
249
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts). For dry-run,
250
+ * `wouldCreate` and `insertedCount` are projected to the **root** of the
251
+ * envelope data (NOT under `dryRunSummary`) — confirmed in
252
+ * mutate-projection.ts lines 224/232.
253
+ */
254
+ const tasksAddBatchOutputContract = {
255
+ operation: 'tasks.add-batch',
256
+ shapeNote: 'Atomic batch insert. Each created task ID (bare string) is in /data/created (array). ' +
257
+ 'Dry-run projections are at root: /data/wouldCreate and /data/insertedCount (=0). ' +
258
+ 'NOT under /data/dryRunSummary.',
259
+ dataSchema: {
260
+ type: 'object',
261
+ required: ['count', 'created', 'updated', 'deleted'],
262
+ additionalProperties: true,
263
+ properties: {
264
+ ...TASK_MUTATION_DATA_SCHEMA.properties,
265
+ wouldCreate: {
266
+ type: 'number',
267
+ description: 'Dry-run: predicted write count. Present only when dryRun=true.',
268
+ },
269
+ insertedCount: {
270
+ type: 'number',
271
+ description: 'Dry-run: always 0 (no DB write). Present only when dryRun=true.',
272
+ },
273
+ wouldAffect: {
274
+ type: 'number',
275
+ description: 'Dry-run: generic affected count. Present only when dryRun=true.',
276
+ },
277
+ },
278
+ },
279
+ fieldPointers: ['/data/created/0', '/data/count', '/data/wouldCreate', '/data/insertedCount'],
280
+ };
281
+ /**
282
+ * OUTPUT contract for `tasks.update` (and the `complete` alias, which is an
283
+ * update to `status: done`).
284
+ *
285
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the changed
286
+ * task ID lands as a bare string in `updated[0]`.
287
+ * Use `/data/updated/0` (a string like "T11692"), NOT `/data/updated/0/id`.
288
+ */
289
+ const tasksUpdateOutputContract = {
290
+ operation: 'tasks.update',
291
+ shapeNote: 'The updated task ID (bare string) is at /data/updated/0 — NOT /data/updated/0/id. ' +
292
+ 'Use /data/status for the post-mutation status.',
293
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
294
+ fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
295
+ };
296
+ /**
297
+ * OUTPUT contract for `tasks.complete`.
298
+ *
299
+ * Identical envelope to `tasks.update` — completion is a status mutation that
300
+ * lands the task ID in `updated[0]` as a bare string.
301
+ */
302
+ const tasksCompleteOutputContract = {
303
+ operation: 'tasks.complete',
304
+ shapeNote: 'Completion is a status mutation — the task ID (bare string) is at /data/updated/0 ' +
305
+ '(status=done). Use /data/status for the post-mutation status.',
306
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
307
+ fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
308
+ };
309
+ // ---------------------------------------------------------------------------
310
+ // Registry
311
+ // ---------------------------------------------------------------------------
312
+ /**
313
+ * SSoT registry of per-operation OUTPUT contracts, keyed by the canonical
314
+ * `<domain>.<verb>` operation id. Extend this map as operations are migrated
315
+ * to the schema-first OUTPUT surface (high-traffic first).
316
+ *
317
+ * @task T11692
318
+ */
319
+ export const OUTPUT_CONTRACTS = {
320
+ 'tasks.show': tasksShowOutputContract,
321
+ 'tasks.list': tasksListOutputContract,
322
+ 'tasks.find': tasksFindOutputContract,
323
+ 'tasks.add': tasksAddOutputContract,
324
+ 'tasks.add-batch': tasksAddBatchOutputContract,
325
+ 'tasks.update': tasksUpdateOutputContract,
326
+ 'tasks.complete': tasksCompleteOutputContract,
327
+ };
328
+ //# sourceMappingURL=output-contracts-data.js.map