@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +221 -6
  3. package/bin/cairn +43 -2
  4. package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
  5. package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
  6. package/claude/hooks/compaction-capture.sh +34 -0
  7. package/claude/hooks/memory-capture.sh +47 -5
  8. package/claude/hooks/memory-wakeup.sh +55 -2
  9. package/docs/building-an-overlay.md +1 -1
  10. package/docs/design/meta-agent.md +439 -0
  11. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  12. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  13. package/docs/learning/lessons/L02-installation.md +2 -2
  14. package/docs/learning/lessons/L03-first-project.md +1 -1
  15. package/docs/operating.md +531 -4
  16. package/docs/privacy-and-data-flow.md +417 -0
  17. package/docs/storage.md +252 -0
  18. package/examples/eval/README.md +42 -0
  19. package/examples/eval/adapter.json +12 -0
  20. package/examples/eval/bundled-fake.json +1 -0
  21. package/examples/eval/task-set.json +1 -0
  22. package/mcp-memory-server/dist/artifact-cli.js +184 -0
  23. package/mcp-memory-server/dist/artifact-schema.js +218 -0
  24. package/mcp-memory-server/dist/artifact-store.js +812 -0
  25. package/mcp-memory-server/dist/capability-adapter.js +286 -0
  26. package/mcp-memory-server/dist/capability-cli.js +361 -0
  27. package/mcp-memory-server/dist/capability-config.js +310 -0
  28. package/mcp-memory-server/dist/capability-harness.js +496 -0
  29. package/mcp-memory-server/dist/capability-registry.js +123 -0
  30. package/mcp-memory-server/dist/capability-schema.js +65 -0
  31. package/mcp-memory-server/dist/capability-store.js +431 -0
  32. package/mcp-memory-server/dist/compaction-normalize.js +267 -0
  33. package/mcp-memory-server/dist/eval-cli.js +523 -0
  34. package/mcp-memory-server/dist/eval-plan.js +346 -0
  35. package/mcp-memory-server/dist/eval-process.js +273 -0
  36. package/mcp-memory-server/dist/eval-report.js +609 -0
  37. package/mcp-memory-server/dist/eval-runner.js +827 -0
  38. package/mcp-memory-server/dist/eval-schema.js +368 -0
  39. package/mcp-memory-server/dist/eval-statistics.js +161 -0
  40. package/mcp-memory-server/dist/eval-workspace.js +313 -0
  41. package/mcp-memory-server/dist/failure-signature.js +141 -0
  42. package/mcp-memory-server/dist/index.js +600 -55
  43. package/mcp-memory-server/dist/node-cli.js +84 -0
  44. package/mcp-memory-server/dist/node-schema.js +170 -0
  45. package/mcp-memory-server/dist/node-store.js +455 -0
  46. package/mcp-memory-server/dist/note-cli.js +235 -0
  47. package/mcp-memory-server/dist/note-distiller.js +177 -0
  48. package/mcp-memory-server/dist/note-enrichment.js +93 -0
  49. package/mcp-memory-server/dist/note-schema.js +4 -0
  50. package/mcp-memory-server/dist/note-store.js +985 -0
  51. package/mcp-memory-server/dist/trajectory-cli.js +141 -0
  52. package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
  53. package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
  54. package/mcp-memory-server/dist/trajectory-schema.js +73 -0
  55. package/mcp-memory-server/dist/trajectory-store.js +339 -0
  56. package/mcp-memory-server/package.json +24 -2
  57. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
  58. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
  59. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
  60. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
  61. package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
  62. package/opencode/capability-contract/plugins/capability-command.ts +319 -0
  63. package/opencode/command/graphify.md +25 -14
  64. package/opencode/plugins/memory-capture.ts +76 -10
  65. package/opencode/plugins/memory-wakeup.ts +58 -6
  66. package/package.json +9 -4
  67. package/pi/extensions/cairnkeep-trajectory.ts +55 -0
  68. package/schemas/artifact.schema.json +133 -0
  69. package/schemas/capability-callback.schema.json +68 -0
  70. package/schemas/capability-contract.schema.json +126 -0
  71. package/schemas/eval-adapter.schema.json +43 -0
  72. package/schemas/eval-protocol.schema.json +114 -0
  73. package/schemas/eval-report.schema.json +423 -0
  74. package/schemas/eval-task-set.schema.json +114 -0
  75. package/schemas/memory-node.schema.json +90 -0
  76. package/schemas/note.schema.json +112 -0
  77. package/schemas/trajectory-redaction.schema.json +25 -0
  78. package/schemas/trajectory.schema.json +82 -0
  79. package/scripts/bootstrap.sh +4 -0
  80. package/scripts/completion.sh +119 -5
  81. package/scripts/doctor.sh +284 -0
  82. package/scripts/fake-eval-adapter.mjs +120 -0
  83. package/scripts/fixtures/capability-harness-contracts.json +408 -0
  84. package/scripts/install-audit-timer.sh +3 -1
  85. package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
  86. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
  87. package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
  88. package/scripts/memory-wiki-audit.sh +49 -0
  89. package/scripts/smoke-bash32.sh +9 -0
  90. package/scripts/sync-claude-assets.sh +99 -6
  91. package/scripts/sync-opencode-graphify-assets.sh +24 -3
  92. package/scripts/sync-opencode-plugin-assets.sh +31 -5
  93. package/scripts/sync-opencode-security-assets.sh +24 -3
  94. package/scripts/sync-opencode-wiki-assets.sh +24 -3
  95. package/scripts/sync-pi-assets.sh +72 -0
  96. package/scripts/test-audit-timer.sh +98 -0
  97. package/scripts/test-bootstrap-untracked.sh +48 -0
  98. package/scripts/test-cli-dispatch.sh +41 -2
  99. package/scripts/test-compaction-hooks.sh +495 -0
  100. package/scripts/test-completion.sh +77 -0
  101. package/scripts/test-doctor.sh +348 -1
  102. package/scripts/test-eval-cli.sh +175 -0
  103. package/scripts/test-launcher-hooks.sh +602 -3
  104. package/scripts/test-notes-cli.sh +114 -0
  105. package/scripts/test-package-install.sh +251 -0
  106. package/scripts/test-phase16-node-lifecycle.sh +50 -0
  107. package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
  108. package/scripts/test-phase18-capability-lifecycle.sh +504 -0
  109. package/scripts/test-phase18-harness-boundary.sh +1060 -0
  110. package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
  111. package/scripts/test-trajectory-cli.sh +87 -0
  112. package/scripts/test-trajectory-hooks.sh +142 -0
  113. package/scripts/test-uninstall.sh +231 -5
  114. package/scripts/uninstall.sh +37 -5
  115. package/scripts/verify-dependency-security.mjs +121 -0
  116. package/scripts/verify-docs-parity.sh +527 -1
  117. package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
  118. package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
  119. package/templates/capabilities.json.template +7 -0
  120. package/templates/env.example.template +51 -0
  121. package/templates/memory-wiki-audit.service.template +1 -1
  122. package/templates/start-claude.sh.template +36 -0
  123. package/templates/start-opencode.sh.template +47 -5
  124. package/templates/start-pi.sh.template +30 -0
  125. package/templates/trajectory-redaction.json.template +4 -0
