@cairnkeep/cli 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +221 -6
- package/bin/cairn +43 -2
- package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
- package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
- package/claude/hooks/compaction-capture.sh +34 -0
- package/claude/hooks/memory-capture.sh +47 -5
- package/claude/hooks/memory-wakeup.sh +55 -2
- package/docs/building-an-overlay.md +1 -1
- package/docs/design/meta-agent.md +439 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/operating.md +531 -4
- package/docs/privacy-and-data-flow.md +417 -0
- package/docs/storage.md +252 -0
- package/examples/eval/README.md +42 -0
- package/examples/eval/adapter.json +12 -0
- package/examples/eval/bundled-fake.json +1 -0
- package/examples/eval/task-set.json +1 -0
- package/mcp-memory-server/dist/artifact-cli.js +184 -0
- package/mcp-memory-server/dist/artifact-schema.js +218 -0
- package/mcp-memory-server/dist/artifact-store.js +812 -0
- package/mcp-memory-server/dist/capability-adapter.js +286 -0
- package/mcp-memory-server/dist/capability-cli.js +361 -0
- package/mcp-memory-server/dist/capability-config.js +310 -0
- package/mcp-memory-server/dist/capability-harness.js +496 -0
- package/mcp-memory-server/dist/capability-registry.js +123 -0
- package/mcp-memory-server/dist/capability-schema.js +65 -0
- package/mcp-memory-server/dist/capability-store.js +431 -0
- package/mcp-memory-server/dist/compaction-normalize.js +267 -0
- package/mcp-memory-server/dist/eval-cli.js +523 -0
- package/mcp-memory-server/dist/eval-plan.js +346 -0
- package/mcp-memory-server/dist/eval-process.js +273 -0
- package/mcp-memory-server/dist/eval-report.js +609 -0
- package/mcp-memory-server/dist/eval-runner.js +827 -0
- package/mcp-memory-server/dist/eval-schema.js +368 -0
- package/mcp-memory-server/dist/eval-statistics.js +161 -0
- package/mcp-memory-server/dist/eval-workspace.js +313 -0
- package/mcp-memory-server/dist/failure-signature.js +141 -0
- package/mcp-memory-server/dist/index.js +600 -55
- package/mcp-memory-server/dist/node-cli.js +84 -0
- package/mcp-memory-server/dist/node-schema.js +170 -0
- package/mcp-memory-server/dist/node-store.js +455 -0
- package/mcp-memory-server/dist/note-cli.js +235 -0
- package/mcp-memory-server/dist/note-distiller.js +177 -0
- package/mcp-memory-server/dist/note-enrichment.js +93 -0
- package/mcp-memory-server/dist/note-schema.js +4 -0
- package/mcp-memory-server/dist/note-store.js +985 -0
- package/mcp-memory-server/dist/trajectory-cli.js +141 -0
- package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
- package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
- package/mcp-memory-server/dist/trajectory-schema.js +73 -0
- package/mcp-memory-server/dist/trajectory-store.js +339 -0
- package/mcp-memory-server/package.json +24 -2
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
- package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
- package/opencode/capability-contract/plugins/capability-command.ts +319 -0
- package/opencode/command/graphify.md +25 -14
- package/opencode/plugins/memory-capture.ts +76 -10
- package/opencode/plugins/memory-wakeup.ts +58 -6
- package/package.json +9 -4
- package/pi/extensions/cairnkeep-trajectory.ts +55 -0
- package/schemas/artifact.schema.json +133 -0
- package/schemas/capability-callback.schema.json +68 -0
- package/schemas/capability-contract.schema.json +126 -0
- package/schemas/eval-adapter.schema.json +43 -0
- package/schemas/eval-protocol.schema.json +114 -0
- package/schemas/eval-report.schema.json +423 -0
- package/schemas/eval-task-set.schema.json +114 -0
- package/schemas/memory-node.schema.json +90 -0
- package/schemas/note.schema.json +112 -0
- package/schemas/trajectory-redaction.schema.json +25 -0
- package/schemas/trajectory.schema.json +82 -0
- package/scripts/bootstrap.sh +4 -0
- package/scripts/completion.sh +119 -5
- package/scripts/doctor.sh +284 -0
- package/scripts/fake-eval-adapter.mjs +120 -0
- package/scripts/fixtures/capability-harness-contracts.json +408 -0
- package/scripts/install-audit-timer.sh +3 -1
- package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
- package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
- package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
- package/scripts/memory-wiki-audit.sh +49 -0
- package/scripts/smoke-bash32.sh +9 -0
- package/scripts/sync-claude-assets.sh +99 -6
- package/scripts/sync-opencode-graphify-assets.sh +24 -3
- package/scripts/sync-opencode-plugin-assets.sh +31 -5
- package/scripts/sync-opencode-security-assets.sh +24 -3
- package/scripts/sync-opencode-wiki-assets.sh +24 -3
- package/scripts/sync-pi-assets.sh +72 -0
- package/scripts/test-audit-timer.sh +98 -0
- package/scripts/test-bootstrap-untracked.sh +48 -0
- package/scripts/test-cli-dispatch.sh +41 -2
- package/scripts/test-compaction-hooks.sh +495 -0
- package/scripts/test-completion.sh +77 -0
- package/scripts/test-doctor.sh +348 -1
- package/scripts/test-eval-cli.sh +175 -0
- package/scripts/test-launcher-hooks.sh +602 -3
- package/scripts/test-notes-cli.sh +114 -0
- package/scripts/test-package-install.sh +251 -0
- package/scripts/test-phase16-node-lifecycle.sh +50 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
- package/scripts/test-phase18-capability-lifecycle.sh +504 -0
- package/scripts/test-phase18-harness-boundary.sh +1060 -0
- package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
- package/scripts/test-trajectory-cli.sh +87 -0
- package/scripts/test-trajectory-hooks.sh +142 -0
- package/scripts/test-uninstall.sh +231 -5
- package/scripts/uninstall.sh +37 -5
- package/scripts/verify-dependency-security.mjs +121 -0
- package/scripts/verify-docs-parity.sh +527 -1
- package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
- package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
- package/templates/capabilities.json.template +7 -0
- package/templates/env.example.template +51 -0
- package/templates/memory-wiki-audit.service.template +1 -1
- package/templates/start-claude.sh.template +36 -0
- package/templates/start-opencode.sh.template +47 -5
- package/templates/start-pi.sh.template +30 -0
- package/templates/trajectory-redaction.json.template +4 -0
package/scripts/test-doctor.sh
CHANGED
|
@@ -14,6 +14,8 @@ trap cleanup EXIT
|
|
|
14
14
|
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
15
15
|
|
|
16
16
|
doctor="$ROOT/scripts/doctor.sh"
|
|
17
|
+
export HOME="$tmp/home"
|
|
18
|
+
mkdir -p "$HOME"
|
|
17
19
|
# Clean the inherited env so the test controls what is "configured".
|
|
18
20
|
unset CAIRN_LLM_API_URL CAIRN_MEMORY_EMBEDDING_URL CAIRN_GIT_PROVIDER CAIRN_AGENTFS_BASE_DIR
|
|
19
21
|
|
|
@@ -21,6 +23,12 @@ unset CAIRN_LLM_API_URL CAIRN_MEMORY_EMBEDDING_URL CAIRN_GIT_PROVIDER CAIRN_AGEN
|
|
|
21
23
|
proj="$tmp/clean"; mkdir -p "$proj"
|
|
22
24
|
( cd "$proj" && "$doctor" ) >"$tmp/out1" 2>&1 || fail "doctor exited non-zero with nothing configured:\n$(cat "$tmp/out1")"
|
|
23
25
|
grep -q "\[SKIP\]" "$tmp/out1" || fail "expected SKIP lines when nothing is configured"
|
|
26
|
+
grep -q "typed metadata and note transaction state" "$tmp/out1" || fail "expected typed/note diagnostics in the public doctor output"
|
|
27
|
+
grep -q "artifact store (not present" "$tmp/out1" || fail "expected absent opt-in artifact diagnostics"
|
|
28
|
+
grep -q "capability configuration (not present — managed overrides are unused)" "$tmp/out1" ||
|
|
29
|
+
fail "expected absent capability configuration diagnostics"
|
|
30
|
+
grep -q "capability callback namespace (not present — callback logging is unused)" "$tmp/out1" ||
|
|
31
|
+
fail "expected absent capability callback diagnostics"
|
|
24
32
|
|
|
25
33
|
# 2. An unsupported runtime is diagnosed before the server probe.
|
|
26
34
|
mkdir -p "$tmp/old-node"
|
|
@@ -106,4 +114,343 @@ EOF
|
|
|
106
114
|
fail "expected a functional embedding FAIL line"
|
|
107
115
|
fi
|
|
108
116
|
|
|
109
|
-
|
|
117
|
+
# 5. Artifact doctor passes a valid store, repairs only derived state, and
|
|
118
|
+
# refuses authoritative full-record corruption without leaking stored values.
|
|
119
|
+
artifact_cli="$ROOT/mcp-memory-server/dist/artifact-cli.js"
|
|
120
|
+
artifact_fixture="$ROOT/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json"
|
|
121
|
+
artifact_project="$tmp/artifact-project"
|
|
122
|
+
mkdir -p "$artifact_project"
|
|
123
|
+
node "$artifact_cli" capture-claude "$artifact_project" --harness-version 2.1.219 <"$artifact_fixture" >/dev/null 2>&1 || \
|
|
124
|
+
fail "could not create the artifact doctor fixture"
|
|
125
|
+
( cd "$artifact_project" && "$doctor" ) >"$tmp/artifact-valid.out" 2>&1 || \
|
|
126
|
+
fail "doctor rejected a valid artifacts.db:\n$(cat "$tmp/artifact-valid.out")"
|
|
127
|
+
grep -q "\[PASS\] artifact store integrity" "$tmp/artifact-valid.out" || \
|
|
128
|
+
fail "expected the valid artifact PASS line"
|
|
129
|
+
|
|
130
|
+
node --input-type=module - "$ROOT" "$artifact_project" <<'NODE'
|
|
131
|
+
import { join } from "node:path";
|
|
132
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
133
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
134
|
+
const fullRows = await agent.kv.list("artifact/full/");
|
|
135
|
+
const dedupeRows = await agent.kv.list("artifact/index/dedupe/");
|
|
136
|
+
if (fullRows.length !== 1 || dedupeRows.length !== 1) throw new Error("missing derived doctor fixture");
|
|
137
|
+
const artifact = fullRows[0].value;
|
|
138
|
+
await agent.kv.delete(dedupeRows[0].key);
|
|
139
|
+
await agent.kv.set("artifact/index/dedupe/orphan-public-doctor", artifact.artifact_id);
|
|
140
|
+
await agent.kv.set(`compaction/sequence/${artifact.session_ref}`, 0);
|
|
141
|
+
await agent.close();
|
|
142
|
+
NODE
|
|
143
|
+
if ( cd "$artifact_project" && "$doctor" ) >"$tmp/artifact-derived.out" 2>&1; then
|
|
144
|
+
fail "doctor should fail before derived artifact repair:\n$(cat "$tmp/artifact-derived.out")"
|
|
145
|
+
fi
|
|
146
|
+
( cd "$artifact_project" && "$doctor" --repair ) >"$tmp/artifact-repaired.out" 2>&1 || \
|
|
147
|
+
fail "doctor did not repair derived artifact state:\n$(cat "$tmp/artifact-repaired.out")"
|
|
148
|
+
grep -q "\[PASS\] artifact store repaired" "$tmp/artifact-repaired.out" || \
|
|
149
|
+
fail "expected the derived artifact repair PASS line"
|
|
150
|
+
|
|
151
|
+
node --input-type=module - "$ROOT" "$artifact_project" <<'NODE'
|
|
152
|
+
import { join } from "node:path";
|
|
153
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
154
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
155
|
+
const fullRows = await agent.kv.list("artifact/full/");
|
|
156
|
+
const dedupeRows = await agent.kv.list("artifact/index/dedupe/");
|
|
157
|
+
if (fullRows.length !== 1 || dedupeRows.length !== 1) throw new Error("derived repair did not restore the exact maps");
|
|
158
|
+
const artifact = fullRows[0].value;
|
|
159
|
+
const sequenceKey = `compaction/sequence/${artifact.session_ref}`;
|
|
160
|
+
if (await agent.kv.get(sequenceKey) !== artifact.content.revision) throw new Error("derived repair did not restore the retained sequence");
|
|
161
|
+
await agent.kv.set(sequenceKey, artifact.content.revision + 8);
|
|
162
|
+
await agent.close();
|
|
163
|
+
NODE
|
|
164
|
+
( cd "$artifact_project" && "$doctor" --repair ) >"$tmp/artifact-higher-sequence.out" 2>&1 || \
|
|
165
|
+
fail "doctor rejected a valid higher historical sequence:\n$(cat "$tmp/artifact-higher-sequence.out")"
|
|
166
|
+
node --input-type=module - "$ROOT" "$artifact_project" <<'NODE'
|
|
167
|
+
import { join } from "node:path";
|
|
168
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
169
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
170
|
+
const [artifact] = (await agent.kv.list("artifact/full/")).map((row) => row.value);
|
|
171
|
+
if (await agent.kv.get(`compaction/sequence/${artifact.session_ref}`) !== artifact.content.revision + 8) {
|
|
172
|
+
throw new Error("doctor lowered a valid higher historical sequence");
|
|
173
|
+
}
|
|
174
|
+
await agent.close();
|
|
175
|
+
NODE
|
|
176
|
+
|
|
177
|
+
node --input-type=module - "$ROOT" "$artifact_project" <<'NODE'
|
|
178
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
179
|
+
const { putArtifact } = await import(process.argv[2] + "/mcp-memory-server/dist/artifact-store.js");
|
|
180
|
+
const { join } = await import("node:path");
|
|
181
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
182
|
+
const [stored] = (await agent.kv.list("artifact/full/")).map((row) => row.value);
|
|
183
|
+
await agent.close();
|
|
184
|
+
const { revision: _revision, ...content } = stored.content;
|
|
185
|
+
await putArtifact(process.argv[3], {
|
|
186
|
+
kind: stored.kind,
|
|
187
|
+
session_ref: stored.session_ref,
|
|
188
|
+
media_type: stored.media_type,
|
|
189
|
+
provenance: stored.provenance,
|
|
190
|
+
content: {
|
|
191
|
+
...content,
|
|
192
|
+
raw_summary: `${content.raw_summary}\nretention-fixture`,
|
|
193
|
+
task_goals: [...content.task_goals, "retention-fixture"],
|
|
194
|
+
},
|
|
195
|
+
}, {
|
|
196
|
+
artifactMaxBytes: 4096,
|
|
197
|
+
sessionMaxBytes: 65536,
|
|
198
|
+
storeMaxBytes: 262144,
|
|
199
|
+
retentionDays: 3650,
|
|
200
|
+
compactionMaxRevisions: 8,
|
|
201
|
+
generatedFileSnapshotMaxBytes: 4096,
|
|
202
|
+
});
|
|
203
|
+
NODE
|
|
204
|
+
node --input-type=module - "$ROOT" "$artifact_project" "$tmp/artifact-full-before.json" <<'NODE'
|
|
205
|
+
import { writeFileSync } from "node:fs";
|
|
206
|
+
import { join } from "node:path";
|
|
207
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
208
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
209
|
+
writeFileSync(process.argv[4], JSON.stringify(await agent.kv.list("artifact/full/")));
|
|
210
|
+
await agent.close();
|
|
211
|
+
NODE
|
|
212
|
+
if ( cd "$artifact_project" && CAIRN_COMPACTION_MAX_REVISIONS=1 "$doctor" ) >"$tmp/artifact-retention.out" 2>&1; then
|
|
213
|
+
fail "doctor should fail for retained artifacts over the configured revision limit:\n$(cat "$tmp/artifact-retention.out")"
|
|
214
|
+
fi
|
|
215
|
+
if ( cd "$artifact_project" && CAIRN_COMPACTION_MAX_REVISIONS=1 "$doctor" --repair ) >"$tmp/artifact-retention-repair.out" 2>&1; then
|
|
216
|
+
fail "doctor repair should leave retention violations unresolved:\n$(cat "$tmp/artifact-retention-repair.out")"
|
|
217
|
+
fi
|
|
218
|
+
grep -q "\[FAIL\] artifact store could not be repaired safely" "$tmp/artifact-retention-repair.out" || \
|
|
219
|
+
fail "expected a failed non-destructive retention repair result"
|
|
220
|
+
node --input-type=module - "$ROOT" "$artifact_project" "$tmp/artifact-full-before.json" <<'NODE'
|
|
221
|
+
import { readFileSync } from "node:fs";
|
|
222
|
+
import { join } from "node:path";
|
|
223
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
224
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
225
|
+
const after = JSON.stringify(await agent.kv.list("artifact/full/"));
|
|
226
|
+
await agent.close();
|
|
227
|
+
if (after !== readFileSync(process.argv[4], "utf8")) throw new Error("doctor repair changed authoritative rows for a retention violation");
|
|
228
|
+
NODE
|
|
229
|
+
|
|
230
|
+
node --input-type=module - "$ROOT" "$artifact_project" <<'NODE'
|
|
231
|
+
import { join } from "node:path";
|
|
232
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
233
|
+
const agent = await AgentFS.open({ id: "artifacts", path: join(process.argv[3], ".agentfs", "artifacts.db") });
|
|
234
|
+
const rows = await agent.kv.list("artifact/full/");
|
|
235
|
+
if (rows.length === 0) throw new Error("missing authoritative record fixture");
|
|
236
|
+
await agent.kv.set(rows[0].key, { ...rows[0].value, content_digest: "0".repeat(64) });
|
|
237
|
+
await agent.close();
|
|
238
|
+
NODE
|
|
239
|
+
if ( cd "$artifact_project" && "$doctor" --repair ) >"$tmp/artifact-authoritative.out" 2>&1; then
|
|
240
|
+
fail "doctor should reject authoritative artifact corruption:\n$(cat "$tmp/artifact-authoritative.out")"
|
|
241
|
+
fi
|
|
242
|
+
grep -q "\[FAIL\] artifact store could not be repaired safely" "$tmp/artifact-authoritative.out" || \
|
|
243
|
+
fail "expected the authoritative artifact corruption FAIL line"
|
|
244
|
+
if grep -qF 'compact_summary' "$tmp/artifact-authoritative.out"; then
|
|
245
|
+
fail "artifact doctor leaked a stored value"
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
# 6. Capability diagnostics consume only the private fixed doctor envelope and
|
|
249
|
+
# map config/store states without exposing malformed values or callback rows.
|
|
250
|
+
capability_valid="$tmp/capability-valid"
|
|
251
|
+
mkdir -p "$capability_valid/.ai"
|
|
252
|
+
printf '%s\n' '{"schema_version":1,"capabilities":{},"logging":{}}' >"$capability_valid/.ai/capabilities.json"
|
|
253
|
+
chmod 600 "$capability_valid/.ai/capabilities.json"
|
|
254
|
+
( cd "$capability_valid" && "$doctor" ) >"$tmp/capability-valid.out" 2>&1 ||
|
|
255
|
+
fail "doctor rejected valid capability state:\n$(cat "$tmp/capability-valid.out")"
|
|
256
|
+
grep -q "\[PASS\] capability configuration schema and permissions are valid" "$tmp/capability-valid.out" ||
|
|
257
|
+
fail "expected valid capability configuration PASS"
|
|
258
|
+
|
|
259
|
+
capability_warn="$tmp/capability-warn"
|
|
260
|
+
mkdir -p "$capability_warn/.ai"
|
|
261
|
+
printf '%s\n' '{"schema_version":1,"capabilities":{"memory.write":"PHASE18_CONFIG_VALUE_SENTINEL","PHASE18_UNKNOWN_ID_SENTINEL":true},"logging":{"callbacks":"PHASE18_LOGGING_VALUE_SENTINEL"}}' >"$capability_warn/.ai/capabilities.json"
|
|
262
|
+
chmod 600 "$capability_warn/.ai/capabilities.json"
|
|
263
|
+
( cd "$capability_warn" && "$doctor" ) >"$tmp/capability-warn.out" 2>&1 ||
|
|
264
|
+
fail "doctor treated row-local capability warnings as fatal:\n$(cat "$tmp/capability-warn.out")"
|
|
265
|
+
grep -q "\[WARN\] capability configuration has an invalid value for memory.write" "$tmp/capability-warn.out" ||
|
|
266
|
+
fail "expected canonical capability warning"
|
|
267
|
+
grep -q "\[WARN\] capability configuration has an invalid value for logging.callbacks" "$tmp/capability-warn.out" ||
|
|
268
|
+
fail "expected fixed callback setting warning"
|
|
269
|
+
grep -q "\[WARN\] capability configuration contains an unknown override ID" "$tmp/capability-warn.out" ||
|
|
270
|
+
fail "expected value-free unknown-ID warning"
|
|
271
|
+
if grep -Eq 'PHASE18_(CONFIG_VALUE|UNKNOWN_ID|LOGGING_VALUE)_SENTINEL' "$tmp/capability-warn.out"; then
|
|
272
|
+
fail "capability doctor exposed malformed configuration contents"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
capability_unsafe="$tmp/capability-unsafe"
|
|
276
|
+
mkdir -p "$capability_unsafe/.ai"
|
|
277
|
+
printf '%s\n' '{"schema_version":2,"capabilities":{"wiki":false},"logging":{}}' >"$capability_unsafe/.ai/capabilities.json"
|
|
278
|
+
chmod 644 "$capability_unsafe/.ai/capabilities.json"
|
|
279
|
+
if ( cd "$capability_unsafe" && "$doctor" ) >"$tmp/capability-unsafe.out" 2>&1; then
|
|
280
|
+
fail "doctor accepted unsafe or unsupported capability configuration"
|
|
281
|
+
fi
|
|
282
|
+
grep -q "\[FAIL\] capability configuration is invalid or uses an unsupported schema; preserve .ai/capabilities.json before reset" "$tmp/capability-unsafe.out" ||
|
|
283
|
+
fail "expected unsupported-schema backup guidance"
|
|
284
|
+
grep -q "\[FAIL\] capability configuration type or permissions are unsafe; preserve .ai/capabilities.json before recovery" "$tmp/capability-unsafe.out" ||
|
|
285
|
+
fail "expected unsafe-permissions backup guidance"
|
|
286
|
+
|
|
287
|
+
callback_project="$tmp/capability-callbacks"
|
|
288
|
+
mkdir -p "$callback_project"
|
|
289
|
+
node --input-type=module - "$ROOT" "$callback_project" <<'NODE'
|
|
290
|
+
const { appendCapabilityRecord } = await import(process.argv[2] + "/mcp-memory-server/dist/capability-store.js");
|
|
291
|
+
const now = new Date().toISOString();
|
|
292
|
+
await appendCapabilityRecord(process.argv[3], {
|
|
293
|
+
schema_version: 1,
|
|
294
|
+
capability_id: "wiki",
|
|
295
|
+
invocation_id: "cap:11111111-1111-4111-8111-111111111111",
|
|
296
|
+
correlation_id: "cairn:doctor-fixture",
|
|
297
|
+
harness: "other",
|
|
298
|
+
source: "operating-command",
|
|
299
|
+
transport: "local-process",
|
|
300
|
+
started_at: now,
|
|
301
|
+
finished_at: now,
|
|
302
|
+
duration_ms: 0,
|
|
303
|
+
outcome: "success",
|
|
304
|
+
state_source: "project",
|
|
305
|
+
configuration_digest: "1".repeat(64),
|
|
306
|
+
});
|
|
307
|
+
NODE
|
|
308
|
+
( cd "$callback_project" && "$doctor" ) >"$tmp/callback-valid.out" 2>&1 ||
|
|
309
|
+
fail "doctor rejected a valid capability callback namespace:\n$(cat "$tmp/callback-valid.out")"
|
|
310
|
+
grep -q "\[PASS\] capability callback namespace schema and SQLite integrity are valid" "$tmp/callback-valid.out" ||
|
|
311
|
+
fail "expected valid capability callback PASS"
|
|
312
|
+
if grep -qF 'doctor-fixture' "$tmp/callback-valid.out"; then
|
|
313
|
+
fail "capability doctor exposed a callback record"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
node --input-type=module - "$ROOT" "$callback_project" <<'NODE'
|
|
317
|
+
import { join } from "node:path";
|
|
318
|
+
const { AgentFS } = await import(process.argv[2] + "/mcp-memory-server/node_modules/agentfs-sdk/dist/index_node.js");
|
|
319
|
+
const agent = await AgentFS.open({ id: "trajectory", path: join(process.argv[3], ".agentfs", "trajectory.db") });
|
|
320
|
+
await agent.kv.set("capability-callback/meta/schema-version", 2);
|
|
321
|
+
await agent.close();
|
|
322
|
+
NODE
|
|
323
|
+
cp "$callback_project/.agentfs/trajectory.db" "$tmp/callback-unsupported.before"
|
|
324
|
+
if ( cd "$callback_project" && "$doctor" ) >"$tmp/callback-unsupported.out" 2>&1; then
|
|
325
|
+
fail "doctor accepted an unsupported callback namespace"
|
|
326
|
+
fi
|
|
327
|
+
grep -q "\[FAIL\] capability callback namespace schema is unsupported; preserve .agentfs/trajectory.db before recovery" "$tmp/callback-unsupported.out" ||
|
|
328
|
+
fail "expected unsupported callback schema guidance"
|
|
329
|
+
if grep -qF 'doctor-fixture' "$tmp/callback-unsupported.out"; then
|
|
330
|
+
fail "unsupported callback diagnostics exposed a stored record"
|
|
331
|
+
fi
|
|
332
|
+
cmp -s "$tmp/callback-unsupported.before" "$callback_project/.agentfs/trajectory.db" ||
|
|
333
|
+
fail "callback diagnostics mutated an unsupported namespace"
|
|
334
|
+
|
|
335
|
+
callback_corrupt="$tmp/capability-callback-corrupt"
|
|
336
|
+
mkdir -p "$callback_corrupt/.agentfs"
|
|
337
|
+
printf '%s\n' 'PHASE18_CALLBACK_ROW_SENTINEL' >"$callback_corrupt/.agentfs/trajectory.db"
|
|
338
|
+
cp "$callback_corrupt/.agentfs/trajectory.db" "$tmp/callback-corrupt.before"
|
|
339
|
+
if ( cd "$callback_corrupt" && "$doctor" ) >"$tmp/callback-corrupt.out" 2>&1; then
|
|
340
|
+
fail "doctor accepted SQLite callback-store corruption"
|
|
341
|
+
fi
|
|
342
|
+
grep -q "\[FAIL\] capability callback namespace could not be diagnosed safely; preserve .agentfs/trajectory.db before recovery" "$tmp/callback-corrupt.out" ||
|
|
343
|
+
fail "expected value-free callback corruption guidance"
|
|
344
|
+
if grep -qF 'PHASE18_CALLBACK_ROW_SENTINEL' "$tmp/callback-corrupt.out"; then
|
|
345
|
+
fail "callback corruption diagnostics exposed stored bytes"
|
|
346
|
+
fi
|
|
347
|
+
cmp -s "$tmp/callback-corrupt.before" "$callback_corrupt/.agentfs/trajectory.db" ||
|
|
348
|
+
fail "callback diagnostics mutated a corrupt database"
|
|
349
|
+
|
|
350
|
+
# 7. Evaluation diagnostics consume only the private two-key envelope, map all
|
|
351
|
+
# five report states, and collapse malformed CLI output without reflection.
|
|
352
|
+
create_eval_report_fixture() {
|
|
353
|
+
local project="$1"
|
|
354
|
+
local state="$2"
|
|
355
|
+
mkdir -p "$project"
|
|
356
|
+
node --input-type=module - "$ROOT" "$project" "$state" <<'NODE'
|
|
357
|
+
import { chmod, symlink, writeFile } from "node:fs/promises";
|
|
358
|
+
import { join } from "node:path";
|
|
359
|
+
|
|
360
|
+
const root = process.argv[2];
|
|
361
|
+
const project = process.argv[3];
|
|
362
|
+
const state = process.argv[4];
|
|
363
|
+
const reportApi = await import(`${root}/mcp-memory-server/dist/eval-report.js`);
|
|
364
|
+
const store = await reportApi.createEvalReportStore({ project_root: project, experiment_id: "fixture-experiment" });
|
|
365
|
+
if (state === "unsafe") {
|
|
366
|
+
const target = join(project, "environment-sentinel");
|
|
367
|
+
await writeFile(target, "environment-sentinel\n", { mode: 0o600 });
|
|
368
|
+
await symlink(target, store.report_path);
|
|
369
|
+
process.exit(0);
|
|
370
|
+
}
|
|
371
|
+
if (state === "tampered") {
|
|
372
|
+
await writeFile(store.report_path, '{"adapter-stderr":"adapter-stderr-sentinel"}\n', { mode: 0o600 });
|
|
373
|
+
await chmod(store.report_path, 0o600);
|
|
374
|
+
process.exit(0);
|
|
375
|
+
}
|
|
376
|
+
const report = {
|
|
377
|
+
schema_version: 1,
|
|
378
|
+
experiment_id: store.experiment_id,
|
|
379
|
+
status: state === "partial" ? "partial" : "final",
|
|
380
|
+
experiment_kind: "two_pass",
|
|
381
|
+
task_set_digest: "0".repeat(64), adapter_config_digest: "1".repeat(64),
|
|
382
|
+
source_revision: "2".repeat(40), schedule_digest: "3".repeat(64),
|
|
383
|
+
created_at: "2026-01-01T00:00:00.000Z", updated_at: "2026-01-01T00:00:01.000Z",
|
|
384
|
+
runtime: { platform: "linux", arch: "x64", node: "22.0.0", cairnkeep: "0.0.0" },
|
|
385
|
+
schedule: [], observations: [], aggregates: [],
|
|
386
|
+
missingness: { digest: "4".repeat(64), count: 0, reasons: [] }, warnings: [],
|
|
387
|
+
evidence: {
|
|
388
|
+
schema_version: 1, evidence_scope: "offline-framework", source_commit: "2".repeat(40),
|
|
389
|
+
package_version: "0.0.0", runtime_id: "node-22-linux-x64", task_set_digest: "0".repeat(64),
|
|
390
|
+
report_digest: "5".repeat(64), schema_digests: ["6".repeat(64), "7".repeat(64)], note_snapshot_digests: [],
|
|
391
|
+
missingness_digest: "4".repeat(64), claim_anchors: [],
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
await reportApi.checkpointEvalReport(store, report);
|
|
395
|
+
NODE
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for state in ok partial tampered unsafe; do
|
|
399
|
+
eval_project="$tmp/eval-$state"
|
|
400
|
+
create_eval_report_fixture "$eval_project" "$state"
|
|
401
|
+
( cd "$eval_project" && env -u CAIRN_EVAL "$doctor" ) >"$tmp/eval-$state.out" 2>"$tmp/eval-$state.err" ||
|
|
402
|
+
fail "doctor rejected value-free evaluation state $state"
|
|
403
|
+
done
|
|
404
|
+
grep -q "\[PASS\] evaluation reports are complete and valid" "$tmp/eval-ok.out" ||
|
|
405
|
+
fail "expected complete evaluation report PASS"
|
|
406
|
+
grep -q "\[WARN\] evaluation reports are partial" "$tmp/eval-partial.out" ||
|
|
407
|
+
fail "expected partial evaluation report warning"
|
|
408
|
+
grep -q "\[WARN\] evaluation report integrity check failed" "$tmp/eval-tampered.out" ||
|
|
409
|
+
fail "expected tampered evaluation report warning"
|
|
410
|
+
grep -q "\[WARN\] evaluation report storage is unsafe" "$tmp/eval-unsafe.out" ||
|
|
411
|
+
fail "expected unsafe evaluation report warning"
|
|
412
|
+
grep -q "\[SKIP\] evaluation reports (not present — evaluation is opt-in)" "$tmp/out1" ||
|
|
413
|
+
fail "expected disabled/absent evaluation report health result"
|
|
414
|
+
|
|
415
|
+
real_node=$(command -v node)
|
|
416
|
+
mkdir -p "$tmp/eval-node-shim"
|
|
417
|
+
cat >"$tmp/eval-node-shim/node" <<'EOF'
|
|
418
|
+
#!/usr/bin/env bash
|
|
419
|
+
if [[ "${1:-}" == */eval-cli.js && "${2:-}" == doctor-diagnosis ]]; then
|
|
420
|
+
printf '%s\n' "$CAIRN_TEST_EVAL_OUTPUT"
|
|
421
|
+
[[ -z "${CAIRN_TEST_EVAL_STDERR:-}" ]] || printf '%s\n' "$CAIRN_TEST_EVAL_STDERR" >&2
|
|
422
|
+
exit "${CAIRN_TEST_EVAL_EXIT:-0}"
|
|
423
|
+
fi
|
|
424
|
+
exec "$CAIRN_TEST_REAL_NODE" "$@"
|
|
425
|
+
EOF
|
|
426
|
+
chmod 755 "$tmp/eval-node-shim/node"
|
|
427
|
+
|
|
428
|
+
oversized=$(printf '%01024d' 0)
|
|
429
|
+
for malformed_output in \
|
|
430
|
+
'{"schema_version":1,"diagnosis":"unknown"}' \
|
|
431
|
+
'{"schema_version":1,"diagnosis":"ok","prompt":"prompt-sentinel"}' \
|
|
432
|
+
"$oversized"; do
|
|
433
|
+
( cd "$proj" && PATH="$tmp/eval-node-shim:$PATH" CAIRN_TEST_REAL_NODE="$real_node" \
|
|
434
|
+
CAIRN_TEST_EVAL_OUTPUT="$malformed_output" "$doctor" ) >"$tmp/eval-malformed.out" 2>"$tmp/eval-malformed.err" ||
|
|
435
|
+
fail "doctor treated malformed private diagnosis output as a configured dependency failure"
|
|
436
|
+
grep -q "\[WARN\] evaluation report storage is unsafe" "$tmp/eval-malformed.out" ||
|
|
437
|
+
fail "malformed private diagnosis output did not fail closed"
|
|
438
|
+
if grep -Eq 'prompt-sentinel|model-output-sentinel' "$tmp/eval-malformed.out" "$tmp/eval-malformed.err"; then
|
|
439
|
+
fail "malformed private diagnosis output reflected fixture bytes"
|
|
440
|
+
fi
|
|
441
|
+
done
|
|
442
|
+
|
|
443
|
+
( cd "$proj" && PATH="$tmp/eval-node-shim:$PATH" CAIRN_TEST_REAL_NODE="$real_node" \
|
|
444
|
+
CAIRN_TEST_EVAL_OUTPUT='' CAIRN_TEST_EVAL_STDERR='model-output-sentinel' CAIRN_TEST_EVAL_EXIT=9 "$doctor" ) \
|
|
445
|
+
>"$tmp/eval-error.out" 2>"$tmp/eval-error.err" ||
|
|
446
|
+
fail "doctor treated private diagnosis execution failure as a configured dependency failure"
|
|
447
|
+
grep -q "\[WARN\] evaluation report storage is unsafe" "$tmp/eval-error.out" ||
|
|
448
|
+
fail "private diagnosis execution failure did not fail closed"
|
|
449
|
+
|
|
450
|
+
for sentinel in prompt-sentinel model-output-sentinel adapter-stderr-sentinel environment-sentinel; do
|
|
451
|
+
if grep -F "$sentinel" "$tmp"/eval-*.out "$tmp"/eval-*.err >/dev/null; then
|
|
452
|
+
fail "evaluation doctor exposed $sentinel"
|
|
453
|
+
fi
|
|
454
|
+
done
|
|
455
|
+
|
|
456
|
+
echo "PASS: cairn doctor (existing probes plus value-free capability/evaluation diagnostics)"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Installed-style public and private evaluation CLI contract.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
6
|
+
MODE="${1:-baseline}"
|
|
7
|
+
[[ $# -gt 0 ]] && shift
|
|
8
|
+
|
|
9
|
+
usage() {
|
|
10
|
+
echo 'Usage: test-eval-cli.sh [--expected-red|--doctor-diagnosis]' >&2
|
|
11
|
+
}
|
|
12
|
+
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
13
|
+
|
|
14
|
+
case "$MODE" in
|
|
15
|
+
baseline|--expected-red|--doctor-diagnosis) [[ $# -eq 0 ]] || { usage; exit 2; } ;;
|
|
16
|
+
*) usage; exit 2 ;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
tmp=$(mktemp -d)
|
|
20
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
21
|
+
cli="$ROOT/mcp-memory-server/dist/eval-cli.js"
|
|
22
|
+
|
|
23
|
+
baseline() {
|
|
24
|
+
bash -n "$ROOT/scripts/test-eval-cli.sh"
|
|
25
|
+
"$ROOT/bin/cairn" help >/dev/null
|
|
26
|
+
[[ ! -e "$tmp/project" && ! -e "$tmp/output" && ! -e "$tmp/database" && ! -e "$tmp/network" ]] || \
|
|
27
|
+
fail "baseline created a side-effect sentinel"
|
|
28
|
+
# Once the compiled owner lands, default execution exercises the installed-
|
|
29
|
+
# style surface while Wave 0 remains green without changing root composition.
|
|
30
|
+
if [[ -f "$cli" ]]; then
|
|
31
|
+
node "$cli" --help >"$tmp/help"
|
|
32
|
+
for operation in validate run ablate report prune delete; do
|
|
33
|
+
grep -q "$operation" "$tmp/help" || fail "help omitted $operation"
|
|
34
|
+
done
|
|
35
|
+
grep -q 'doctor-diagnosis' "$tmp/help" && fail "public help exposed private diagnosis"
|
|
36
|
+
env -u CAIRN_EVAL node "$cli" validate --task-set "$tmp/unread-task-set" --adapter "$tmp/unread-adapter" --output "$tmp/unwritten" --json >"$tmp/disabled.json"
|
|
37
|
+
node -e 'const v=require(process.argv[1]);if(v.schema_version!==1||v.enabled!==false)process.exit(1)' "$tmp/disabled.json" \
|
|
38
|
+
|| fail "disabled JSON is not stable"
|
|
39
|
+
[[ ! -e "$tmp/unwritten" ]] || fail "disabled CLI created output"
|
|
40
|
+
if node "$cli" unknown-operation >"$tmp/unknown.out" 2>"$tmp/unknown.err"; then
|
|
41
|
+
fail "unknown operation succeeded"
|
|
42
|
+
fi
|
|
43
|
+
fi
|
|
44
|
+
echo "PASS: Phase 19 eval CLI Wave 0/default contract"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
doctor_diagnosis() {
|
|
48
|
+
[[ -f "$cli" ]] || fail "compiled eval CLI is absent"
|
|
49
|
+
|
|
50
|
+
create_report_fixture() {
|
|
51
|
+
local project="$1"
|
|
52
|
+
local state="$2"
|
|
53
|
+
mkdir -p "$project"
|
|
54
|
+
node --input-type=module - "$ROOT" "$project" "$state" <<'NODE'
|
|
55
|
+
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
|
|
56
|
+
import { join } from "node:path";
|
|
57
|
+
|
|
58
|
+
const root = process.argv[2];
|
|
59
|
+
const project = process.argv[3];
|
|
60
|
+
const state = process.argv[4];
|
|
61
|
+
const reportApi = await import(`${root}/mcp-memory-server/dist/eval-report.js`);
|
|
62
|
+
const store = await reportApi.createEvalReportStore({
|
|
63
|
+
project_root: project,
|
|
64
|
+
experiment_id: "fixture-experiment",
|
|
65
|
+
});
|
|
66
|
+
if (state === "unsafe") {
|
|
67
|
+
const target = join(project, "unsafe-target");
|
|
68
|
+
await writeFile(target, "absolute-path-sentinel\n", { mode: 0o600 });
|
|
69
|
+
await symlink(target, store.report_path);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
if (state === "tampered") {
|
|
73
|
+
await writeFile(store.report_path, '{"prompt":"prompt-sentinel"}\n', { mode: 0o600 });
|
|
74
|
+
await chmod(store.report_path, 0o600);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
const report = {
|
|
78
|
+
schema_version: 1,
|
|
79
|
+
experiment_id: store.experiment_id,
|
|
80
|
+
status: state === "partial" ? "partial" : "final",
|
|
81
|
+
experiment_kind: "two_pass",
|
|
82
|
+
task_set_digest: "0".repeat(64),
|
|
83
|
+
adapter_config_digest: "1".repeat(64),
|
|
84
|
+
source_revision: "2".repeat(40),
|
|
85
|
+
schedule_digest: "3".repeat(64),
|
|
86
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
87
|
+
updated_at: "2026-01-01T00:00:01.000Z",
|
|
88
|
+
runtime: { platform: "linux", arch: "x64", node: "22.0.0", cairnkeep: "0.0.0" },
|
|
89
|
+
schedule: [], observations: [], aggregates: [],
|
|
90
|
+
missingness: { digest: "4".repeat(64), count: 0, reasons: [] },
|
|
91
|
+
warnings: [],
|
|
92
|
+
evidence: {
|
|
93
|
+
schema_version: 1,
|
|
94
|
+
evidence_scope: "offline-framework",
|
|
95
|
+
source_commit: "2".repeat(40),
|
|
96
|
+
package_version: "0.0.0",
|
|
97
|
+
runtime_id: "node-22-linux-x64",
|
|
98
|
+
task_set_digest: "0".repeat(64),
|
|
99
|
+
report_digest: "5".repeat(64),
|
|
100
|
+
schema_digests: ["6".repeat(64), "7".repeat(64)],
|
|
101
|
+
note_snapshot_digests: [],
|
|
102
|
+
missingness_digest: "4".repeat(64),
|
|
103
|
+
claim_anchors: [],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
await reportApi.checkpointEvalReport(store, report);
|
|
107
|
+
NODE
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for state in absent ok partial tampered unsafe; do
|
|
111
|
+
project="$tmp/$state"
|
|
112
|
+
mkdir -p "$project"
|
|
113
|
+
[[ "$state" == "absent" ]] || create_report_fixture "$project" "$state"
|
|
114
|
+
env -u CAIRN_EVAL node "$cli" doctor-diagnosis --root "$project" --json >"$tmp/$state.json" 2>"$tmp/$state.err"
|
|
115
|
+
node - "$tmp/$state.json" "$state" <<'NODE'
|
|
116
|
+
const value = require(process.argv[2]);
|
|
117
|
+
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(["diagnosis", "schema_version"])) process.exit(1);
|
|
118
|
+
if (value.schema_version !== 1 || value.diagnosis !== process.argv[3]) process.exit(1);
|
|
119
|
+
NODE
|
|
120
|
+
[[ ! -s "$tmp/$state.err" ]] || fail "diagnosis wrote diagnostics for $state"
|
|
121
|
+
[[ $(wc -c <"$tmp/$state.json") -le 64 ]] || fail "diagnosis output exceeded its fixed bound"
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
mkdir -p "$tmp/symlink-target"
|
|
125
|
+
mkdir -p "$tmp/symlink-root"
|
|
126
|
+
ln -s "$tmp/symlink-target" "$tmp/symlink-root/.agentfs"
|
|
127
|
+
env -u CAIRN_EVAL node "$cli" doctor-diagnosis --root "$tmp/symlink-root" --json >"$tmp/symlink-root.json"
|
|
128
|
+
node -e 'const v=require(process.argv[1]);if(v.schema_version!==1||v.diagnosis!=="unsafe"||Object.keys(v).length!==2)process.exit(1)' \
|
|
129
|
+
"$tmp/symlink-root.json" || fail "symlinked evaluation ancestor did not fail closed"
|
|
130
|
+
|
|
131
|
+
for malformed in \
|
|
132
|
+
"doctor-diagnosis --json" \
|
|
133
|
+
"doctor-diagnosis --root prompt-sentinel" \
|
|
134
|
+
"doctor-diagnosis --root $tmp/absent --json --extra model-output-sentinel"; do
|
|
135
|
+
# shellcheck disable=SC2086
|
|
136
|
+
env -u CAIRN_EVAL node "$cli" $malformed >"$tmp/malformed.json" 2>"$tmp/malformed.err" ||
|
|
137
|
+
fail "malformed diagnosis invocation did not collapse safely"
|
|
138
|
+
node -e 'const v=require(process.argv[1]);if(JSON.stringify(v)!==JSON.stringify({schema_version:1,diagnosis:"unsafe"}))process.exit(1)' \
|
|
139
|
+
"$tmp/malformed.json" || fail "malformed diagnosis invocation returned a non-canonical envelope"
|
|
140
|
+
[[ ! -s "$tmp/malformed.err" ]] || fail "malformed diagnosis invocation exposed stderr"
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
help_and_completions="$tmp/public-vocabulary"
|
|
144
|
+
{
|
|
145
|
+
node "$cli" --help
|
|
146
|
+
sed -n '1,220p' "$ROOT/scripts/completion.sh"
|
|
147
|
+
} >"$help_and_completions"
|
|
148
|
+
! grep -qF 'doctor-diagnosis' "$help_and_completions" || fail "private diagnosis entered public discovery"
|
|
149
|
+
|
|
150
|
+
for sentinel in prompt-sentinel model-output-sentinel adapter-stderr-sentinel environment-sentinel absolute-path-sentinel; do
|
|
151
|
+
! grep -F "$sentinel" "$tmp"/*.json "$tmp"/*.err >/dev/null || fail "diagnosis reflected $sentinel"
|
|
152
|
+
done
|
|
153
|
+
echo "PASS: Phase 19 private value-free five-state diagnosis contract"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case "$MODE" in
|
|
157
|
+
--expected-red)
|
|
158
|
+
baseline >/dev/null
|
|
159
|
+
set +e
|
|
160
|
+
node --input-type=module - "$cli" >"$tmp/red.out" 2>"$tmp/red.err" <<'NODE'
|
|
161
|
+
import { pathToFileURL } from "node:url";
|
|
162
|
+
await import(pathToFileURL(process.argv[2]).href);
|
|
163
|
+
NODE
|
|
164
|
+
status=$?
|
|
165
|
+
set -e
|
|
166
|
+
if [[ "$status" -ne 0 ]] && grep -qF '/dist/eval-cli.js' "$tmp/red.err" && grep -qF 'ERR_MODULE_NOT_FOUND' "$tmp/red.err"; then
|
|
167
|
+
echo 'PHASE19_RED:EVAL_CLI_MISSING'
|
|
168
|
+
exit 0
|
|
169
|
+
fi
|
|
170
|
+
cat "$tmp/red.out" "$tmp/red.err" >&2
|
|
171
|
+
fail "expected only the missing compiled eval CLI"
|
|
172
|
+
;;
|
|
173
|
+
--doctor-diagnosis) doctor_diagnosis ;;
|
|
174
|
+
baseline) baseline ;;
|
|
175
|
+
esac
|