create-agdf 0.20.1 → 0.20.2
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/README.md +6 -1
- package/generated/.agdf/control/templates/artefacts/SD.md +7 -1
- package/generated/.agdf/control/templates/artefacts/TP.md +4 -0
- package/generated/.opencode/contracts/quality.md +71 -4
- package/generated/.opencode/contracts/sd-definition.md +16 -0
- package/generated/.opencode/contracts/tp-definition.md +6 -0
- package/generated/.opencode/skills/agdf-clean-implementation-review/SKILL.md +2 -0
- package/generated/.opencode/skills/agdf-code-review/SKILL.md +2 -0
- package/generated/.opencode/skills/agdf-qa-gate/SKILL.md +10 -9
- package/generated/.opencode/skills/agdf-sd-definition/SKILL.md +2 -1
- package/generated/.opencode/skills/agdf-task-plan-review/SKILL.md +2 -0
- package/generated/.opencode/skills/agdf-tp-definition/SKILL.md +2 -1
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/artefacts/SD.md +7 -1
- package/generated/plugins/agdf/control/templates/artefacts/TP.md +4 -0
- package/generated/plugins/agdf/mcp/server/README.md +5 -5
- package/generated/plugins/agdf/mcp/server/package.json +2 -2
- package/generated/plugins/agdf/mcp/server/src/server.js +21 -8
- package/generated/plugins/agdf/mcp/server/src/ui-capability.js +49 -1
- package/generated/plugins/agdf/meta/agdf-mcp-capability.json +2 -2
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +5 -3
- package/generated/plugins/agdf/meta/contracts/quality.md +71 -4
- package/generated/plugins/agdf/meta/contracts/sd-definition.md +16 -0
- package/generated/plugins/agdf/meta/contracts/tp-definition.md +6 -0
- package/generated/plugins/agdf/meta/copilot-payload-baseline.json +2 -2
- package/generated/plugins/agdf/meta/distribution-profile-history.json +7 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +5 -3
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/quality.md +71 -4
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/sd-definition.md +16 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/tp-definition.md +6 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +10 -2
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/contract.js +26 -34
- package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/service.js +26 -8
- package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-command-contract.js +140 -9
- package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +2 -2
- package/generated/plugins/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/skills/clean-implementation-review/SKILL.md +2 -0
- package/generated/plugins/agdf/skills/code-review/SKILL.md +2 -0
- package/generated/plugins/agdf/skills/qa-gate/SKILL.md +10 -9
- package/generated/plugins/agdf/skills/sd-definition/SKILL.md +2 -1
- package/generated/plugins/agdf/skills/task-plan-review/SKILL.md +2 -0
- package/generated/plugins/agdf/skills/tp-definition/SKILL.md +2 -1
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +74 -74
- package/generated/plugins/copilot/agdf/control/templates/artefacts/SD.md +7 -1
- package/generated/plugins/copilot/agdf/control/templates/artefacts/TP.md +4 -0
- package/generated/plugins/copilot/agdf/mcp/server/README.md +5 -5
- package/generated/plugins/copilot/agdf/mcp/server/package.json +2 -2
- package/generated/plugins/copilot/agdf/mcp/server/src/server.js +21 -8
- package/generated/plugins/copilot/agdf/mcp/server/src/ui-capability.js +49 -1
- package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +5 -3
- package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +7 -0
- package/generated/plugins/copilot/agdf/plugin.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +5 -3
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/quality.md +71 -4
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/sd-definition.md +16 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/contracts/tp-definition.md +6 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/mcp-dispatch-runtime.js +10 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/contract.js +26 -34
- package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-inspect/service.js +26 -8
- package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-command-contract.js +140 -9
- package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/control-state/run-presentation-render.js +2 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/runtime/core/lib/interaction-presentation.js +1 -1
- package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/copilot/agdf/skills/agdf-clean-implementation-review/SKILL.md +2 -0
- package/generated/plugins/copilot/agdf/skills/agdf-code-review/SKILL.md +2 -0
- package/generated/plugins/copilot/agdf/skills/agdf-qa-gate/SKILL.md +10 -9
- package/generated/plugins/copilot/agdf/skills/agdf-sd-definition/SKILL.md +2 -1
- package/generated/plugins/copilot/agdf/skills/agdf-task-plan-review/SKILL.md +2 -0
- package/generated/plugins/copilot/agdf/skills/agdf-tp-definition/SKILL.md +2 -1
- package/generated/plugins/copilot/agdf/skills/contracts/quality.md +71 -4
- package/generated/plugins/copilot/agdf/skills/contracts/sd-definition.md +16 -0
- package/generated/plugins/copilot/agdf/skills/contracts/tp-definition.md +6 -0
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +5 -3
- package/generated/submissions/openai/agdf/meta/contracts/quality.md +71 -4
- package/generated/submissions/openai/agdf/meta/contracts/sd-definition.md +16 -0
- package/generated/submissions/openai/agdf/meta/contracts/tp-definition.md +6 -0
- package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +2 -2
- package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +7 -0
- package/generated/submissions/openai/agdf/plugin.json +1 -1
- package/generated/submissions/openai/agdf/skills/clean-implementation-review/SKILL.md +2 -0
- package/generated/submissions/openai/agdf/skills/code-review/SKILL.md +2 -0
- package/generated/submissions/openai/agdf/skills/qa-gate/SKILL.md +10 -9
- package/generated/submissions/openai/agdf/skills/sd-definition/SKILL.md +2 -1
- package/generated/submissions/openai/agdf/skills/task-plan-review/SKILL.md +2 -0
- package/generated/submissions/openai/agdf/skills/tp-definition/SKILL.md +2 -1
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +21 -21
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/mcp-dispatch-runtime.js +10 -2
- package/lib/mcp-lifecycle/adapters/copilot.js +13 -7
- package/lib/mcp-lifecycle/adapters/opencode.js +97 -8
- package/lib/mcp-lifecycle/profile.js +2 -1
- package/package.json +1 -1
- package/runtime/core/lib/control-inspect/contract.js +26 -34
- package/runtime/core/lib/control-inspect/service.js +26 -8
- package/runtime/core/lib/control-state/run-command-contract.js +140 -9
- package/runtime/core/lib/control-state/run-presentation-render.js +2 -2
- package/runtime/core/lib/interaction-presentation.js +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, rmSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import {
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
atomicWrite,
|
|
7
7
|
fileSnapshot,
|
|
8
8
|
inheritedPermissionEffect,
|
|
9
|
-
readJsonObject,
|
|
10
9
|
referenceIdentityForSurface,
|
|
11
10
|
restoreSnapshot,
|
|
12
11
|
} from "./file-utils.js";
|
|
@@ -15,8 +14,75 @@ const OWNER = "create-agdf:mcp-runtime";
|
|
|
15
14
|
const CREATED_CONFIG = "AGDF_MCP_CREATED_CONFIG";
|
|
16
15
|
const SCHEMA = "https://opencode.ai/config.json";
|
|
17
16
|
|
|
17
|
+
// Preserve offsets: comments and trailing commas are syntax, never string data.
|
|
18
|
+
function jsonc(content) {
|
|
19
|
+
const tokens = [], clean = content.split('');
|
|
20
|
+
for (const match of content.matchAll(/"(?:\\[\s\S]|[^"\\])*"|\/\/[^\r\n]*|\/\*[\s\S]*?\*\/|[{}\[\]:,]|[^\s{}\[\]:,/"']+|[^\s]/gu)) {
|
|
21
|
+
const text = match[0], start = match.index, end = start + text.length;
|
|
22
|
+
if (text.startsWith('//') || text.startsWith('/*')) {
|
|
23
|
+
for (let i = start; i < end; ++i) if (!/[\r\n]/u.test(clean[i])) clean[i] = ' ';
|
|
24
|
+
} else tokens.push({ text, start, end });
|
|
25
|
+
}
|
|
26
|
+
for (let i = 0; i < tokens.length - 1; ++i) {
|
|
27
|
+
if (i > 0 && tokens[i].text === ',' && ['}', ']'].includes(tokens[i + 1].text)
|
|
28
|
+
&& !['{', '[', ':', ','].includes(tokens[i - 1].text)) clean[tokens[i].start] = ' ';
|
|
29
|
+
}
|
|
30
|
+
const sanitized = clean.join(''), config = JSON.parse(sanitized);
|
|
31
|
+
let cursor = 0;
|
|
32
|
+
function node() {
|
|
33
|
+
const first = tokens[cursor++], properties = [];
|
|
34
|
+
if (!['{', '['].includes(first.text)) return { ...first };
|
|
35
|
+
const object = first.text === '{', close = object ? '}' : ']';
|
|
36
|
+
while (tokens[cursor].text !== close) {
|
|
37
|
+
const key = object ? tokens[cursor++] : null;
|
|
38
|
+
if (object) ++cursor; // colon; JSON.parse already validated the grammar
|
|
39
|
+
const value = node(), comma = tokens[cursor].text === ',' ? tokens[cursor++] : null;
|
|
40
|
+
if (object) {
|
|
41
|
+
const name = JSON.parse(key.text);
|
|
42
|
+
if (properties.some(row => row.name === name)) throw Error('duplicate_key');
|
|
43
|
+
properties.push({ name, start: key.start, value, comma });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { start: first.start, end: tokens[cursor++].end, ...(object ? { properties } : {}) };
|
|
47
|
+
}
|
|
48
|
+
return { config, tree: node(), preserveSyntax: sanitized !== content };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function editJsonc(content, path, value) {
|
|
52
|
+
let { tree } = jsonc(content);
|
|
53
|
+
for (let i = 0; i < path.length; ++i) {
|
|
54
|
+
if (!tree.properties) throw Error('invalid_object');
|
|
55
|
+
const index = tree.properties.findIndex(row => row.name === path[i]), property = tree.properties[index];
|
|
56
|
+
if (property && i < path.length - 1) { tree = property.value; continue; }
|
|
57
|
+
const edits = [];
|
|
58
|
+
if (property) {
|
|
59
|
+
if (value !== undefined) edits.push([property.value.start, property.value.end, JSON.stringify(value, null, 2)]);
|
|
60
|
+
else {
|
|
61
|
+
edits.push([property.start, property.comma?.end ?? property.value.end, '']);
|
|
62
|
+
const previous = tree.properties[index - 1]?.comma;
|
|
63
|
+
if (!property.comma && previous) edits.push([previous.start, previous.end, '']);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
if (value === undefined) return content;
|
|
67
|
+
for (const key of path.slice(i + 1).reverse()) value = { [key]: value };
|
|
68
|
+
const last = tree.properties.at(-1);
|
|
69
|
+
if (last && !last.comma) edits.push([last.value.end, last.value.end, ',']);
|
|
70
|
+
edits.push([tree.end - 1, tree.end - 1, `\n${' '.repeat(i + 1)}${JSON.stringify(path[i])}: ${JSON.stringify(value, null, 2)}\n${' '.repeat(i)}`]);
|
|
71
|
+
}
|
|
72
|
+
for (const [start, end, text] of edits.sort((a, b) => b[0] - a[0])) content = content.slice(0, start) + text + content.slice(end);
|
|
73
|
+
return content;
|
|
74
|
+
}
|
|
75
|
+
throw Error('invalid_path');
|
|
76
|
+
}
|
|
77
|
+
|
|
18
78
|
function readConfig(path) {
|
|
19
|
-
|
|
79
|
+
let value;
|
|
80
|
+
try {
|
|
81
|
+
const content = existsSync(path) ? readFileSync(path, 'utf8') : '{}';
|
|
82
|
+
const parsed = jsonc(content);
|
|
83
|
+
value = { content, config: parsed.config, preserveSyntax: path.endsWith('.jsonc') || parsed.preserveSyntax };
|
|
84
|
+
if (!value.config || typeof value.config !== 'object' || Array.isArray(value.config)) throw Error('invalid');
|
|
85
|
+
} catch { throw Error('AGDF_MCP_OPENCODE_CONFIG_INVALID'); }
|
|
20
86
|
if (value.config.mcp !== undefined && (!value.config.mcp || typeof value.config.mcp !== "object" || Array.isArray(value.config.mcp))) {
|
|
21
87
|
throw new Error("AGDF_MCP_OPENCODE_CONFIG_INVALID");
|
|
22
88
|
}
|
|
@@ -64,13 +130,26 @@ function inspectPath(path, expected) {
|
|
|
64
130
|
}
|
|
65
131
|
|
|
66
132
|
function sources({ scope, target, env }) {
|
|
133
|
+
const user = env.OPENCODE_CONFIG_DIR || join(homedir(), '.config', 'opencode');
|
|
134
|
+
const files = (id, directory, priority) => {
|
|
135
|
+
const paths = ['opencode.jsonc', 'opencode.json'].map(name => join(directory, name)).filter(existsSync);
|
|
136
|
+
if (!paths.length) paths.push(join(directory, 'opencode.json'));
|
|
137
|
+
// JSONC wins conflicting keys. Otherwise update the file owning the entry,
|
|
138
|
+
// rather than silently shadowing it by creating a second registration.
|
|
139
|
+
const selected = paths.find(path => {
|
|
140
|
+
const { config } = readConfig(path);
|
|
141
|
+
return config.mcp?.agdf !== undefined || config.mcp?.servers?.agdf !== undefined;
|
|
142
|
+
}) ?? paths[0];
|
|
143
|
+
return paths.map((path, index) => ({ id: path === selected ? id : `${id}_${path.endsWith('.jsonc') ? 'jsonc' : 'json'}`,
|
|
144
|
+
path, priority: priority + index, selected: scope === id && path === selected }));
|
|
145
|
+
};
|
|
67
146
|
return [
|
|
68
147
|
...(typeof env.OPENCODE_CONFIG_CONTENT === "string" && env.OPENCODE_CONFIG_CONTENT.trim()
|
|
69
148
|
? [{ id: "inline", path: "env:OPENCODE_CONFIG_CONTENT", priority: 0, selected: false, inline: env.OPENCODE_CONFIG_CONTENT }] : []),
|
|
70
149
|
...(typeof env.OPENCODE_CONFIG === "string" && env.OPENCODE_CONFIG.trim()
|
|
71
150
|
? [{ id: "custom", path: env.OPENCODE_CONFIG, priority: 5, selected: false }] : []),
|
|
72
|
-
|
|
73
|
-
|
|
151
|
+
...files('project', target, 10),
|
|
152
|
+
...files('user', user, 20),
|
|
74
153
|
];
|
|
75
154
|
}
|
|
76
155
|
|
|
@@ -80,7 +159,7 @@ function inspect(input) {
|
|
|
80
159
|
const states = new Map(list.map((source) => {
|
|
81
160
|
if (!source.inline) return [source.id, inspectPath(source.path, input.spec)];
|
|
82
161
|
try {
|
|
83
|
-
const config =
|
|
162
|
+
const config = jsonc(source.inline).config;
|
|
84
163
|
if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error("invalid");
|
|
85
164
|
return [source.id, inspectConfig(config, source.path, input.spec)];
|
|
86
165
|
} catch { return [source.id, { status: "invalid", path: source.path, identity: null }]; }
|
|
@@ -96,7 +175,7 @@ function transaction({ action, scope, target, spec, previousSpec, env }) {
|
|
|
96
175
|
return { status: "unchanged", path: current.path, apply() {}, rollback() {} };
|
|
97
176
|
}
|
|
98
177
|
const snapshot = fileSnapshot(current.path);
|
|
99
|
-
const { config } = readConfig(current.path);
|
|
178
|
+
const { config, content, preserveSyntax } = readConfig(current.path);
|
|
100
179
|
const mcp = { ...(config.mcp ?? {}) };
|
|
101
180
|
const nested = spec.host?.config_variant === "nested_v2";
|
|
102
181
|
const createdConfig = !snapshot.existed || current.created_config === true;
|
|
@@ -117,7 +196,17 @@ function transaction({ action, scope, target, spec, previousSpec, env }) {
|
|
|
117
196
|
const remaining = Object.keys(next);
|
|
118
197
|
const removable = current.created_config === true
|
|
119
198
|
&& (remaining.length === 0 || (remaining.length === 1 && remaining[0] === "$schema" && next.$schema === SCHEMA));
|
|
120
|
-
|
|
199
|
+
let after = `${JSON.stringify(next, null, 2)}\n`;
|
|
200
|
+
if (snapshot.existed && preserveSyntax) {
|
|
201
|
+
after = content;
|
|
202
|
+
for (const keys of [['mcp', 'agdf'], ['mcp', 'servers', 'agdf'], ['mcp', 'servers'], ['mcp']]) {
|
|
203
|
+
const beforeValue = keys.reduce((row, key) => row?.[key], config), nextValue = keys.reduce((row, key) => row?.[key], next);
|
|
204
|
+
if ((keys.length > 2 || keys.at(-1) === 'agdf' || nextValue === undefined) && JSON.stringify(beforeValue) !== JSON.stringify(nextValue)) {
|
|
205
|
+
after = editJsonc(after, keys, nextValue);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (action === 'disable' && removable) after = null;
|
|
121
210
|
return { status: "changed", path: current.path,
|
|
122
211
|
apply() { if (after === null) rmSync(current.path, { force: true }); else atomicWrite(current.path, after); },
|
|
123
212
|
rollback() { restoreSnapshot(snapshot); },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mcpQualificationFields, validateMcpQualificationRecord } from "./evidence.js";
|
|
2
|
+
import { SKILL_DISPATCH_FUNCTION_DEFINITION } from '#agdf-core/skill-dispatch/contract.js';
|
|
2
3
|
|
|
3
4
|
const CAPABILITY_STATES = new Set(["supported", "manual_compatible", "unavailable", "unsupported", "unverified"]);
|
|
4
5
|
const REGISTRATION_STATES = new Set(["absent", "matched", "foreign", "owned_mismatch", "precedence_conflict", "invalid"]);
|
|
@@ -26,7 +27,7 @@ export function validateMcpCapabilityProfile(profile, { expectedVersion } = {})
|
|
|
26
27
|
if (profile?.tool?.semantic_owner !== "packages/core/lib/skill-dispatch/contract.js") errors.push("semantic_owner");
|
|
27
28
|
if (Object.hasOwn(profile?.tool ?? {}, "description") || Object.hasOwn(profile?.tool ?? {}, "input_schema")
|
|
28
29
|
|| Object.hasOwn(profile?.tool ?? {}, "output_schema")) errors.push("duplicated_tool_semantics");
|
|
29
|
-
if (profile?.tool?.authorizes !== false || profile?.tool?.read_only !==
|
|
30
|
+
if (profile?.tool?.authorizes !== false || profile?.tool?.read_only !== SKILL_DISPATCH_FUNCTION_DEFINITION.annotations.readOnlyHint) errors.push("tool_boundary");
|
|
30
31
|
if (profile?.package?.name !== "@agdf/mcp-server" || profile?.package?.sdk_server !== "2.0.0") errors.push("package");
|
|
31
32
|
if (profile?.lifecycle?.owner !== "create-agdf" || profile?.lifecycle?.default_scope !== "project") errors.push("lifecycle");
|
|
32
33
|
if (profile?.lifecycle?.result_contract_version !== 2) errors.push("result_contract_version");
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ import { ALL_ACTIVE_OPERATIONS, VARIANT_OPERATION, MODULE_OPERATION, CONTROL_INS
|
|
|
14
14
|
|
|
15
15
|
export const CONTROL_INSPECT_SCHEMA_VERSION = "1";
|
|
16
16
|
export const CONTROL_INSPECT_CONTRACT_VERSION = 1;
|
|
17
|
-
//
|
|
17
|
+
// Shared discovery budget across hosts (SDD-005).
|
|
18
18
|
export const CONTROL_INSPECT_MAX_DEFINITION_BYTES = 5000;
|
|
19
19
|
export const OPERATION_CONTRACT_MODULES = Object.freeze(Object.entries(RUN_COMMAND_GROUPS)
|
|
20
20
|
.flatMap(([group, kinds]) => kinds.map(kind => `${group}/${kind}`)));
|
|
@@ -25,28 +25,29 @@ function deepFreeze(value) {
|
|
|
25
25
|
return Object.freeze(value);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// Shared discovery, selector guards and on-demand help.
|
|
29
29
|
export const PRESENTATION_OPERATIONS = Object.freeze(['presentation-document', 'presentation-preview']);
|
|
30
30
|
export const BOUND_INSPECT_OPERATIONS = Object.freeze(['artifact-readiness', 'operation-contract', 'operation-status', 'native-qualification', ...PRESENTATION_OPERATIONS, 'runtime-generations']);
|
|
31
31
|
export const PRESENTATION_CATALOG_MODULE = 'agdf_prepare/presentation.prepare';
|
|
32
32
|
const uuidSchema = { type: 'string', pattern: REVISION_ID_PATTERN.source };
|
|
33
33
|
const cursorSchema = { type: 'string', maxLength: 80, pattern: '^[0-9a-f]{64}:(?:0|[1-9][0-9]{0,6})$' };
|
|
34
34
|
const fieldModel = {
|
|
35
|
-
operation: { help: 'Read
|
|
36
|
-
presentation_language: { help: 'Required BCP47 tag; valid
|
|
37
|
-
working_directory: { help: 'Absolute
|
|
38
|
-
target_source: { help: '
|
|
39
|
-
primary_target: { help: 'Confirmed absolute
|
|
40
|
-
run_id: { help: '
|
|
41
|
-
gate: { help: '
|
|
42
|
-
expected_revision_id: { help: '
|
|
43
|
-
presentation_id: { help: '
|
|
44
|
-
target_id: { help: 'Copy
|
|
45
|
-
cursor: { help: 'Omit
|
|
46
|
-
variant: { help: 'gate-check only
|
|
47
|
-
module: { help: 'contract
|
|
48
|
-
all_active: { help: '
|
|
35
|
+
operation: { help: 'Read only; never prepare or approve.', schema: { enum: [...CONTROL_INSPECT_OPERATIONS] }, description: 'Read action.' },
|
|
36
|
+
presentation_language: { help: 'Required BCP47 tag; unsupported valid languages fall back to complete English.', schema: { type: 'string', maxLength: 64, pattern: PRESENTATION_LANGUAGE_TAG_PATTERN_SOURCE }, description: 'Reply BCP47 tag.' },
|
|
37
|
+
working_directory: { help: 'Absolute context; never selects a target.', schema: { type: 'string', minLength: 1, maxLength: 4096 }, description: 'Context; not target authority.' },
|
|
38
|
+
target_source: { help: 'Actual provenance: explicit_target = specified; continued_target = confirmed; current_repository = unambiguous.', schema: { enum: [...TASK_TARGET_SOURCES] }, description: 'Requires primary_target.' },
|
|
39
|
+
primary_target: { help: 'Confirmed absolute root paired with target_source; cwd is not target authority.', schema: { type: 'string', minLength: 1, maxLength: 4096 }, description: 'Pair: target_source.' },
|
|
40
|
+
run_id: { help: 'Named or confirmed Run; never select by recency.', schema: { type: 'string', pattern: RUN_ID_PATTERN.source }, description: 'Confirmed run.' },
|
|
41
|
+
gate: { help: 'Requested document gate or catalog filter, never a selection heuristic. artifact-readiness: UR/PRD/SD/TP only.', schema: { enum: [...APPROVAL_GATES] }, description: 'Document gate. Never with gate-check.', operations: ['artifact-readiness', 'operation-contract', ...PRESENTATION_OPERATIONS] },
|
|
42
|
+
expected_revision_id: { help: 'Bound inspections use current gate-check revision. Document/preview reads preserve the original selected catalog or conversation revision.', schema: { $ref: '#/$defs/i' }, description: 'gate-check revision; original for reads.', operations: BOUND_INSPECT_OPERATIONS },
|
|
43
|
+
presentation_id: { help: 'Exact prepared ID from validated catalog or conversation. Never guess, select by timestamp, or prepare a replacement to read.', schema: { $ref: '#/$defs/i' }, description: 'ID from catalog/conversation.', operations: PRESENTATION_OPERATIONS },
|
|
44
|
+
target_id: { help: 'Copy target hash from operation-contract binding or catalog read_arguments; never grants authority.', schema: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' }, description: 'Target hash: operation-contract.', operations: PRESENTATION_OPERATIONS },
|
|
45
|
+
cursor: { help: 'Omit initially; copy next_cursor. Catalog cursors: only operation-contract module=agdf_prepare/presentation.prepare. Never exchange cursor types.', schema: cursorSchema, description: 'Copy next_cursor.', operations: [...PRESENTATION_OPERATIONS, 'operation-contract'] },
|
|
46
|
+
variant: { help: 'gate-check view only; never records approval.', schema: { enum: [...GATE_CHECK_VARIANTS] }, description: 'gate-check view.', operations: [VARIANT_OPERATION] },
|
|
47
|
+
module: { help: 'contract: advertised module; operation-contract: group/kind; operation-status: original operation UUID from saved receipt, never a new UUID.', schema: { type: 'string', minLength: 1, maxLength: 64 }, description: 'Contract, group/kind or receipt UUID.', operations: [MODULE_OPERATION, 'operation-contract', 'operation-status'] },
|
|
48
|
+
all_active: { help: 'true: doctor/delivery-map only; false: selected Run.', schema: { type: 'boolean' }, description: 'All runs: doctor/delivery-map.' },
|
|
49
49
|
};
|
|
50
|
+
const selectorKeys = { gate: 'gate', expected_revision_id: 'expectedRevisionId', presentation_id: 'presentationId', target_id: 'targetId', cursor: 'cursor', variant: 'variant', module: 'contractModule' };
|
|
50
51
|
const moduleOperations = [MODULE_OPERATION, 'operation-contract', 'operation-status'];
|
|
51
52
|
const inputSchema = {
|
|
52
53
|
type: 'object', additionalProperties: false,
|
|
@@ -69,9 +70,12 @@ const inputSchema = {
|
|
|
69
70
|
],
|
|
70
71
|
};
|
|
71
72
|
|
|
73
|
+
inputSchema.dependentSchemas.operation = { allOf: inputSchema.allOf };
|
|
74
|
+
delete inputSchema.allOf;
|
|
75
|
+
|
|
72
76
|
const inspectContract = {
|
|
73
77
|
name: "agdf_inspect",
|
|
74
|
-
description: "Read-only
|
|
78
|
+
description: "Read-only. gate-check: language, cwd, target_source+primary_target, Run; omit gate/module. Follow navigation.next_calls sequentially; read all pages.",
|
|
75
79
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
76
80
|
inputSchema,
|
|
77
81
|
outputSchema: {
|
|
@@ -103,10 +107,7 @@ const inspectContract = {
|
|
|
103
107
|
},
|
|
104
108
|
};
|
|
105
109
|
|
|
106
|
-
//
|
|
107
|
-
// bounded envelope; repeating every forwarding branch in each model session is
|
|
108
|
-
// unnecessary. Both declarations derive from this same contract, without a
|
|
109
|
-
// second evaluator or a larger discovery budget.
|
|
110
|
+
// Core retains full result validation; discovery advertises the bounded envelope.
|
|
110
111
|
export const CONTROL_INSPECT_OUTPUT_SCHEMA = deepFreeze(inspectContract.outputSchema);
|
|
111
112
|
export const CONTROL_INSPECT_FUNCTION_DEFINITION = deepFreeze({
|
|
112
113
|
...inspectContract,
|
|
@@ -134,7 +135,7 @@ export function inspectContractHelp() {
|
|
|
134
135
|
examples: [ { ...base, operation: 'gate-check' },
|
|
135
136
|
{ ...base, operation: 'operation-contract', module: PRESENTATION_CATALOG_MODULE, expected_revision_id: revision, gate: 'UR' },
|
|
136
137
|
{ ...base, operation: 'presentation-document', expected_revision_id: revision, gate: 'UR', presentation_id: '00000000-0000-4000-8000-000000000002', target_id: `sha256:${'0'.repeat(64)}` } ],
|
|
137
|
-
provenance: '
|
|
138
|
+
provenance: 'Preserve exact conversation bindings; otherwise require unique complete catalog discovery or explicit selection. Reads keep original revisions. Prepared proves neither viewing nor approval. Catalog cursors require the catalog module.',
|
|
138
139
|
catalog_limits: { page_candidates: 16, record_files: 256, record_bytes: 1048576, directory_entries: 1024 },
|
|
139
140
|
});
|
|
140
141
|
}
|
|
@@ -172,13 +173,7 @@ export function parseControlInspectFunctionArguments(argumentsValue, trustedCont
|
|
|
172
173
|
targetSource: argumentsValue.target_source,
|
|
173
174
|
primaryTarget: argumentsValue.primary_target,
|
|
174
175
|
runId: argumentsValue.run_id,
|
|
175
|
-
...(
|
|
176
|
-
...(argumentsValue.expected_revision_id !== undefined ? { expectedRevisionId: argumentsValue.expected_revision_id } : {}),
|
|
177
|
-
...(argumentsValue.presentation_id !== undefined ? { presentationId: argumentsValue.presentation_id } : {}),
|
|
178
|
-
...(argumentsValue.target_id !== undefined ? { targetId: argumentsValue.target_id } : {}),
|
|
179
|
-
...(argumentsValue.cursor !== undefined ? { cursor: argumentsValue.cursor } : {}),
|
|
180
|
-
...(argumentsValue.variant !== undefined ? { variant: argumentsValue.variant } : {}),
|
|
181
|
-
...(argumentsValue.module !== undefined ? { contractModule: argumentsValue.module } : {}),
|
|
176
|
+
...Object.fromEntries(Object.entries(selectorKeys).filter(([field]) => argumentsValue[field] !== undefined).map(([field, key]) => [key, argumentsValue[field]])),
|
|
182
177
|
...(argumentsValue.all_active !== undefined ? { allActive: argumentsValue.all_active } : {}),
|
|
183
178
|
surface: trustedContext.surface,
|
|
184
179
|
expectedVersion: trustedContext.expectedVersion,
|
|
@@ -186,8 +181,7 @@ export function parseControlInspectFunctionArguments(argumentsValue, trustedCont
|
|
|
186
181
|
});
|
|
187
182
|
}
|
|
188
183
|
|
|
189
|
-
//
|
|
190
|
-
// read-selection rules. Throws SkillDispatchInputError or ReadSelectionError; both carry `field`.
|
|
184
|
+
// Shared language, target and read-selection rules; errors carry the invalid field.
|
|
191
185
|
export function normalizeControlInspectInput(input) {
|
|
192
186
|
const operation = requireText(input.operation, "operation", 64);
|
|
193
187
|
if (!CONTROL_INSPECT_OPERATIONS.includes(operation)) throw new SkillDispatchInputError("operation", `Unsupported inspect operation: ${operation}`);
|
|
@@ -240,7 +234,6 @@ export function normalizeControlInspectInput(input) {
|
|
|
240
234
|
contractModule: input.contractModule,
|
|
241
235
|
variant: input.variant,
|
|
242
236
|
});
|
|
243
|
-
const selectorKeys = { gate: 'gate', expected_revision_id: 'expectedRevisionId', presentation_id: 'presentationId', target_id: 'targetId', cursor: 'cursor', variant: 'variant', module: 'contractModule' };
|
|
244
237
|
for (const [field, key] of Object.entries(selectorKeys)) {
|
|
245
238
|
if (input[key] !== undefined && !fieldModel[field].operations.includes(operation)) throw new SkillDispatchInputError(field, `Unsupported selector for ${operation}`);
|
|
246
239
|
}
|
|
@@ -269,8 +262,7 @@ export { ReadSelectionError };
|
|
|
269
262
|
|
|
270
263
|
export const CONTROL_INSPECT_OUTPUT_TOO_LARGE_RECOVERY = "The inspect result exceeds the 1 MiB MCP output limit. Narrow the selection (one run_id instead of all_active) or use the CLI with --json.";
|
|
271
264
|
|
|
272
|
-
//
|
|
273
|
-
// receives a schema-valid recovery instead of a schema error.
|
|
265
|
+
// Preserve the result envelope when enforcing the shared 1 MiB output ceiling.
|
|
274
266
|
export function serializeControlInspectResult(result) {
|
|
275
267
|
const output = JSON.stringify(result, null, 2);
|
|
276
268
|
if (Buffer.byteLength(output, "utf8") <= SKILL_DISPATCH_MAX_OUTPUT_BYTES) return output;
|
|
@@ -8,7 +8,7 @@ import { renderSkillDispatchInputRecovery, renderSkillDispatchRecovery, renderTa
|
|
|
8
8
|
import { resolveTaskTarget, TaskTargetInputError } from "../task-target-resolution.js";
|
|
9
9
|
import { SKILL_DISPATCH_PRESENTATION_LANGUAGE_RECOVERY, SkillDispatchInputError, emptySkillDispatchTiming } from "../skill-dispatch/contract.js";
|
|
10
10
|
import { CONTROL_INSPECT_CONTRACT_VERSION, CONTROL_INSPECT_SCHEMA_VERSION, ReadSelectionError, normalizeControlInspectInput, inspectContractHelp, PRESENTATION_CATALOG_MODULE } from "./contract.js";
|
|
11
|
-
import { operationContract } from '../control-state/run-command-contract.js';
|
|
11
|
+
import { RUN_COMMAND_DESCRIPTORS, operationContract } from '../control-state/run-command-contract.js';
|
|
12
12
|
import { inspectRunOperation } from '../control-state/run-operation-status.js';
|
|
13
13
|
import { resolveControlCommandTarget } from '../control-state/approval-command-contract.js';
|
|
14
14
|
import { readRunState } from '../control-evaluation/run-state.js';
|
|
@@ -22,11 +22,8 @@ import { sourceRevisionBoundary } from '../control-state/run-revision-boundary.j
|
|
|
22
22
|
import { sourceRevisionQualityFacts } from '../control-state/run-source-revisions.js';
|
|
23
23
|
import { readHistoryBytes } from '../control-state/run-revision-history.js';
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// existing content validators through its read-only Core owner; it adds no CLI command. It never writes,
|
|
28
|
-
// never selects a run on its own and never prepares an approval binding. Evaluators run with the
|
|
29
|
-
// dispatcher's dependency set: no git child process, so verified_change git observation is absent.
|
|
25
|
+
// Read-only projection of canonical CLI evaluators and Core validators. It never
|
|
26
|
+
// writes, selects a Run or prepares approval. Git observations are unavailable.
|
|
30
27
|
|
|
31
28
|
const defaultNow = () => process.hrtime.bigint();
|
|
32
29
|
const milliseconds = (start, end) => Number(end - start) / 1_000_000;
|
|
@@ -200,7 +197,7 @@ export function createControlInspectService(dependencies = {}) {
|
|
|
200
197
|
return bindHostAction(result);
|
|
201
198
|
}
|
|
202
199
|
const controlStarted = now();
|
|
203
|
-
const { report, presentation, invalidModule } = stage(DISPATCH_RECOVERY.control_evaluation_failed, () => {
|
|
200
|
+
const { report, presentation, invalidModule, navigation } = stage(DISPATCH_RECOVERY.control_evaluation_failed, () => {
|
|
204
201
|
validateControlReadBoundary?.(target.governance_target);
|
|
205
202
|
const selection = {
|
|
206
203
|
...(input.run_id ? { runId: input.run_id } : {}),
|
|
@@ -270,7 +267,27 @@ export function createControlInspectService(dependencies = {}) {
|
|
|
270
267
|
case "gate-check": {
|
|
271
268
|
const evaluate = () => gateCheck(target.governance_target, { ...selection, presentationLanguage: input.presentation_language });
|
|
272
269
|
const evaluated = evaluate();
|
|
273
|
-
|
|
270
|
+
let navigation;
|
|
271
|
+
// Optional hints never replace the canonical report.
|
|
272
|
+
try {
|
|
273
|
+
if (input.run_id && input.target_source && input.primary_target && evaluated.status_card?.run_id === input.run_id) {
|
|
274
|
+
const revisionId = evaluated.status_card.runState?.meta?.revision_id;
|
|
275
|
+
const targetId = resolveControlCommandTarget(target.governance_target).target_id;
|
|
276
|
+
const common = Object.fromEntries(['presentation_language', 'working_directory', 'target_source', 'primary_target', 'run_id'].map(key => [key, input[key]]));
|
|
277
|
+
common.expected_revision_id = revisionId;
|
|
278
|
+
const catalog = readPresentationCatalog(target.governance_target, { runId: input.run_id, revisionId, targetId,
|
|
279
|
+
gate: evaluated.current_gate, workingDirectory: input.working_directory, targetSource: input.target_source,
|
|
280
|
+
primaryTarget: input.primary_target, language: input.presentation_language });
|
|
281
|
+
const kind = evaluated.next_operation?.operation_key;
|
|
282
|
+
const group = RUN_COMMAND_DESCRIPTORS[kind]?.group;
|
|
283
|
+
const nextCalls = catalog.state !== 'unavailable' && group ? [{ tool: 'agdf_inspect', arguments: {
|
|
284
|
+
operation: 'operation-contract', ...common, module: `${group}/${kind}` } }] : [];
|
|
285
|
+
if (catalog.state === 'unique' && catalog.complete_scan && catalog.page_complete)
|
|
286
|
+
nextCalls.push({ tool: 'agdf_inspect', arguments: catalog.candidates[0].read_arguments });
|
|
287
|
+
navigation = { presentation_catalog: catalog, next_calls: nextCalls };
|
|
288
|
+
}
|
|
289
|
+
} catch { /* Advisory navigation unavailable. */ }
|
|
290
|
+
return { report: evaluated, navigation, presentation: gateCheckPresentation(evaluated, input.variant, evaluate) };
|
|
274
291
|
}
|
|
275
292
|
case "delivery-map": {
|
|
276
293
|
const evaluated = deliveryMap(target.governance_target, selection, deliveryMapDependencies);
|
|
@@ -299,6 +316,7 @@ export function createControlInspectService(dependencies = {}) {
|
|
|
299
316
|
result.target = target;
|
|
300
317
|
result.report = report;
|
|
301
318
|
result.presentation = presentation;
|
|
319
|
+
if (navigation) result.navigation = navigation;
|
|
302
320
|
timing.total_ms = round(milliseconds(started, now()));
|
|
303
321
|
return bindHostAction(result);
|
|
304
322
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UR_AUTHORING_FORMAT } from '../control-evaluation/ur-readiness.js';
|
|
2
2
|
import { PRD_DECISION_FORMAT } from '../control-evaluation/prd-readiness.js';
|
|
3
3
|
import { SD_DECISION_FORMAT } from '../control-evaluation/sd-readiness.js';
|
|
4
|
-
import { DIGEST_PATTERN, canonicalJson, digest } from './approval-command-contract.js';
|
|
4
|
+
import { DIGEST_PATTERN, COOPERATIVE_ASSURANCE, canonicalJson, digest } from './approval-command-contract.js';
|
|
5
5
|
import { RUN_ID_PATTERN, REVISION_ID_PATTERN, APPROVAL_GATES } from './run-identity.js';
|
|
6
6
|
import { TASK_TARGET_SOURCES } from '../task-target-resolution.js';
|
|
7
7
|
import { PRESENTATION_LANGUAGE_TAG_PATTERN_SOURCE } from '../interaction-presentation.js';
|
|
@@ -73,14 +73,128 @@ function payloadSchema(group, kind) {
|
|
|
73
73
|
original_kind: enumeration(Object.keys(recoveryGroups).filter(original => recoveryGroups[original] === group)) })
|
|
74
74
|
: RUN_COMMAND_DESCRIPTORS[kind].payload_schema;
|
|
75
75
|
}
|
|
76
|
+
// Canonical help is shared by expanded Core validation and operation help.
|
|
77
|
+
const fieldHelp = Object.freeze({
|
|
78
|
+
schema_version: 'Contract version 1.',
|
|
79
|
+
presentation_language: 'Response BCP 47 language, e.g. de.',
|
|
80
|
+
working_directory: 'Absolute task directory for target resolution.',
|
|
81
|
+
target_source: 'Human task basis for this target; cwd alone is not authority.',
|
|
82
|
+
primary_target: 'Confirmed absolute target from resolution.',
|
|
83
|
+
target_id: 'Exact hash from current MCP target inspection; never guess.',
|
|
84
|
+
run_id: 'Scope-matching Run from inspection; recency is not authority.',
|
|
85
|
+
expected_revision_id: 'Current inspected revision; recovery retains original binding.',
|
|
86
|
+
operation_id: 'New command UUID; after handoff reuse original UUID/exact payload.',
|
|
87
|
+
assurance: 'Cooperative intent, not human proof or approval.',
|
|
88
|
+
command: 'Kind/payload; nested help: operation-contract.',
|
|
89
|
+
path: 'Contained Run-local source path; read the actual file before recording.',
|
|
90
|
+
digest: 'Canonical SHA-256 of the exact source bytes; never guess or reseal unrelated edits.',
|
|
91
|
+
gate: 'Exact gate named by current control, not permission to advance it.',
|
|
92
|
+
presentation_id: 'Immutable current presentation id from MCP inspection or preparation.',
|
|
93
|
+
response: 'Forward the new deliberate human reply for this exact presentation.',
|
|
94
|
+
confirmation: 'Exact concrete confirmation required by this operation contract.',
|
|
95
|
+
source_gate: 'Earliest actually changed approved source, determined from the reviewed proposal.',
|
|
96
|
+
preview_digest: 'Exact preview digest returned for the same reviewed proposal and binding.',
|
|
97
|
+
recovery_revision_id: 'Revision allowed by the original pending operation recovery contract.',
|
|
98
|
+
pending_digest: 'Exact pending operation digest from authoritative operation-status.',
|
|
99
|
+
original_kind: 'Original operation kind in this fixed permission group; preserve its identity.',
|
|
100
|
+
});
|
|
101
|
+
function describeFields(schema) {
|
|
102
|
+
const value = { ...schema };
|
|
103
|
+
if (value.properties) value.properties = Object.fromEntries(Object.entries(value.properties).map(([key, child]) =>
|
|
104
|
+
[key, { ...describeFields(child), ...(fieldHelp[key] ? { description: fieldHelp[key] } : {}) }]));
|
|
105
|
+
for (const key of ['oneOf', 'anyOf', 'allOf']) if (value[key]) value[key] = value[key].map(describeFields);
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
function commandSchema(group) {
|
|
109
|
+
return { oneOf: RUN_COMMAND_GROUPS[group].map(kind => strictObject({ kind: { const: kind },
|
|
110
|
+
payload: { ...payloadSchema(group, kind), description: RUN_COMMAND_DESCRIPTORS[kind].effects.join('; ') } })) };
|
|
111
|
+
}
|
|
76
112
|
export function commandGroupSchema(group) {
|
|
77
|
-
|
|
78
|
-
return strictObject({ ...envelope, command:
|
|
113
|
+
if (!RUN_COMMAND_GROUPS[group]) throw Error('command_group_unsupported');
|
|
114
|
+
return describeFields(strictObject({ ...envelope, command: commandSchema(group) }));
|
|
115
|
+
}
|
|
116
|
+
// Repeated immutable schema nodes become deterministic local references. Only
|
|
117
|
+
// schema positions are traversed: a properties map is never itself a schema.
|
|
118
|
+
export function compactCommandSchema(expanded) {
|
|
119
|
+
const visit = (node, transform, depth = 0) => {
|
|
120
|
+
const copy = { ...node };
|
|
121
|
+
if (copy.properties) copy.properties = Object.fromEntries(Object.entries(copy.properties).map(([key, child]) =>
|
|
122
|
+
[key, visit(child, transform, depth + 1)]));
|
|
123
|
+
for (const key of ['oneOf', 'anyOf', 'allOf']) if (copy[key]) copy[key] = copy[key].map(child => visit(child, transform, depth + 1));
|
|
124
|
+
if (copy.items) copy.items = visit(copy.items, transform, depth + 1);
|
|
125
|
+
return transform(copy, depth);
|
|
126
|
+
};
|
|
127
|
+
// Discovery keeps direct envelope and per-operation payload meaning; nested
|
|
128
|
+
// field detail remains in the expanded operation-contract from this owner.
|
|
129
|
+
const discovery = visit(expanded, (node, depth) => {
|
|
130
|
+
if (depth > 1) delete node.description;
|
|
131
|
+
return node;
|
|
132
|
+
});
|
|
133
|
+
// Restore every payload's concise meaning explicitly, without phrase policy.
|
|
134
|
+
discovery.properties.command.oneOf.forEach((row, i) => { row.properties.payload.description = expanded.properties.command.oneOf[i].properties.payload.description; });
|
|
135
|
+
const counts = new Map();
|
|
136
|
+
visit(discovery, node => { const { description, ...base } = node; const key = JSON.stringify(base); counts.set(key, (counts.get(key) ?? 0) + 1); return node; });
|
|
137
|
+
const definitions = {}, names = new Map();
|
|
138
|
+
const factor = (node, depth = 0, defining = false) => {
|
|
139
|
+
const { description, ...base } = node;
|
|
140
|
+
const key = JSON.stringify(base);
|
|
141
|
+
if (!defining && depth > 0 && (counts.get(key) ?? 0) > 1 && key.length >= 48) {
|
|
142
|
+
if (!names.has(key)) {
|
|
143
|
+
const name = `s${names.size}`; names.set(key, name);
|
|
144
|
+
definitions[name] = factor(base, depth, true);
|
|
145
|
+
}
|
|
146
|
+
return { $ref: `#/$defs/${names.get(key)}`, ...(description ? { description } : {}) };
|
|
147
|
+
}
|
|
148
|
+
const copy = { ...node };
|
|
149
|
+
if (copy.properties) copy.properties = Object.fromEntries(Object.entries(copy.properties).map(([name, child]) => [name, factor(child, depth + 1)]));
|
|
150
|
+
for (const name of ['oneOf', 'anyOf', 'allOf']) if (copy[name]) copy[name] = copy[name].map(child => factor(child, depth + 1));
|
|
151
|
+
if (copy.items) copy.items = factor(copy.items, depth + 1);
|
|
152
|
+
return copy;
|
|
153
|
+
};
|
|
154
|
+
const projected = factor(discovery);
|
|
155
|
+
if (names.size) projected.$defs = definitions;
|
|
156
|
+
return projected;
|
|
79
157
|
}
|
|
158
|
+
const openObject = { type: 'object', additionalProperties: true };
|
|
159
|
+
const nullableObject = { ...openObject, type: ['object', 'null'] };
|
|
160
|
+
const nullableString = { type: ['string', 'null'] };
|
|
161
|
+
const resultProperties = {
|
|
162
|
+
schema_version: { const: '1' }, outcome: enumeration(['accepted', 'already_applied', 'rejected', 'recovery_required', 'effect_unknown']), authorizes: { const: false },
|
|
163
|
+
reason: { ...nullableString, minLength: 1 }, operation_id: { ...identity, type: ['string', 'null'] }, request_digest: { ...hash, type: ['string', 'null'] },
|
|
164
|
+
binding: { ...nullableObject, properties: { target_id: hash, run_id: envelope.run_id, expected_revision_id: identity, kind: string() }, required: ['target_id', 'run_id', 'expected_revision_id', 'kind'] },
|
|
165
|
+
receipt: nullableObject, effect: nullableObject,
|
|
166
|
+
current: { ...nullableObject, properties: { available: { type: 'boolean' }, revision: { type: 'integer' }, revision_id: identity }, required: ['available'] },
|
|
167
|
+
assurance: { ...openObject, properties: Object.fromEntries(Object.entries(COOPERATIVE_ASSURANCE).map(([key, value]) => [key, { const: value }])), required: Object.keys(COOPERATIVE_ASSURANCE) }, family_result: openObject,
|
|
168
|
+
historical_effect: { type: 'boolean' }, dispatch: enumeration(['not_dispatched', 'unknown']), recovery: nullableString,
|
|
169
|
+
diagnostics: { type: 'array', items: openObject },
|
|
170
|
+
presentation_delivery: { ...openObject, properties: { mode: enumeration(['native_plus_chat', 'full_chat']), display: enumeration(['confirmed', 'unconfirmed']),
|
|
171
|
+
text: { type: 'string' }, presentation_id: identity, revision_id: identity, document_digest: hash, document_language: nullableString, authorizes: { const: false } },
|
|
172
|
+
required: ['mode', 'display', 'text', 'presentation_id', 'revision_id', 'document_digest', 'document_language', 'authorizes'] },
|
|
173
|
+
app_capability: { type: 'string' }, app_reason: { type: 'string' }, app_display: { type: 'string' },
|
|
174
|
+
};
|
|
175
|
+
const resultVariants = [
|
|
176
|
+
{ properties: { outcome: enumeration(['accepted', 'already_applied']), operation_id: { type: 'string' }, request_digest: { type: 'string' }, binding: { type: 'object' }, current: { type: 'object' } }, required: ['operation_id', 'request_digest', 'binding', 'receipt', 'effect', 'current', 'assurance', 'historical_effect', 'recovery', 'reason', 'family_result'] },
|
|
177
|
+
{ properties: { outcome: enumeration(['rejected', 'recovery_required', 'effect_unknown']), reason: { type: 'string', minLength: 1 } }, required: ['reason'] },
|
|
178
|
+
];
|
|
179
|
+
export const RUN_COMMAND_RESULT_SCHEMA = freeze({ type: 'object', additionalProperties: true, properties: resultProperties,
|
|
180
|
+
required: ['schema_version', 'outcome', 'authorizes'], oneOf: resultVariants,
|
|
181
|
+
allOf: [{ oneOf: [
|
|
182
|
+
{ properties: { outcome: enumeration(['accepted', 'already_applied', 'rejected']) } },
|
|
183
|
+
{ properties: { outcome: enumeration(['recovery_required', 'effect_unknown']), recovery: { type: 'string', minLength: 1 } }, required: ['operation_id', 'recovery'] },
|
|
184
|
+
] }] });
|
|
185
|
+
// Essential envelope projection from the full model, not a second result owner.
|
|
186
|
+
export function commandResultSchema() {
|
|
187
|
+
const keys = ['schema_version', 'outcome', 'authorizes', 'reason', 'operation_id', 'binding', 'receipt', 'recovery'];
|
|
188
|
+
const properties = Object.fromEntries(keys.map(key => [key, key === 'binding' ? { type: resultProperties.binding.type } : key === 'outcome' ? { type: 'string' } : resultProperties[key]]));
|
|
189
|
+
return { type: 'object', properties, required: RUN_COMMAND_RESULT_SCHEMA.required,
|
|
190
|
+
oneOf: resultVariants.map(variant => ({ ...variant, properties: Object.fromEntries(Object.entries(variant.properties).filter(([key]) => keys.includes(key))), required: variant.required.filter(key => keys.includes(key)) })) };
|
|
191
|
+
}
|
|
192
|
+
export function validateRunCommandResult(value) { return schemaDiagnostics(value, RUN_COMMAND_RESULT_SCHEMA); }
|
|
80
193
|
export function commandGroupDefinition(group) {
|
|
81
194
|
const definition = { name: group,
|
|
82
|
-
description: `${group}: ${RUN_COMMAND_GROUPS[group]?.join(', ')}.
|
|
83
|
-
annotations: { readOnlyHint: false, destructiveHint: group !== 'agdf_prepare', idempotentHint: false, openWorldHint: false },
|
|
195
|
+
description: `${group}: ${RUN_COMMAND_GROUPS[group]?.join(', ')}. Inspect operation-contract for payloads/bindings. Cooperative intent never approves a gate. After uncertainty inspect operation-status with original UUID/payload. No VCS.`,
|
|
196
|
+
annotations: { readOnlyHint: false, destructiveHint: group !== 'agdf_prepare', idempotentHint: false, openWorldHint: false },
|
|
197
|
+
inputSchema: compactCommandSchema(commandGroupSchema(group)), outputSchema: commandResultSchema() };
|
|
84
198
|
if (Buffer.byteLength(JSON.stringify(definition)) > RUN_COMMAND_LIMITS.definition_bytes) throw Error('command_definition_budget_exceeded');
|
|
85
199
|
return freeze(definition);
|
|
86
200
|
}
|
|
@@ -88,14 +202,31 @@ export function commandGroupDefinition(group) {
|
|
|
88
202
|
// No caller validator, accessor execution, coercion or default-expanded effect is allowed.
|
|
89
203
|
export function schemaDiagnostics(value, schema, path = '$') {
|
|
90
204
|
const errors = [];
|
|
91
|
-
if (schema.oneOf
|
|
205
|
+
if (schema.oneOf && schema.oneOf.filter(choice => schemaDiagnostics(value, choice, path).length === 0).length !== 1) errors.push({ path, code: 'one_of_invalid' });
|
|
206
|
+
if (schema.allOf) for (const choice of schema.allOf) errors.push(...schemaDiagnostics(value, choice, path));
|
|
207
|
+
if (Array.isArray(schema.type)) {
|
|
208
|
+
const matches = schema.type.map(type => schemaDiagnostics(value, { ...schema, type }, path));
|
|
209
|
+
return matches.some(result => result.length === 0) ? errors : [...errors, { path, code: 'type_invalid' }];
|
|
210
|
+
}
|
|
211
|
+
if (schema.type === 'null' && value !== null) errors.push({ path, code: 'null_required' });
|
|
212
|
+
if (schema.type === 'integer' && !Number.isInteger(value)) errors.push({ path, code: 'integer_required' });
|
|
213
|
+
if (schema.type === 'boolean' && typeof value !== 'boolean') errors.push({ path, code: 'boolean_required' });
|
|
214
|
+
if (schema.type === 'array') {
|
|
215
|
+
if (!Array.isArray(value)) return [...errors, { path, code: 'array_required' }];
|
|
216
|
+
for (let i = 0; i < value.length; i++) {
|
|
217
|
+
const field = Object.getOwnPropertyDescriptor(value, String(i));
|
|
218
|
+
if (!field || !Object.hasOwn(field, 'value')) errors.push({ path, code: 'accessor_forbidden' });
|
|
219
|
+
else if (schema.items) errors.push(...schemaDiagnostics(field.value, schema.items, `${path}[${i}]`));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
92
222
|
if (Object.hasOwn(schema, 'const') && value !== schema.const) errors.push({ path, code: 'constant_invalid' });
|
|
93
223
|
if (schema.enum && !schema.enum.includes(value)) errors.push({ path, code: 'enum_invalid' });
|
|
94
|
-
if (schema.type === 'object') {
|
|
224
|
+
if (schema.type === 'object' || !schema.type && (schema.properties || schema.required) && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
95
225
|
if (value === null || typeof value !== 'object' || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return [{ path, code: 'object_required' }];
|
|
96
226
|
const keys = Reflect.ownKeys(value);
|
|
97
227
|
for (const key of keys) {
|
|
98
|
-
if (typeof key !== 'string'
|
|
228
|
+
if (typeof key !== 'string') { errors.push({ path, code: 'unknown_field' }); continue; }
|
|
229
|
+
if (!Object.hasOwn(schema.properties ?? {}, key)) { if (schema.additionalProperties === false) errors.push({ path, code: 'unknown_field' }); continue; }
|
|
99
230
|
const property = Object.getOwnPropertyDescriptor(value, key);
|
|
100
231
|
if (!Object.hasOwn(property, 'value')) { errors.push({ path: `${path}.${key}`, code: 'accessor_forbidden' }); continue; }
|
|
101
232
|
errors.push(...schemaDiagnostics(property.value, schema.properties[key], `${path}.${key}`));
|
|
@@ -122,7 +253,7 @@ export const runCommandRequestDigest = input => digest(`agdf-run-command/1\0${ca
|
|
|
122
253
|
export function operationContract(group, kind, { gate } = {}) {
|
|
123
254
|
if (!RUN_COMMAND_GROUPS[group]?.includes(kind)) return { available: false, reason: 'command_group_unsupported' };
|
|
124
255
|
return { schema_version: RUN_COMMAND_VERSION, available: true, group, kind, ...RUN_COMMAND_DESCRIPTORS[kind], payload_schema: payloadSchema(group, kind),
|
|
125
|
-
schema: strictObject({ ...envelope, command: strictObject({ kind: { const: kind }, payload: payloadSchema(group, kind) }) }), retry_class: 'inspect_original_operation_after_dispatch', assurance: 'cooperative_local',
|
|
256
|
+
schema: describeFields(strictObject({ ...envelope, command: strictObject({ kind: { const: kind }, payload: { ...payloadSchema(group, kind), description: RUN_COMMAND_DESCRIPTORS[kind].effects.join('; ') } }) })), result_schema: RUN_COMMAND_RESULT_SCHEMA, retry_class: 'inspect_original_operation_after_dispatch', assurance: 'cooperative_local',
|
|
126
257
|
reference_limits: { input_bytes: RUN_COMMAND_LIMITS.input_bytes, source_file_bytes: 131072, result_bytes: RUN_COMMAND_LIMITS.result_bytes },
|
|
127
258
|
internal_result_formats: kind === 'step.record' ? { 'UX Intent Definition': UX_DECISION_FORMAT,
|
|
128
259
|
reviews: { declaration: '- decision: pass | revise | block | not_applicable', result_scope: 'actual current owner report; no generated judgement or approval' } } : undefined,
|
|
@@ -431,8 +431,8 @@ export function renderReviewableApproval(root, report, { runId, gate, revisionId
|
|
|
431
431
|
german ? 'Antworte „Dokument lesen“, um diese Fassung vollständig im Chat zu lesen. Fragen und Änderungswünsche kannst du hier besprechen. Ohne deine Freigabe bleibt dieser Schritt offen.'
|
|
432
432
|
: 'Reply “Read document” to read this complete version in Chat. Discuss questions and requested changes here. This step remains open until you approve.',
|
|
433
433
|
pack.decisionCard.chatApprovalHint.replace('{gate}', gate),
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
`### ${german ? 'Details und Nachweise' : 'Details and evidence'}\n\n${summaryMarkdown}`,
|
|
435
|
+
`### ${german ? 'Fassung und Quellen' : 'Version and sources'}\n\nRun: \`${runId}\` · ${pack.gateTitles[gate]} · Revision: \`${revisionId}\`\n\nSHA-256: \`${artefactDigest}\``];
|
|
436
436
|
return parts.every((part) => typeof part === "string" && part.trim())
|
|
437
437
|
? { markdown: parts.join("\n\n"), companion_markdown: [german
|
|
438
438
|
? `Die AGDF-Karte ist geöffnet. Du kannst dort „${qaAcceptance?.approve_label || 'Freigeben'}“ klicken oder hier \`Approval: ${gate}\` eingeben. Beide Wege entscheiden dieselbe vorgelegte Fassung einmal.`
|
|
@@ -409,7 +409,7 @@ function markdownCell(value) {
|
|
|
409
409
|
.replaceAll("&", "&")
|
|
410
410
|
.replaceAll("<", "<")
|
|
411
411
|
.replaceAll(">", ">")
|
|
412
|
-
.replace(/\r
|
|
412
|
+
.replace(/\r\n?|\n/g, " · ");
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
function localizedOperationalValue(value, pack, fallbackPack, fallbackLocale) {
|