@@ -21,6 +21,9 @@ set -euo pipefail
21
21
  usage() {
22
22
  cat <<'EOF'
23
23
  Usage: verify-docs-parity.sh
24
+ verify-docs-parity.sh --artifact-remote-path-only
25
+ verify-docs-parity.sh --expect-red-artifact-remote-path
26
+ verify-docs-parity.sh --self-test-native-capability-docs
24
27
  verify-docs-parity.sh -h|--help
25
28
 
26
29
  Checks that every CAIRN_*/MCP_HTTP_* env key read by the cairn-memory MCP
@@ -32,14 +35,235 @@ drift, or 0 if the docs are fully in sync with the shipped code.
32
35
  EOF
33
36
  }
34
37
 
38
+ EXPECTED_RED_EXIT=86
39
+ ARTIFACT_REMOTE_PATH_RED_MARKER='PHASE17_RED:ARTIFACT_REMOTE_PATH_DOC_DRIFT'
40
+
35
41
  ENV_KEY_PATTERN='(CAIRN_[A-Z_]+|MCP_HTTP_[A-Z_]+)'
36
42
 
43
+ run_native_capability_docs_self_test() {
44
+ local fixture_root pristine_root
45
+ fixture_root=$(mktemp -d)
46
+ trap "rm -rf '$fixture_root'" EXIT HUP INT TERM
47
+ pristine_root="$fixture_root/pristine"
48
+
49
+ mkdir -p \
50
+ "$fixture_root/docs" \
51
+ "$fixture_root/mcp-memory-server/src" \
52
+ "$fixture_root/claude/capability-contract/hooks" \
53
+ "$fixture_root/opencode/capability-contract/plugins" \
54
+ "$fixture_root/scripts" \
55
+ "$pristine_root"
56
+ cp docs/operating.md docs/privacy-and-data-flow.md docs/storage.md "$fixture_root/docs/"
57
+ cp docs/operating.md docs/privacy-and-data-flow.md docs/storage.md "$pristine_root/"
58
+ cp mcp-memory-server/src/capability-harness.ts "$fixture_root/mcp-memory-server/src/"
59
+ cp mcp-memory-server/src/capability-store.ts "$fixture_root/mcp-memory-server/src/"
60
+ cp claude/capability-contract/hooks/capability-command-start.sh "$fixture_root/claude/capability-contract/hooks/"
61
+ cp claude/capability-contract/hooks/capability-command-finish.sh "$fixture_root/claude/capability-contract/hooks/"
62
+ cp opencode/capability-contract/plugins/capability-command.ts "$fixture_root/opencode/capability-contract/plugins/"
63
+ cp scripts/sync-claude-assets.sh scripts/sync-opencode-plugin-assets.sh "$fixture_root/scripts/"
64
+
65
+ check_native_capability_docs "$fixture_root"
66
+
67
+ expect_native_docs_drift() {
68
+ local label="$1" file="$2" before="$3" after="$4"
69
+ cp "$pristine_root/operating.md" "$fixture_root/docs/operating.md"
70
+ cp "$pristine_root/privacy-and-data-flow.md" "$fixture_root/docs/privacy-and-data-flow.md"
71
+ cp "$pristine_root/storage.md" "$fixture_root/docs/storage.md"
72
+ python3 - "$fixture_root/$file" "$before" "$after" <<'PY'
73
+ from pathlib import Path
74
+ import sys
75
+
76
+ path = Path(sys.argv[1])
77
+ before, after = sys.argv[2], sys.argv[3]
78
+ text = path.read_text(encoding="utf-8")
79
+ if before not in text:
80
+ raise SystemExit(f"self-test fixture text not found: {before}")
81
+ path.write_text(text.replace(before, after, 1), encoding="utf-8")
82
+ PY
83
+ if check_native_capability_docs "$fixture_root" >/dev/null 2>&1; then
84
+ echo "FATAL: native capability docs mutation was not rejected: $label" >&2
85
+ return 1
86
+ fi
87
+ }
88
+
89
+ expect_native_docs_drift "master-off state" docs/operating.md \
90
+ "create no capability measurement state" "create one capability measurement record"
91
+ expect_native_docs_drift "all-three-consents requirement" docs/privacy-and-data-flow.md \
92
+ "When all three consents are enabled" "When logging alone is enabled"
93
+ expect_native_docs_drift "disabled unmeasured block" docs/privacy-and-data-flow.md \
94
+ "the same fixed block remains in force and" "owner execution continues and"
95
+ expect_native_docs_drift "enabled unmeasured pass-through" docs/privacy-and-data-flow.md \
96
+ "measurement consent off leaves owner execution unchanged and writes no" \
97
+ "measurement consent off changes owner execution and writes one"
98
+ expect_native_docs_drift "StopFailure immediacy" docs/operating.md \
99
+ "error settlement is not deferred to" "error settlement is deferred to"
100
+ expect_native_docs_drift "immutable start binding" docs/operating.md \
101
+ "project identity and lease are immutable" "project identity and lease may be rebound"
102
+ expect_native_docs_drift "master-off installation" docs/operating.md \
103
+ "install and invoke no capability hook or plugin" "install and invoke a capability hook or plugin"
104
+ expect_native_docs_drift "evidence scope" docs/operating.md \
105
+ "They are not" "They are"
106
+
107
+ echo "[native-capability-docs-self-test] OK: focused drift cases are rejected"
108
+ }
109
+
110
+ native_file_text() {
111
+ awk '{$1=$1; printf "%s ", $0}' "$1"
112
+ }
113
+
114
+ native_section_text() {
115
+ local file="$1" start="$2" end="$3"
116
+ awk -v start="$start" -v end="$end" '
117
+ index($0, start) { active = 1 }
118
+ active && index($0, end) && !index($0, start) { exit }
119
+ active { $1=$1; printf "%s ", $0 }
120
+ ' "$file"
121
+ }
122
+
123
+ require_native_term() {
124
+ local text="$1" term="$2" label="$3"
125
+ case "$text" in
126
+ *"$term"*) return 0 ;;
127
+ *) echo "FATAL: native capability parity term is missing: $label" >&2; return 1 ;;
128
+ esac
129
+ }
130
+
131
+ reject_native_term() {
132
+ local text="$1" term="$2" label="$3"
133
+ case "$text" in
134
+ *"$term"*) echo "FATAL: retired native capability wording remains: $label" >&2; return 1 ;;
135
+ *) return 0 ;;
136
+ esac
137
+ }
138
+
139
+ check_native_capability_docs() {
140
+ local root="${1:-.}" failed=0
141
+ local operating privacy storage harness store claude_start claude_finish opencode_plugin claude_sync opencode_sync
142
+ local operating_contract privacy_contract storage_contract file
143
+
144
+ operating="$root/docs/operating.md"
145
+ privacy="$root/docs/privacy-and-data-flow.md"
146
+ storage="$root/docs/storage.md"
147
+ harness="$root/mcp-memory-server/src/capability-harness.ts"
148
+ store="$root/mcp-memory-server/src/capability-store.ts"
149
+ claude_start="$root/claude/capability-contract/hooks/capability-command-start.sh"
150
+ claude_finish="$root/claude/capability-contract/hooks/capability-command-finish.sh"
151
+ opencode_plugin="$root/opencode/capability-contract/plugins/capability-command.ts"
152
+ claude_sync="$root/scripts/sync-claude-assets.sh"
153
+ opencode_sync="$root/scripts/sync-opencode-plugin-assets.sh"
154
+
155
+ for file in "$operating" "$privacy" "$storage" "$harness" "$store" \
156
+ "$claude_start" "$claude_finish" "$opencode_plugin" "$claude_sync" "$opencode_sync"; do
157
+ if [[ ! -f "$file" ]]; then
158
+ echo "FATAL: native capability parity input is missing: ${file#"$root"/}" >&2
159
+ failed=1
160
+ fi
161
+ done
162
+ [[ "$failed" -eq 0 ]] || return "$failed"
163
+
164
+ operating_contract=$(native_section_text "$operating" "### Managed capability contract" "### Typed memory nodes")
165
+ privacy_contract=$(native_section_text "$privacy" "## Capability callback flow" "## Hindsight note distillation")
166
+ storage_contract=$(native_section_text "$storage" "## Capability callback storage" "## Artifact storage")
167
+
168
+ while IFS='|' read -r term label; do
169
+ [[ -n "$term" ]] || continue
170
+ require_native_term "$operating_contract" "$term" "$label" || failed=1
171
+ done <<'EOF'
172
+ Master off is exact legacy behavior|master-off exact legacy behavior
173
+ install and invoke no capability hook or plugin|master-off native owner absence
174
+ no capability block|master-off block absence
175
+ create no capability measurement state|master-off measurement-state absence
176
+ `capability-overlay` mode|overlay-only installation
177
+ UserPromptExpansion|Claude pre-expansion admission
178
+ project identity and lease are immutable|immutable start-time project binding
179
+ StopFailure|Claude error terminal
180
+ error settlement is not deferred to|immediate StopFailure settlement
181
+ abandonment cleanup only for unfinished|unfinished-only cleanup
182
+ OpenCode 1.17.20|pinned OpenCode lifecycle
183
+ `session.error` settles error|OpenCode error settlement
184
+ all three consents are on|three-consent disabled final
185
+ either measurement consent is off, it blocks with no state|disabled unmeasured block
186
+ target enabled, either measurement consent being off preserves owner execution unchanged|enabled unmeasured pass-through
187
+ Deterministic native-boundary tests|deterministic evidence label
188
+ not exhaustive live real-owner evidence|non-exhaustive evidence label
189
+ complete live eight-by-seven matrix|mandatory live matrix
190
+ 56 genuine owner executions|live matrix cell count
191
+ Any missing, failed, or unavailable cell keeps Phase 18 incomplete|blocking live acceptance
192
+ `wiki-ingest`, `wiki-query`, `wiki-lint`, `graphify`, and `security-audit`|five operating command surfaces
193
+ D-10, D-12, and D-16|preserved owner decisions
194
+ EOF
195
+
196
+ while IFS='|' read -r term label; do
197
+ [[ -n "$term" ]] || continue
198
+ require_native_term "$privacy_contract" "$term" "$label" || failed=1
199
+ done <<'EOF'
200
+ three-state privacy contract|three-state privacy declaration
201
+ installs or invokes no capability hook/plugin|master-off owner absence
202
+ creates no pending lease, callback final, or other capability measurement state|master-off state absence
203
+ target disabled, the fixed block always occurs before owner I/O|disabled pre-owner block
204
+ When all three consents are enabled|all-three-consents requirement
205
+ exactly one D-25/D-26 value-free `disabled` final|value-free disabled final
206
+ same fixed block remains in force and no pending or final state is written|disabled unmeasured no-state branch
207
+ target enabled, turning either measurement consent off leaves owner execution unchanged and writes no pending or final state|enabled unmeasured pass-through
208
+ EOF
209
+
210
+ while IFS='|' read -r term label; do
211
+ [[ -n "$term" ]] || continue
212
+ require_native_term "$storage_contract" "$term" "$label" || failed=1
213
+ done <<'EOF'
214
+ No capability state exists in exact legacy master-off operation|master-off storage absence
215
+ target-enabled invocation creates no lease or final when either measurement consent is off|enabled unmeasured storage absence
216
+ target-disabled invocation still blocks before owner I/O|disabled policy storage branch
217
+ Only all three consents may create the recoverable lease|all-three-consents lease issuance
218
+ exactly one value-free `disabled` final|disabled final storage shape
219
+ settlement is atomic and idempotent|atomic idempotent settlement
220
+ cleanup consume only unfinished leases as abandonment|unfinished-only abandonment
221
+ never replace a settled terminal|terminal immutability
222
+ EOF
223
+
224
+ for term in "prompt wrapper" "prompt owner" "command wrapper" "workflow wrapper"; do
225
+ reject_native_term "$operating_contract" "$term" "$term" || failed=1
226
+ reject_native_term "$privacy_contract" "$term" "$term" || failed=1
227
+ reject_native_term "$storage_contract" "$term" "$term" || failed=1
228
+ done
229
+ reject_native_term "$operating_contract" "They are exhaustive live real-owner evidence" \
230
+ "deterministic evidence presented as exhaustive" || failed=1
231
+
232
+ while IFS='|' read -r file term label; do
233
+ [[ -n "$file" ]] || continue
234
+ require_native_term "$(native_file_text "$file")" "$term" "$label" || failed=1
235
+ done <<EOF
236
+ $harness|if (!isCapabilityContractEnabled()) return ALLOW;|runtime master-off bypass
237
+ $harness|const measured = snapshot.logging.enabled && isTrajectoryCaptureEnabled();|runtime measurement consent
238
+ $harness|if (!measured) return BLOCK;|runtime disabled unmeasured block
239
+ $harness|if (!measured) return ALLOW;|runtime enabled unmeasured pass-through
240
+ $harness|canonicalProjectBinding|runtime project binding
241
+ $harness|settleLease|runtime recoverable settlement
242
+ $store|CAPABILITY_CALLBACK_PENDING_PREFIX|pending issuance namespace
243
+ $store|inImmediateTransaction|atomic callback transaction
244
+ $store|sameIssuance|issued handle matching
245
+ $claude_start|UserPromptExpansion|Claude start event
246
+ $claude_start|FIXED_BLOCK|Claude fixed disabled block
247
+ $claude_finish|StopFailure|Claude immediate error event
248
+ $claude_finish|SessionEnd|Claude abandonment event
249
+ $opencode_plugin|version: "1.17.20"|OpenCode version pin
250
+ $opencode_plugin|admissionHook: "command.execute.before"|OpenCode admission hook
251
+ $opencode_plugin|error_terminal: "session.error"|OpenCode error terminal
252
+ $opencode_plugin|abandonment_only: "session.deleted"|OpenCode abandonment event
253
+ $claude_sync|CAPABILITY_OVERLAY_ACTIVE|Claude overlay installation gate
254
+ $opencode_sync|CAPABILITY_OVERLAY|OpenCode overlay installation gate
255
+ EOF
256
+
257
+ [[ "$failed" -eq 0 ]] && echo "[native-capability-docs] OK: lifecycle, consent, storage, overlays, owners, and evidence scope match native runtime"
258
+ return "$failed"
259
+ }
260
+
37
261
  # check_env_keys(): comm -23 of sorted code-keys vs sorted doc-keys --
38
262
  # code-keys-not-in-docs only (one-directional, per D-10).
39
263
  check_env_keys() {
40
264
  local code_keys doc_keys missing
41
265
 
42
- code_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" mcp-memory-server/src/*.ts | sort -u)
266
+ code_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" mcp-memory-server/src/*.ts | grep -v '^CAIRN_TEST_' | sort -u)
43
267
  doc_keys=$(grep -ohE "\\b${ENV_KEY_PATTERN}\\b" docs/operating.md README.md | sort -u)
44
268
 
45
269
  missing=$(comm -23 <(printf '%s\n' "$code_keys") <(printf '%s\n' "$doc_keys"))
@@ -83,7 +307,304 @@ check_commands() {
83
307
  return 0
84
308
  }
85
309
 
310
+ check_typed_contract() {
311
+ local failed=0 term files
312
+ while IFS='|' read -r term files; do
313
+ [[ -n "$term" ]] || continue
314
+ if ! grep -qF "$term" $files; then
315
+ echo "FATAL: typed-memory public term '$term' is missing from $files" >&2
316
+ failed=1
317
+ fi
318
+ done <<'EOF'
319
+ CAIRN_TYPED_MEMORY_NODES|README.md docs/operating.md
320
+ memory_import|README.md docs/operating.md docs/privacy-and-data-flow.md
321
+ address_space|README.md docs/operating.md docs/privacy-and-data-flow.md
322
+ node_types|README.md docs/operating.md
323
+ tags_all|README.md docs/operating.md
324
+ tags_any|README.md docs/operating.md
325
+ schemas/memory-node.schema.json|docs/storage.md
326
+ cairn_node_metadata_v1|docs/storage.md
327
+ cairn_node_import_replays_v1|docs/storage.md
328
+ prepared|docs/storage.md docs/privacy-and-data-flow.md
329
+ committing|docs/storage.md docs/privacy-and-data-flow.md
330
+ committed-before-cleanup|docs/storage.md docs/privacy-and-data-flow.md
331
+ EOF
332
+ [[ "$failed" -eq 0 ]] && echo "[typed-contract] OK: typed memory, import, and recovery terms are documented"
333
+ return "$failed"
334
+ }
335
+
336
+ check_artifact_contract() {
337
+ local failed=0 key value source_term
338
+
339
+ while IFS='|' read -r key value source_term; do
340
+ [[ -n "$key" ]] || continue
341
+ grep -qF "$key" mcp-memory-server/src/artifact-schema.ts || {
342
+ echo "FATAL: artifact env key '$key' is missing from source" >&2
343
+ failed=1
344
+ }
345
+ grep -qF "$source_term" mcp-memory-server/src/artifact-schema.ts || {
346
+ echo "FATAL: artifact default '$key=$value' is missing from source" >&2
347
+ failed=1
348
+ }
349
+ grep -qxF "# $key=$value" templates/env.example.template || {
350
+ echo "FATAL: artifact default '$key=$value' is missing from env.example" >&2
351
+ failed=1
352
+ }
353
+ grep -qF "$key" README.md docs/operating.md || {
354
+ echo "FATAL: artifact env key '$key' is missing from public configuration docs" >&2
355
+ failed=1
356
+ }
357
+ grep -qF "$value" README.md docs/operating.md || {
358
+ echo "FATAL: artifact default '$key=$value' is missing from public configuration docs" >&2
359
+ failed=1
360
+ }
361
+ done <<'EOF'
362
+ CAIRN_ARTIFACT_MAX_BYTES|1048576|ARTIFACT_DEFAULT_MAX_BYTES = 1024 * 1024
363
+ CAIRN_ARTIFACT_SESSION_MAX_BYTES|16777216|ARTIFACT_DEFAULT_SESSION_MAX_BYTES = 16 * 1024 * 1024
364
+ CAIRN_ARTIFACT_STORE_MAX_BYTES|268435456|ARTIFACT_DEFAULT_STORE_MAX_BYTES = 256 * 1024 * 1024
365
+ CAIRN_ARTIFACT_RETENTION_DAYS|30|ARTIFACT_DEFAULT_RETENTION_DAYS = 30
366
+ CAIRN_COMPACTION_MAX_REVISIONS|8|COMPACTION_DEFAULT_MAX_REVISIONS = 8
367
+ CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES|262144|GENERATED_FILE_MAX_SNAPSHOT_BYTES = 256 * 1024
368
+ EOF
369
+
370
+ for key in CAIRN_COMPACTION_CAPTURE CAIRN_ARTIFACT_STORE CAIRN_ARTIFACT_HTTP; do
371
+ grep -qF "$key" mcp-memory-server/src/artifact-schema.ts README.md docs/operating.md docs/privacy-and-data-flow.md || {
372
+ echo "FATAL: artifact feature flag '$key' is not source/docs complete" >&2
373
+ failed=1
374
+ }
375
+ done
376
+
377
+ for source_term in artifact_write artifact_read artifact_list artifact_delete; do
378
+ grep -qF "\"$source_term\"" mcp-memory-server/src/index.ts || failed=1
379
+ grep -qF "$source_term" README.md docs/operating.md docs/privacy-and-data-flow.md || failed=1
380
+ done
381
+ for source_term in 'list [--kind K] [--session REF] [--json]' 'show <artifact-id-or-prefix> [--json]' 'delete <artifact-id-or-prefix> [--dry-run] [--json]' 'prune [--dry-run] [--include-protected] [--json]'; do
382
+ grep -qF "$source_term" mcp-memory-server/src/artifact-cli.ts || failed=1
383
+ done
384
+ grep -qF '.agentfs/artifacts.db' docs/storage.md docs/privacy-and-data-flow.md || failed=1
385
+ grep -qF 'CAIRN_ARTIFACT_HTTP' mcp-memory-server/src/index.ts README.md docs/operating.md docs/privacy-and-data-flow.md || failed=1
386
+ grep -qE 'redact[^.]{0,180}(before|then)[^.]{0,180}(digest|index|write)' docs/privacy-and-data-flow.md || failed=1
387
+ grep -qE 'default uninstall retains|Default uninstall retains' docs/storage.md docs/privacy-and-data-flow.md || failed=1
388
+ grep -qF -- '--purge-memory PROJECT' docs/storage.md || failed=1
389
+ grep -qF 'revert.sh' docs/storage.md docs/privacy-and-data-flow.md || failed=1
390
+
391
+ for version in 2.1.219 2.1.220; do
392
+ grep -qF "version: \"$version\"" mcp-memory-server/src/compaction-normalize.ts || failed=1
393
+ grep -qF "$version" README.md docs/operating.md docs/privacy-and-data-flow.md || failed=1
394
+ done
395
+ grep -qF 'version: "1.17.20"' mcp-memory-server/src/compaction-normalize.ts || failed=1
396
+ grep -qF '1.17.20' README.md docs/operating.md docs/privacy-and-data-flow.md || failed=1
397
+
398
+ [[ "$failed" -eq 0 ]] && echo "[artifact-contract] OK: artifact flags/defaults/tools/paths/privacy/uninstall/version pins match source"
399
+ return "$failed"
400
+ }
401
+
402
+ check_artifact_remote_path_contract() {
403
+ local failed=0
404
+ local canonical_path='${CAIRN_AGENTFS_BASE_DIR}/<project-id>/.agentfs/artifacts.db'
405
+
406
+ grep -qF "$canonical_path" docs/storage.md || {
407
+ echo "FATAL: canonical remote artifact database path is missing from docs/storage.md" >&2
408
+ failed=1
409
+ }
410
+ grep -qF 'resolveRemoteArtifactProjectRoot' mcp-memory-server/src/artifact-store.ts || {
411
+ echo "FATAL: explicit remote artifact project-root helper is missing" >&2
412
+ failed=1
413
+ }
414
+ grep -qF 'resolveRemoteArtifactProjectRoot' mcp-memory-server/src/index.ts || {
415
+ echo "FATAL: explicit remote artifact project-root wiring is missing" >&2
416
+ failed=1
417
+ }
418
+ grep -qF 'getArtifactDbPath(resolveRemoteArtifactProjectRoot(baseDirectory, projectId))' mcp-memory-server/src/artifact-store.ts || {
419
+ echo "FATAL: remote artifact database resolver does not compose the canonical project-root helper" >&2
420
+ failed=1
421
+ }
422
+ grep -qF 'join("project-alpha", ".agentfs", "artifacts.db")' mcp-memory-server/scripts/smoke-artifact-mcp.mjs || {
423
+ echo "FATAL: HTTP smoke test does not assert the exact project-alpha artifact path" >&2
424
+ failed=1
425
+ }
426
+ grep -qF 'join("project-beta", ".agentfs", "artifacts.db")' mcp-memory-server/scripts/smoke-artifact-mcp.mjs || {
427
+ echo "FATAL: HTTP smoke test does not assert the exact project-beta artifact path" >&2
428
+ failed=1
429
+ }
430
+
431
+ [[ "$failed" -eq 0 ]] && echo "[artifact-remote-path] OK: source, HTTP behavior, and storage docs share the canonical path"
432
+ return "$failed"
433
+ }
434
+
435
+ check_capability_contract() {
436
+ local failed=0 file id env tool term source_file docs_files
437
+
438
+ for file in \
439
+ mcp-memory-server/src/capability-schema.ts \
440
+ mcp-memory-server/src/capability-registry.ts \
441
+ mcp-memory-server/src/capability-config.ts \
442
+ mcp-memory-server/src/capability-cli.ts \
443
+ mcp-memory-server/src/capability-store.ts \
444
+ mcp-memory-server/src/capability-adapter.ts \
445
+ schemas/capability-callback.schema.json \
446
+ docs/operating.md docs/storage.md docs/privacy-and-data-flow.md README.md; do
447
+ if [[ ! -f "$file" ]]; then
448
+ echo "FATAL: capability parity input is missing: $file" >&2
449
+ failed=1
450
+ fi
451
+ done
452
+ [[ "$failed" -eq 0 ]] || return "$failed"
453
+
454
+ while IFS='|' read -r id env; do
455
+ grep -qF "\"$id\"" mcp-memory-server/src/capability-schema.ts || {
456
+ echo "FATAL: canonical capability ID is missing from source: $id" >&2
457
+ failed=1
458
+ }
459
+ grep -qF "$id" README.md docs/operating.md || {
460
+ echo "FATAL: canonical capability ID is undocumented: $id" >&2
461
+ failed=1
462
+ }
463
+ grep -qF "$env" README.md docs/operating.md templates/env.example.template || {
464
+ echo "FATAL: capability environment key is not source/docs complete: $env" >&2
465
+ failed=1
466
+ }
467
+ done <<'EOF'
468
+ memory.write|CAIRN_CAPABILITY_MEMORY_WRITE
469
+ memory.search|CAIRN_CAPABILITY_MEMORY_SEARCH
470
+ notes.distill|CAIRN_CAPABILITY_NOTES_DISTILL
471
+ wiki|CAIRN_CAPABILITY_WIKI
472
+ graph|CAIRN_CAPABILITY_GRAPH
473
+ security.audit|CAIRN_CAPABILITY_SECURITY_AUDIT
474
+ route.check|CAIRN_CAPABILITY_ROUTE_CHECK
475
+ context.explore|CAIRN_CAPABILITY_CONTEXT_EXPLORE
476
+ EOF
477
+
478
+ for term in CAIRN_CAPABILITY_CONTRACT CAIRN_CAPABILITY_LOGGING; do
479
+ grep -qF "$term" mcp-memory-server/src/capability-config.ts README.md docs/operating.md templates/env.example.template || {
480
+ echo "FATAL: capability master/logging flag is not source/docs complete: $term" >&2
481
+ failed=1
482
+ }
483
+ done
484
+
485
+ while IFS='|' read -r id tool; do
486
+ grep -qF "\"$id\"" mcp-memory-server/src/index.ts || failed=1
487
+ grep -qF "\"$tool\"" mcp-memory-server/src/index.ts || failed=1
488
+ grep -qF "$tool" README.md docs/operating.md || {
489
+ echo "FATAL: omitted MCP tool is undocumented: $tool" >&2
490
+ failed=1
491
+ }
492
+ done <<'EOF'
493
+ memory.write|memory_write
494
+ memory.search|memory_search
495
+ route.check|route_check
496
+ context.explore|context_explore
497
+ EOF
498
+
499
+ while IFS='|' read -r term source_file doc_term docs_files; do
500
+ grep -qF "$term" "$source_file" || {
501
+ echo "FATAL: capability source term is missing: $term ($source_file)" >&2
502
+ failed=1
503
+ }
504
+ grep -qF "$doc_term" $docs_files || {
505
+ echo "FATAL: capability source fact is undocumented: $doc_term ($docs_files)" >&2
506
+ failed=1
507
+ }
508
+ done <<'EOF'
509
+ capability-callback/v1/record/|mcp-memory-server/src/capability-store.ts|capability-callback/v1|docs/storage.md docs/privacy-and-data-flow.md
510
+ capability-callback/v1/pending/|mcp-memory-server/src/capability-store.ts|capability-callback/v1/pending/|docs/storage.md docs/privacy-and-data-flow.md
511
+ .agentfs/trajectory.db|mcp-memory-server/src/capability-cli.ts|.agentfs/trajectory.db|docs/storage.md docs/privacy-and-data-flow.md
512
+ CAPABILITY_CALLBACK_RECORD_MAX_COUNT = 10_000|mcp-memory-server/src/capability-store.ts|10,000-record cap|docs/storage.md
513
+ getTrajectoryLimits|mcp-memory-server/src/capability-store.ts|CAIRN_TRAJECTORY_RETENTION_DAYS|docs/storage.md
514
+ transport !== "http"|mcp-memory-server/src/capability-adapter.ts|no remote/HTTP callback persistence|docs/privacy-and-data-flow.md
515
+ isTrajectoryCaptureEnabled|mcp-memory-server/src/capability-adapter.ts|CAIRN_TRAJECTORY_CAPTURE|docs/privacy-and-data-flow.md
516
+ configuration_digest|mcp-memory-server/src/capability-store.ts|configuration_digest|docs/operating.md docs/privacy-and-data-flow.md
517
+ correlation_id|mcp-memory-server/src/capability-store.ts|correlation_id|docs/privacy-and-data-flow.md
518
+ invocation_id|mcp-memory-server/src/capability-store.ts|invocation_id|docs/privacy-and-data-flow.md
519
+ duration_ms|mcp-memory-server/src/capability-store.ts|duration_ms|docs/privacy-and-data-flow.md
520
+ issueOperatingCapability|mcp-memory-server/src/capability-adapter.ts|durably issue|docs/privacy-and-data-flow.md
521
+ settleOperatingCapability|mcp-memory-server/src/capability-adapter.ts|consumed once|docs/storage.md docs/privacy-and-data-flow.md
522
+ isCapabilityContractEnabled()|mcp-memory-server/src/capability-adapter.ts|checked again at finish|docs/privacy-and-data-flow.md
523
+ const current = await resolveCapabilityStatus({ projectRoot });|mcp-memory-server/src/capability-adapter.ts|current three-consent authorization|docs/privacy-and-data-flow.md
524
+ current.logging.enabled|mcp-memory-server/src/capability-adapter.ts|managed logging|docs/privacy-and-data-flow.md
525
+ current.configuration_digest === handle.configuration_digest|mcp-memory-server/src/capability-adapter.ts|stale|docs/privacy-and-data-flow.md
526
+ EOF
527
+
528
+ local expected_final_fields source_final_fields public_final_fields
529
+ expected_final_fields=$(printf '%s\n' \
530
+ capability_id configuration_digest correlation_id duration_ms error_code \
531
+ finished_at harness invocation_id outcome schema_version source started_at \
532
+ state_source transport | sort)
533
+ source_final_fields=$(sed -n \
534
+ '/export const capabilityCallbackRecordSchema/,/}).superRefine/p' \
535
+ mcp-memory-server/src/capability-store.ts \
536
+ | sed -n 's/^ \([a-z_]*\):.*/\1/p' \
537
+ | sort)
538
+ public_final_fields=$(node -e '
539
+ const schema = require("./schemas/capability-callback.schema.json");
540
+ process.stdout.write(Object.keys(schema.properties).sort().join("\n"));
541
+ ')
542
+ if [[ "$source_final_fields" != "$expected_final_fields" ]]; then
543
+ echo "FATAL: runtime final-record fields changed from the strict allow-list" >&2
544
+ failed=1
545
+ fi
546
+ if [[ "$public_final_fields" != "$expected_final_fields" ]]; then
547
+ echo "FATAL: public final-record fields changed from the strict allow-list" >&2
548
+ failed=1
549
+ fi
550
+
551
+ for term in \
552
+ 'capabilities list' 'capabilities status' 'capabilities enable' \
553
+ 'capabilities disable' 'capabilities reset' 'capabilities logging'; do
554
+ grep -qF "$term" mcp-memory-server/src/capability-cli.ts docs/operating.md || {
555
+ echo "FATAL: managed capability CLI operation is not source/docs complete: $term" >&2
556
+ failed=1
557
+ }
558
+ done
559
+
560
+ for term in \
561
+ 'three consents' 'payload-free' 'no start record' 'fail-open' \
562
+ 'no-payload, no-telemetry, no-network' \
563
+ 'no telemetry' 'HTTP callbacks are never persisted' \
564
+ 'Default uninstall retains' '--purge-memory PROJECT' 'revert.sh' \
565
+ 'no database migration'; do
566
+ grep -qiF -- "$term" docs/storage.md docs/privacy-and-data-flow.md || {
567
+ echo "FATAL: capability privacy/storage term is missing: $term" >&2
568
+ failed=1
569
+ }
570
+ done
571
+
572
+ for term in 'wiki covers ingest, query, and lint' 'graph covers its command family' \
573
+ 'audit covers its command and workflow family' 'five operating command surfaces' \
574
+ 'Master off is exact'; do
575
+ grep -qiF "$term" docs/operating.md || {
576
+ echo "FATAL: capability operating-guard family is undocumented: $term" >&2
577
+ failed=1
578
+ }
579
+ done
580
+
581
+ [[ "$failed" -eq 0 ]] && echo "[capability-contract] OK: IDs, env, defaults, omissions, CLI, guards, storage, consent, and uninstall facts match source"
582
+ return "$failed"
583
+ }
584
+
86
585
  main() {
586
+ local mode="${1:-}"
587
+ if [[ "$mode" == "--artifact-remote-path-only" ]]; then
588
+ [[ $# -eq 1 ]] || { usage >&2; exit 2; }
589
+ check_artifact_remote_path_contract
590
+ return
591
+ fi
592
+ if [[ "$mode" == "--expect-red-artifact-remote-path" ]]; then
593
+ [[ $# -eq 1 ]] || { usage >&2; exit 2; }
594
+ main
595
+ if check_artifact_remote_path_contract; then
596
+ echo "FATAL: artifact remote path documentation unexpectedly matches source" >&2
597
+ exit 1
598
+ fi
599
+ echo "$ARTIFACT_REMOTE_PATH_RED_MARKER"
600
+ exit "$EXPECTED_RED_EXIT"
601
+ fi
602
+ if [[ "$mode" == "--self-test-native-capability-docs" ]]; then
603
+ [[ $# -eq 1 ]] || { usage >&2; exit 2; }
604
+ run_native_capability_docs_self_test
605
+ return
606
+ fi
607
+
87
608
  while [[ $# -gt 0 ]]; do
88
609
  case "$1" in
89
610
  -h|--help)
@@ -100,6 +621,11 @@ main() {
100
621
  local failed=0
101
622
  check_env_keys || failed=1
102
623
  check_commands || failed=1
624
+ check_typed_contract || failed=1
625
+ check_artifact_contract || failed=1
626
+ check_artifact_remote_path_contract || failed=1
627
+ check_capability_contract || failed=1
628
+ check_native_capability_docs || failed=1
103
629
 
104
630
  if [[ "$failed" -ne 0 ]]; then
105
631
  echo "FATAL: docs-parity check found drift (see above) -- SC-02 not yet satisfied" >&2