blun-king-cli 9.1.567 → 9.1.569

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 (262) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  4. package/agent-spine-plugin/CHANGELOG.md +1581 -0
  5. package/agent-spine-plugin/README.md +30 -4
  6. package/agent-spine-plugin/blun.plugin.json +3 -3
  7. package/agent-spine-plugin/docs/acceptance.md +61 -0
  8. package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
  9. package/agent-spine-plugin/docs/host-integration.md +178 -0
  10. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  11. package/agent-spine-plugin/docs/preservation-contract.md +53 -0
  12. package/agent-spine-plugin/docs/quality-gates.md +50 -0
  13. package/agent-spine-plugin/docs/releasing.md +85 -0
  14. package/agent-spine-plugin/docs/session-timeline.md +251 -0
  15. package/agent-spine-plugin/docs/source-roots.md +113 -0
  16. package/agent-spine-plugin/docs/structured-completion.md +67 -0
  17. package/agent-spine-plugin/docs/world-model.md +94 -0
  18. package/agent-spine-plugin/hooks/codex.json +2 -2
  19. package/agent-spine-plugin/hooks/hooks.json +1 -1
  20. package/agent-spine-plugin/hooks/version.json +1 -1
  21. package/agent-spine-plugin/package.json +13 -3
  22. package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
  23. package/agent-spine-plugin/scripts/check-hosts.js +14 -5
  24. package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
  25. package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
  26. package/agent-spine-plugin/scripts/check-install.js +478 -0
  27. package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
  28. package/agent-spine-plugin/scripts/check-syntax.js +29 -0
  29. package/agent-spine-plugin/scripts/github-actions.js +11 -0
  30. package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
  31. package/agent-spine-plugin/scripts/release-check.js +145 -0
  32. package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
  33. package/agent-spine-plugin/scripts/run-checks.js +47 -0
  34. package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
  35. package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
  36. package/agent-spine-plugin/spine-example/1-identity.md +12 -0
  37. package/agent-spine-plugin/spine-example/2-voice.md +6 -0
  38. package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
  39. package/agent-spine-plugin/spine-example/4-history.md +4 -0
  40. package/agent-spine-plugin/src/cli-agent.js +296 -0
  41. package/agent-spine-plugin/src/cli-attention.js +95 -0
  42. package/agent-spine-plugin/src/cli-autonomy.js +36 -0
  43. package/agent-spine-plugin/src/cli-common.js +71 -0
  44. package/agent-spine-plugin/src/cli-continuity.js +116 -0
  45. package/agent-spine-plugin/src/cli-core.js +128 -0
  46. package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
  47. package/agent-spine-plugin/src/cli-host.js +21 -0
  48. package/agent-spine-plugin/src/cli-learning.js +305 -0
  49. package/agent-spine-plugin/src/cli-premortem.js +16 -0
  50. package/agent-spine-plugin/src/cli-sharing.js +230 -0
  51. package/agent-spine-plugin/src/cli.js +40 -1350
  52. package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
  53. package/agent-spine-plugin/src/hook.js +185 -567
  54. package/agent-spine-plugin/src/index.js +16 -2
  55. package/agent-spine-plugin/src/lib/acceptance.js +113 -11
  56. package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
  57. package/agent-spine-plugin/src/lib/attention-context.js +167 -0
  58. package/agent-spine-plugin/src/lib/attention-events.js +113 -0
  59. package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
  60. package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
  61. package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
  62. package/agent-spine-plugin/src/lib/attention.js +9 -600
  63. package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
  64. package/agent-spine-plugin/src/lib/audit.js +56 -6
  65. package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
  66. package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
  67. package/agent-spine-plugin/src/lib/autonomy.js +8 -0
  68. package/agent-spine-plugin/src/lib/briefing.js +67 -4
  69. package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
  70. package/agent-spine-plugin/src/lib/catalog.js +1 -1
  71. package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
  72. package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
  73. package/agent-spine-plugin/src/lib/context.js +3 -1
  74. package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
  75. package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
  76. package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
  77. package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
  78. package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
  79. package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
  80. package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
  81. package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
  82. package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
  83. package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
  84. package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
  85. package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
  86. package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
  87. package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
  88. package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
  89. package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
  90. package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
  91. package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
  92. package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
  93. package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
  94. package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
  95. package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
  96. package/agent-spine-plugin/src/lib/documents.js +27 -5
  97. package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
  98. package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
  99. package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
  100. package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
  101. package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
  102. package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
  103. package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
  104. package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
  105. package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
  106. package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
  107. package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
  108. package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
  109. package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
  110. package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
  111. package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
  112. package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
  113. package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
  114. package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
  115. package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
  116. package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
  117. package/agent-spine-plugin/src/lib/hook-context.js +424 -0
  118. package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
  119. package/agent-spine-plugin/src/lib/hook-input.js +27 -0
  120. package/agent-spine-plugin/src/lib/hook-output.js +140 -0
  121. package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
  122. package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
  123. package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
  124. package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
  125. package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
  126. package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
  127. package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
  128. package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
  129. package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
  130. package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
  131. package/agent-spine-plugin/src/lib/learning-context.js +130 -0
  132. package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
  133. package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
  134. package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
  135. package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
  136. package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
  137. package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
  138. package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
  139. package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
  140. package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
  141. package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
  142. package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
  143. package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
  144. package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
  145. package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
  146. package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
  147. package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
  148. package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
  149. package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
  150. package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
  151. package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
  152. package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
  153. package/agent-spine-plugin/src/lib/learning.js +36 -6923
  154. package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
  155. package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
  156. package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
  157. package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
  158. package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
  159. package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
  160. package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
  161. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
  162. package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
  163. package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
  164. package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
  165. package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
  166. package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
  167. package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
  168. package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
  169. package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
  170. package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
  171. package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
  172. package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
  173. package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
  174. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
  175. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
  176. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
  177. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
  178. package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
  179. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
  180. package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
  181. package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
  182. package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
  183. package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
  184. package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
  185. package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
  186. package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
  187. package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
  188. package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
  189. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
  190. package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
  191. package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
  192. package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
  193. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
  194. package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
  195. package/agent-spine-plugin/src/lib/source-roots.js +69 -136
  196. package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
  197. package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
  198. package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
  199. package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
  200. package/agent-spine-plugin/src/lib/world-model.js +278 -0
  201. package/agent-spine-plugin/src/mcp.js +20 -161
  202. package/agent-spine-plugin/src/version.js +1 -1
  203. package/agent-spine-plugin/src/worker.js +22 -5
  204. package/bin/agent-resume-snapshot.cjs +2 -2
  205. package/bin/agentspine-king-goal-inbox.mjs +111 -0
  206. package/bin/agentspine-king-goal-intake.mjs +106 -0
  207. package/bin/baseline-skill-performance-policy.cjs +1 -16
  208. package/bin/core-bootstrap.js +2 -0
  209. package/bin/curiosity-scout-policy.cjs +5 -1
  210. package/bin/input-draft-persistence.cjs +2 -2
  211. package/bin/king-tui-function-contract.json +33 -0
  212. package/bin/launcher-restart-policy.cjs +150 -0
  213. package/bin/launcher-runtime.js +56 -44
  214. package/bin/managed-context-startup-policy.cjs +27 -0
  215. package/bin/managed-plugin-selection.cjs +116 -0
  216. package/bin/mistake-relevance-policy.cjs +1 -1
  217. package/bin/observer-hooks.cjs +14 -0
  218. package/bin/oversized-context-offload-policy.cjs +86 -0
  219. package/bin/plugin-bootstrap.js +7 -40
  220. package/bin/proactive-compaction-policy.cjs +1 -1
  221. package/bin/provider-model-refresh-deadline.cjs +53 -0
  222. package/bin/provider-model-refresh-policy.cjs +107 -0
  223. package/bin/release-artifact-freeze-policy.cjs +30 -0
  224. package/bin/repeated-user-message-projection.cjs +3 -126
  225. package/bin/research-page-result.cjs +74 -0
  226. package/bin/runtime-exit-ledger.cjs +1 -0
  227. package/bin/session-compaction-policy.cjs +84 -0
  228. package/bin/skill-listing-performance-policy.cjs +2 -2
  229. package/bin/standard-tools-bootstrap.js +0 -37
  230. package/bin/subagent-skill-policy.cjs +3 -1
  231. package/bin/telegram-approval-relay.cjs +12 -7
  232. package/bin/telegram-private-conversation-policy.cjs +3 -2
  233. package/bin/telegram-queue-handoff-policy.cjs +24 -0
  234. package/bin/thinking-activity-status-policy.cjs +1 -1
  235. package/bin/thinking-only-guard.cjs +16 -12
  236. package/bin/tool-call-loop-policy.cjs +0 -2
  237. package/bin/tool-result-offload-policy.cjs +11 -2
  238. package/bin/tui-functional-contract.cjs +55 -0
  239. package/bin/update-notice.js +18 -14
  240. package/bin/user-message-offload-policy.cjs +1 -1
  241. package/bin/user-prompt-hook-origin-policy.cjs +34 -0
  242. package/bin/windows-node-crash-dump.cjs +110 -0
  243. package/blun.mjs +2517 -1048
  244. package/codebase-index/codebase_index.py +4 -3
  245. package/package.json +3 -17
  246. package/standard-skills/research-evidence/SKILL.md +39 -0
  247. package/standard-skills/research-evidence/references/evidence-format.md +82 -0
  248. package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
  249. package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
  250. package/standard-skills/web-lesen/SKILL.md +37 -22
  251. package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
  252. package/telegram-plugin/DELIVERY.md +36 -0
  253. package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
  254. package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
  255. package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
  256. package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
  257. package/telegram-plugin/dist/bridge.mjs +7 -56
  258. package/telegram-plugin/dist/mcp-server.mjs +33 -4
  259. package/agent-spine-plugin/skill/SKILL.md +0 -76
  260. package/bin/mnemo-connect-heartbeat.cjs +0 -204
  261. package/bin/mnemo-tool-agent-policy.cjs +0 -22
  262. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
@@ -0,0 +1,165 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ OUTCOME_PHASES, MEASUREMENT_KINDS, METRIC_DIRECTIONS, ID_RE, DIGEST_RE, COVERAGE_EVALUATIONS,
4
+ LINEAGE_EVALUATIONS, ROOT_BOUND_EVALUATIONS, INITIAL_TRIAL_EVALUATIONS, TARGET_BOUND_EVALUATIONS, INITIAL_TRIAL_APPLICATIONS, TARGET_BOUND_APPLICATIONS,
5
+ DEADLINE_BOUND_APPLICATIONS
6
+ } from "./learning-schema.js";
7
+ import {
8
+ normalizeScope, scopeContains, exactScope, digest
9
+ } from "./learning-scope-targets.js";
10
+ import {
11
+ date, number, integer, safeText
12
+ } from "./learning-storage.js";
13
+
14
+ export function outcomePayload({ schema = "agentspine.learning-outcome/v1", id, learningId, phase, scope, metric, measurement,
15
+ applicationId, deliveryId, evaluationId, coverage, measurementReceiptId, measurementReceiptDigest, measuredAt }) {
16
+ return {
17
+ schema,
18
+ id,
19
+ learningId,
20
+ phase,
21
+ scope,
22
+ metric,
23
+ measurement,
24
+ ...(["agentspine.learning-outcome/v2", "agentspine.learning-outcome/v3", "agentspine.learning-outcome/v4", "agentspine.learning-outcome/v5", "agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(schema) ? { applicationId } : {}),
25
+ ...(["agentspine.learning-outcome/v3", "agentspine.learning-outcome/v4", "agentspine.learning-outcome/v5", "agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(schema) ? { evaluationId } : {}),
26
+ ...(["agentspine.learning-outcome/v4", "agentspine.learning-outcome/v5", "agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(schema) ? { deliveryId } : {}),
27
+ ...(["agentspine.learning-outcome/v5", "agentspine.learning-outcome/v6", "agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(schema) ? { coverage } : {}),
28
+ ...(["agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(schema) ? { measurementReceiptId, measurementReceiptDigest } : {}),
29
+ measuredAt,
30
+ authority: "context-only"
31
+ };
32
+ }
33
+
34
+ export function normalizeOutcome(input, candidate, timestamp, application = null, delivery = null, evaluation = null,
35
+ measurementReceipt = null) {
36
+ const id = input.id || `outcome:${randomUUID()}`;
37
+ if (!ID_RE.test(id)) throw new Error("outcome.id must be a stable, whitespace-free identifier");
38
+ const lineageRequired = LINEAGE_EVALUATIONS.has(evaluation?.schema);
39
+ if (lineageRequired && (!measurementReceipt || measurementReceipt.evaluationId !== evaluation.id
40
+ || measurementReceipt.learningId !== candidate.id)) {
41
+ throw new Error("outcomes require one matching immutable measurement receipt");
42
+ }
43
+ if (lineageRequired && input.phase !== undefined && input.phase !== measurementReceipt.phase) {
44
+ throw new Error("outcome phase conflicts with its measurement receipt");
45
+ }
46
+ if (lineageRequired && input.scope !== undefined && !exactScope(normalizeScope(input.scope), measurementReceipt.scope)) {
47
+ throw new Error("outcome scope conflicts with its measurement receipt");
48
+ }
49
+ if (lineageRequired && input.metric !== undefined && digest(input.metric) !== digest(measurementReceipt.metric)) {
50
+ throw new Error("outcome metric conflicts with its measurement receipt");
51
+ }
52
+ if (lineageRequired && input.measurement !== undefined && digest(input.measurement) !== digest(measurementReceipt.measurement)) {
53
+ throw new Error("outcome measurement conflicts with its measurement receipt");
54
+ }
55
+ if (lineageRequired && input.coverage !== undefined && input.coverage !== null
56
+ && digest(input.coverage) !== digest(measurementReceipt.coverage)) {
57
+ throw new Error("outcome coverage conflicts with its measurement receipt");
58
+ }
59
+ const phase = lineageRequired ? measurementReceipt.phase : input.phase;
60
+ if (!OUTCOME_PHASES.has(phase)) throw new Error("outcome.phase must be before or after");
61
+ const scope = lineageRequired ? measurementReceipt.scope : normalizeScope(input.scope);
62
+ if (!scopeContains(candidate.scope, scope)) throw new Error("outcome scope does not match the learning candidate");
63
+ if (!evaluation || evaluation.learningId !== candidate.id || !exactScope(evaluation.scope, scope)) {
64
+ throw new Error("outcomes require a matching immutable evaluation contract");
65
+ }
66
+ const name = lineageRequired ? measurementReceipt.metric.name : safeText(input.metric?.name, "outcome.metric.name", 120);
67
+ const direction = lineageRequired ? measurementReceipt.metric.direction : input.metric?.direction;
68
+ if (!METRIC_DIRECTIONS.has(direction)) throw new Error("outcome.metric.direction must be higher or lower");
69
+ if (name !== evaluation.metric.name || direction !== evaluation.metric.direction) {
70
+ throw new Error("outcome metric does not match the evaluation contract");
71
+ }
72
+ const metric = lineageRequired ? measurementReceipt.metric : {
73
+ name,
74
+ direction,
75
+ value: number(input.metric?.value, "outcome.metric.value", 0, 1),
76
+ blockingDefects: integer(input.metric?.blockingDefects ?? 0, "outcome.metric.blockingDefects", 0, 1000)
77
+ };
78
+ const kind = lineageRequired ? measurementReceipt.measurement.kind : input.measurement?.kind;
79
+ if (!MEASUREMENT_KINDS.has(kind)) throw new Error("outcome.measurement.kind is unsupported");
80
+ const evaluatorId = lineageRequired ? measurementReceipt.measurement.evaluatorId : input.measurement?.evaluatorId;
81
+ if (!ID_RE.test(evaluatorId || "")) throw new Error("outcome.measurement.evaluatorId is required");
82
+ if (!evaluation.evaluatorIds.includes(evaluatorId)) throw new Error("outcome evaluator is not allowed by the evaluation contract");
83
+ const evaluatorRoot = ROOT_BOUND_EVALUATIONS.has(evaluation.schema)
84
+ ? evaluation.evaluatorRoots.find((root) => root.evaluatorId === evaluatorId) : null;
85
+ if (ROOT_BOUND_EVALUATIONS.has(evaluation.schema)
86
+ && (measurementReceipt.schema !== "agentspine.learning-measurement/v2"
87
+ || evaluatorRoot?.principalDigest !== measurementReceipt.measurement.evaluatorRootDigest)) {
88
+ throw new Error("outcome evaluator root does not match the frozen evaluation contract");
89
+ }
90
+ const sourceDigest = lineageRequired ? measurementReceipt.measurement.sourceDigest : input.measurement?.sourceDigest ?? null;
91
+ if (sourceDigest !== null && !/^[a-f0-9]{64}$/.test(sourceDigest)) {
92
+ throw new Error("outcome.measurement.sourceDigest must be a SHA-256 digest");
93
+ }
94
+ const measurement = lineageRequired ? measurementReceipt.measurement : { kind, evaluatorId, sourceDigest, authority: "context-only" };
95
+ const provenanceRequired = evaluation.schema === "agentspine.learning-evaluation/v3";
96
+ if (provenanceRequired && !DIGEST_RE.test(sourceDigest || "")) {
97
+ throw new Error("outcome.measurement.sourceDigest is required by the evaluation contract");
98
+ }
99
+ const coverageRequired = COVERAGE_EVALUATIONS.has(evaluation.schema);
100
+ const coverage = lineageRequired ? measurementReceipt.coverage : coverageRequired ? {
101
+ datasetDigest: input.coverage?.datasetDigest,
102
+ caseCount: integer(input.coverage?.caseCount, "outcome.coverage.caseCount", 1, 1000000),
103
+ authority: "context-only"
104
+ } : null;
105
+ if (coverageRequired && coverage.datasetDigest !== evaluation.benchmark.datasetDigest) {
106
+ throw new Error("outcome coverage dataset does not match the evaluation contract");
107
+ }
108
+ if (coverageRequired && coverage.caseCount < evaluation.benchmark.minCases) {
109
+ throw new Error(`outcome coverage requires at least ${evaluation.benchmark.minCases} cases`);
110
+ }
111
+ const measuredAt = lineageRequired ? measurementReceipt.measuredAt : date(input.measuredAt || timestamp, "outcome.measuredAt");
112
+ if (new Date(measuredAt).getTime() < new Date(evaluation.registeredAt).getTime()
113
+ || new Date(measuredAt).getTime() > new Date(evaluation.expiresAt).getTime()) {
114
+ throw new Error("outcome is outside its evaluation contract window");
115
+ }
116
+ const applicationId = phase === "after" ? input.applicationId : null;
117
+ const deliveryId = phase === "after" ? input.deliveryId : null;
118
+ if (phase === "after") {
119
+ if (!ID_RE.test(applicationId || "") || !application) throw new Error("after outcomes require a recorded learning application receipt");
120
+ if (application.learningId !== candidate.id || !exactScope(application.scope, scope)) {
121
+ throw new Error("learning application scope does not match the after outcome");
122
+ }
123
+ if (new Date(measuredAt).getTime() < new Date(application.projectedAt).getTime()
124
+ || new Date(measuredAt).getTime() > new Date(application.expiresAt).getTime()) {
125
+ throw new Error("after outcome is outside its learning application window");
126
+ }
127
+ if (DEADLINE_BOUND_APPLICATIONS.has(application.schema)
128
+ && new Date(measuredAt).getTime() > new Date(application.outcomeExpiresAt).getTime()) {
129
+ throw new Error("after outcome missed its immutable initial trial outcome deadline");
130
+ }
131
+ if (!ID_RE.test(deliveryId || "") || !delivery || delivery.applicationId !== application.id
132
+ || delivery.learningId !== candidate.id || !exactScope(delivery.scope, scope)) {
133
+ throw new Error("after outcomes require the matching completed model-turn delivery receipt");
134
+ }
135
+ if (new Date(measuredAt).getTime() < new Date(delivery.completedAt).getTime()) {
136
+ throw new Error("after outcome predates the completed model-turn delivery");
137
+ }
138
+ if (INITIAL_TRIAL_EVALUATIONS.has(evaluation.schema)
139
+ && application.schema !== "agentspine.learning-application/v4") {
140
+ const trial = evaluation.initialTrials.after.find((entry) =>
141
+ entry.evaluatorId === measurementReceipt.measurement.evaluatorId);
142
+ if (!trial || !INITIAL_TRIAL_APPLICATIONS.has(application.schema)
143
+ || application.initialAdmission.evaluationId !== evaluation.id
144
+ || application.initialAdmission.evaluationDigest !== evaluation.digest
145
+ || application.initialAdmission.slot !== trial.slot
146
+ || application.initialAdmission.trialDigest !== trial.trialDigest
147
+ || (TARGET_BOUND_EVALUATIONS.has(evaluation.schema)
148
+ && (!TARGET_BOUND_APPLICATIONS.has(application.schema)
149
+ || application.initialAdmission.targetDigest !== evaluation.target.digest))) {
150
+ throw new Error("after outcome does not match its precommitted first-admitted trial");
151
+ }
152
+ }
153
+ }
154
+ const payload = outcomePayload({ schema: ROOT_BOUND_EVALUATIONS.has(evaluation.schema)
155
+ ? "agentspine.learning-outcome/v9" : evaluation.schema === "agentspine.learning-evaluation/v5"
156
+ ? "agentspine.learning-outcome/v8" : lineageRequired ? "agentspine.learning-outcome/v7"
157
+ : provenanceRequired ? "agentspine.learning-outcome/v6"
158
+ : coverageRequired ? "agentspine.learning-outcome/v5" : "agentspine.learning-outcome/v4",
159
+ id, learningId: candidate.id, phase, scope, metric, measurement, applicationId, deliveryId,
160
+ evaluationId: evaluation.id, coverage,
161
+ measurementReceiptId: lineageRequired ? measurementReceipt.id : undefined,
162
+ measurementReceiptDigest: lineageRequired ? measurementReceipt.digest : undefined,
163
+ measuredAt });
164
+ return { ...payload, digest: digest(payload) };
165
+ }
@@ -0,0 +1,343 @@
1
+ import {
2
+ AUTO_KINDS, OUTCOME_AUTO_KINDS, CONTINUITY_AUTO_KINDS, SCOPE_FIELDS, ID_RE, COVERAGE_EVALUATIONS,
3
+ LINEAGE_EVALUATIONS, PAIRED_EVALUATIONS, ROOT_BOUND_EVALUATIONS, REGISTRY_BOUND_EVALUATIONS, INITIAL_TRIAL_EVALUATIONS, TARGET_BOUND_EVALUATIONS,
4
+ DEADLINE_BOUND_EVALUATIONS, STALENESS_BOUND_EVALUATIONS, DEADLINE_BOUND_APPLICATIONS
5
+ } from "./learning-schema.js";
6
+ import {
7
+ digest, revokedEvidence, revokedMeasurement, revokedMeasurementForCandidate, activeEvaluationBinding, revokedEvaluation
8
+ } from "./learning-scope-targets.js";
9
+ import {
10
+ evaluationStalenessPolicy, revokedEvidenceSourceAttestation
11
+ } from "./learning-measurement-contracts.js";
12
+ import {
13
+ revokedApplication, revokedApplicationForCandidate, revokedDelivery, revokedDeliveryForCandidate, revokedOutcome, revokedOutcomeForCandidate
14
+ } from "./learning-delivery-contracts.js";
15
+ import {
16
+ date, number, integer, safeText, mutation
17
+ } from "./learning-storage.js";
18
+ import {
19
+ acceptCandidate, distinctEvidence
20
+ } from "./learning-candidates.js";
21
+ import {
22
+ normalizeOutcome
23
+ } from "./learning-outcome-contracts.js";
24
+ import {
25
+ promotableReceipts, rollbackCandidate
26
+ } from "./learning-reconciliation.js";
27
+ import {
28
+ reconcileCanary
29
+ } from "./learning-trial-recovery.js";
30
+
31
+ export async function recordLearningOutcome({ root = process.cwd(), id, learningId, phase, scope, metric, measurement,
32
+ applicationId = null, deliveryId = null, evaluationId, measurementReceiptId = null,
33
+ coverage = null, measuredAt, now = new Date() }) {
34
+ if (!ID_RE.test(learningId || "")) throw new Error("learningId is required");
35
+ const timestamp = date(now, "now");
36
+ return mutation(root, (state, _catalog, learningPath) => {
37
+ const candidate = state.candidates.find((entry) => entry.id === learningId);
38
+ if (!candidate) throw new Error(`unknown learning candidate: ${learningId}`);
39
+ if (!ID_RE.test(evaluationId || "")) throw new Error("evaluationId is required");
40
+ const evaluation = state.evaluations.find((item) => item.id === evaluationId);
41
+ if (revokedEvaluation(state, evaluationId)) {
42
+ throw new Error("learning evaluation contract was explicitly revoked and cannot support an outcome");
43
+ }
44
+ if (revokedEvidenceSourceAttestation(state, evaluationId)) {
45
+ throw new Error("learning evidence source attestation was explicitly revoked and cannot support an outcome");
46
+ }
47
+ if (REGISTRY_BOUND_EVALUATIONS.has(evaluation?.schema)
48
+ && !activeEvaluationBinding(state, evaluation)) {
49
+ throw new Error("outcome evaluator registry binding is missing, changed, or revoked");
50
+ }
51
+ const measurementReceipt = measurementReceiptId === null ? null
52
+ : state.measurements.find((item) => item.id === measurementReceiptId);
53
+ if (measurementReceipt && revokedMeasurement(state, measurementReceipt.id)) {
54
+ throw new Error("learning measurement was explicitly revoked and cannot be consumed");
55
+ }
56
+ const effectivePhase = LINEAGE_EVALUATIONS.has(evaluation?.schema)
57
+ ? measurementReceipt?.phase : phase;
58
+ const application = applicationId === null ? null : state.applications.find((item) => item.id === applicationId);
59
+ const delivery = deliveryId === null ? null : state.deliveries.find((item) => item.id === deliveryId);
60
+ if (application && revokedApplication(state, application.id)) {
61
+ throw new Error("learning application was explicitly revoked and cannot support an outcome");
62
+ }
63
+ if (delivery && revokedDelivery(state, delivery.id)) {
64
+ throw new Error("learning delivery was explicitly revoked and cannot support an outcome");
65
+ }
66
+ const existing = id ? state.outcomes.find((item) => item.id === id) : null;
67
+ if (existing) {
68
+ if (revokedOutcome(state, existing.id)) {
69
+ throw new Error("learning outcome was explicitly revoked and cannot be replayed");
70
+ }
71
+ const retry = normalizeOutcome({ id, phase, scope, metric, measurement, applicationId, deliveryId, evaluationId, coverage,
72
+ measuredAt: measuredAt ?? existing.measuredAt }, candidate, timestamp, application, delivery, evaluation, measurementReceipt);
73
+ if (existing.digest === retry.digest) {
74
+ return { receipt: existing, candidate, decision: "unchanged", learningPath, unchanged: true };
75
+ }
76
+ throw new Error("outcome receipt IDs are immutable");
77
+ }
78
+ if (effectivePhase === "before" && candidate.status !== "candidate") throw new Error("before outcomes require an unreviewed candidate");
79
+ if (effectivePhase === "after" && (candidate.status !== "accepted" || candidate.promotion?.mode !== "outcome-canary"
80
+ || candidate.promotion?.canary?.status !== "active")) {
81
+ throw new Error("after outcomes require an active outcome canary");
82
+ }
83
+ if (effectivePhase === "after" && DEADLINE_BOUND_APPLICATIONS.has(application?.schema)
84
+ && new Date(timestamp).getTime() > new Date(application.outcomeExpiresAt).getTime()) {
85
+ throw new Error("after outcome registration missed its immutable initial trial deadline");
86
+ }
87
+ const receipt = normalizeOutcome({ id, phase, scope, metric, measurement, applicationId, deliveryId, evaluationId, coverage,
88
+ measuredAt }, candidate, timestamp, application, delivery, evaluation, measurementReceipt);
89
+ const duplicate = state.outcomes.find((item) => item.digest === receipt.digest);
90
+ if (duplicate) return { receipt: duplicate, candidate, decision: "unchanged", learningPath, unchanged: true };
91
+ if (receipt.schema === "agentspine.learning-outcome/v6" && state.outcomes.some((item) =>
92
+ item.schema === "agentspine.learning-outcome/v6" && item.evaluationId === receipt.evaluationId
93
+ && item.measurement.sourceDigest === receipt.measurement.sourceDigest)) {
94
+ throw new Error("outcome measurement provenance cannot be replayed within one evaluation contract");
95
+ }
96
+ if (["agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(receipt.schema)
97
+ && state.outcomes.some((item) => ["agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(item.schema)
98
+ && item.measurementReceiptId === receipt.measurementReceiptId)) {
99
+ throw new Error("one measurement receipt cannot be consumed by multiple outcomes");
100
+ }
101
+ if (["agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(receipt.schema)
102
+ && state.outcomes.some((item) => item.schema === receipt.schema && item.evaluationId === receipt.evaluationId
103
+ && item.phase === receipt.phase
104
+ && (receipt.schema === "agentspine.learning-outcome/v9"
105
+ ? item.measurement.evaluatorRootDigest === receipt.measurement.evaluatorRootDigest
106
+ : item.measurement.evaluatorId === receipt.measurement.evaluatorId))) {
107
+ throw new Error("paired evaluation accepts exactly one outcome per evaluator and phase");
108
+ }
109
+ if (["agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(receipt.schema) && receipt.phase === "after"
110
+ && state.outcomes.some((item) => item.schema === receipt.schema
111
+ && item.phase === "after" && item.evaluationId === receipt.evaluationId
112
+ && item.applicationId === receipt.applicationId)) {
113
+ throw new Error("paired evaluation requires a distinct completed turn for each after outcome");
114
+ }
115
+ state.outcomes.push(receipt);
116
+ state.outcomes.sort((a, b) => a.id.localeCompare(b.id));
117
+ const reconciled = effectivePhase === "after" ? reconcileCanary(state, candidate, timestamp) : { candidate, decision: "recorded", restored: [] };
118
+ return { receipt, ...reconciled, learningPath, unchanged: false };
119
+ });
120
+ }
121
+
122
+ export async function evaluateLearning({ root = process.cwd(), now = new Date() } = {}) {
123
+ const timestamp = date(now, "now");
124
+ return mutation(root, (state, _catalog, learningPath) => {
125
+ const accepted = [];
126
+ const reconciled = [];
127
+ while (true) {
128
+ const current = state.candidates.find((entry) => entry.status === "accepted" && revokedEvidence(state, entry));
129
+ if (!current) break;
130
+ rollbackCandidate(state, current, "learning evidence was explicitly revoked",
131
+ timestamp, "automatic-evidence-revocation");
132
+ reconciled.push({ id: current.id, decision: "rolled-back" });
133
+ }
134
+ while (true) {
135
+ const current = state.candidates.find((entry) => entry.status === "accepted"
136
+ && revokedMeasurementForCandidate(state, entry));
137
+ if (!current) break;
138
+ rollbackCandidate(state, current, "learning measurement evidence was explicitly revoked",
139
+ timestamp, "automatic-measurement-revocation");
140
+ reconciled.push({ id: current.id, decision: "rolled-back" });
141
+ }
142
+ while (true) {
143
+ const current = state.candidates.find((entry) => entry.status === "accepted"
144
+ && revokedApplicationForCandidate(state, entry));
145
+ if (!current) break;
146
+ rollbackCandidate(state, current, "learning application evidence was explicitly revoked",
147
+ timestamp, "automatic-application-revocation");
148
+ reconciled.push({ id: current.id, decision: "rolled-back" });
149
+ }
150
+ while (true) {
151
+ const current = state.candidates.find((entry) => entry.status === "accepted"
152
+ && revokedDeliveryForCandidate(state, entry));
153
+ if (!current) break;
154
+ rollbackCandidate(state, current, "learning delivery evidence was explicitly revoked",
155
+ timestamp, "automatic-delivery-revocation");
156
+ reconciled.push({ id: current.id, decision: "rolled-back" });
157
+ }
158
+ while (true) {
159
+ const current = state.candidates.find((entry) => entry.status === "accepted"
160
+ && revokedOutcomeForCandidate(state, entry));
161
+ if (!current) break;
162
+ rollbackCandidate(state, current, "learning outcome evidence was explicitly revoked",
163
+ timestamp, "automatic-outcome-revocation");
164
+ reconciled.push({ id: current.id, decision: "rolled-back" });
165
+ }
166
+ for (const current of state.candidates.filter((entry) => entry.status === "accepted" && entry.promotion?.mode === "outcome-canary")) {
167
+ const result = reconcileCanary(state, current, timestamp);
168
+ if (result.decision !== "unchanged" && result.decision !== "active") reconciled.push({ id: current.id, decision: result.decision });
169
+ }
170
+ if (state.config.autoPromote) {
171
+ for (const candidate of state.candidates.filter((entry) => entry.status === "candidate")) {
172
+ if (revokedEvidence(state, candidate)) continue;
173
+ if (state.measurementRevocations.some((receipt) => receipt.learningId === candidate.id)) continue;
174
+ if (state.applicationRevocations.some((receipt) => receipt.learningId === candidate.id)) continue;
175
+ if (state.outcomeRevocations.some((receipt) => receipt.learningId === candidate.id)) continue;
176
+ if (candidate.conflictsWith?.some((id) => state.candidates.some((entry) => entry.id === id && ["candidate", "accepted"].includes(entry.status)))) continue;
177
+ if (OUTCOME_AUTO_KINDS.has(candidate.kind)) {
178
+ if (SCOPE_FIELDS.every((field) => candidate.scope?.[field] === null)) continue;
179
+ if (candidate.requiresLocalReview) continue;
180
+ const planned = promotableReceipts(state, candidate, timestamp);
181
+ if (!planned) continue;
182
+ const { contract, receipts } = planned;
183
+ const minConfidence = contract.thresholds?.minConfidence ?? state.config.minConfidence;
184
+ const minEvidence = contract.thresholds?.minEvidence ?? state.config.minEvidence;
185
+ if (candidate.confidence < minConfidence || distinctEvidence(candidate) < minEvidence) continue;
186
+ const baseline = receipts.reduce((sum, item) => sum + item.metric.value, 0) / receipts.length;
187
+ accepted.push(acceptCandidate(state, candidate, timestamp, true, {
188
+ mode: "outcome-canary",
189
+ minConfidence,
190
+ minEvidence,
191
+ evidenceCount: distinctEvidence(candidate),
192
+ evaluatedAt: timestamp,
193
+ canary: {
194
+ status: "active",
195
+ scope: receipts[0].scope,
196
+ metric: { name: receipts[0].metric.name, direction: receipts[0].metric.direction },
197
+ evaluationId: contract.id,
198
+ evaluationDigest: contract.digest,
199
+ pairing: PAIRED_EVALUATIONS.has(contract.schema) ? contract.pairing : null,
200
+ evaluatorRootDigest: ROOT_BOUND_EVALUATIONS.has(contract.schema)
201
+ ? digest(contract.evaluatorRoots) : null,
202
+ evaluatorRegistryBindingDigest: REGISTRY_BOUND_EVALUATIONS.has(contract.schema)
203
+ ? state.evaluationBindings.find((binding) => binding.evaluationId === contract.id)?.digest : null,
204
+ initialTrialsDigest: INITIAL_TRIAL_EVALUATIONS.has(contract.schema)
205
+ ? digest(contract.initialTrials) : null,
206
+ targetDigest: TARGET_BOUND_EVALUATIONS.has(contract.schema) ? contract.target.digest : null,
207
+ completionPolicyDigest: DEADLINE_BOUND_EVALUATIONS.has(contract.schema)
208
+ ? contract.completionPolicy.digest : null,
209
+ stalenessPolicyDigest: STALENESS_BOUND_EVALUATIONS.has(contract.schema)
210
+ ? contract.stalenessPolicy.digest : null,
211
+ coverage: COVERAGE_EVALUATIONS.has(contract.schema) ? {
212
+ datasetDigest: contract.benchmark.datasetDigest,
213
+ minCases: contract.benchmark.minCases,
214
+ authority: "context-only"
215
+ } : null,
216
+ baseline,
217
+ beforeReceipts: receipts.map((item) => item.id),
218
+ expiresAt: new Date(Math.min(new Date(contract.expiresAt).getTime(),
219
+ new Date(timestamp).getTime()
220
+ + evaluationStalenessPolicy(contract, state.config).canaryTtlDays * 86400000)).toISOString()
221
+ },
222
+ authority: "context-only"
223
+ }));
224
+ continue;
225
+ }
226
+ if (AUTO_KINDS.has(candidate.kind)) {
227
+ if (candidate.confidence < state.config.minConfidence) continue;
228
+ if (distinctEvidence(candidate) < state.config.minEvidence) continue;
229
+ accepted.push(acceptCandidate(state, candidate, timestamp, true, {
230
+ mode: "automatic-low-risk",
231
+ minConfidence: state.config.minConfidence,
232
+ minEvidence: state.config.minEvidence,
233
+ evidenceCount: distinctEvidence(candidate),
234
+ evaluatedAt: timestamp,
235
+ authority: "context-only"
236
+ }));
237
+ }
238
+ }
239
+ }
240
+ return { enabled: state.config.autoPromote, accepted, reconciled, learningPath, authority: "context-only" };
241
+ });
242
+ }
243
+
244
+ export async function purgeStaleLearningApplications({ root = process.cwd(), confirmation, now = new Date() } = {}) {
245
+ if (confirmation !== "local-user-purge-confirmed") {
246
+ throw new Error("purging stale learning applications requires explicit local confirmation");
247
+ }
248
+ const timestamp = date(now, "now");
249
+ return mutation(root, (state, _catalog, learningPath) => {
250
+ const ids = new Set(state.applications.filter((application) => application.schema === "agentspine.learning-application/v2"
251
+ && new Date(application.deliveryExpiresAt).getTime() < new Date(timestamp).getTime()
252
+ && !revokedApplication(state, application.id)
253
+ && !state.deliveries.some((delivery) => delivery.applicationId === application.id)).map((application) => application.id));
254
+ state.applications = state.applications.filter((application) => !ids.has(application.id));
255
+ return { schema: "agentspine.learning-delivery-purge/v1", purged: ids.size, applicationIds: [...ids].sort(),
256
+ learningPath, authority: "context-only" };
257
+ });
258
+ }
259
+
260
+ export async function purgeStaleLearningMeasurements({ root = process.cwd(), confirmation, now = new Date() } = {}) {
261
+ if (confirmation !== "local-user-purge-confirmed") {
262
+ throw new Error("purging stale learning measurements requires explicit local confirmation");
263
+ }
264
+ const timestamp = date(now, "now");
265
+ return mutation(root, (state, _catalog, learningPath) => {
266
+ const consumed = new Set(state.outcomes.filter((outcome) => ["agentspine.learning-outcome/v7", "agentspine.learning-outcome/v8", "agentspine.learning-outcome/v9"].includes(outcome.schema))
267
+ .map((outcome) => outcome.measurementReceiptId));
268
+ for (const revocation of state.measurementRevocations) consumed.add(revocation.measurementId);
269
+ const validationHistory = state.history.filter((entry) => entry.kind === "learning-validation")
270
+ .map((entry) => entry.value);
271
+ for (const lease of [...state.validationLeases, ...validationHistory]
272
+ .filter((entry) => ["agentspine.learning-validation/v2", "agentspine.learning-validation/v3",
273
+ "agentspine.learning-validation/v4", "agentspine.learning-validation/v5"].includes(entry?.schema))) {
274
+ for (const evidence of lease.renewalEvidence) consumed.add(evidence.measurementId);
275
+ }
276
+ const cutoff = new Date(timestamp).getTime() - state.config.outcomeMaxAgeDays * 86400000;
277
+ const ids = new Set(state.measurements.filter((measurement) => !consumed.has(measurement.id)
278
+ && (new Date(measurement.measuredAt).getTime() < cutoff
279
+ || state.evaluations.some((contract) => contract.id === measurement.evaluationId
280
+ && new Date(contract.expiresAt).getTime() < new Date(timestamp).getTime())))
281
+ .map((measurement) => measurement.id));
282
+ state.measurements = state.measurements.filter((measurement) => !ids.has(measurement.id));
283
+ return { schema: "agentspine.learning-measurement-purge/v1", purged: ids.size,
284
+ measurementReceiptIds: [...ids].sort(), learningPath, authority: "context-only" };
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Internal runtime promotion path for locally opted-in continuity signals.
290
+ * This is intentionally not exposed over MCP. The caller must provide the
291
+ * directness, confidence, repetition and local opt-in proof recorded by the
292
+ * continuity state machine.
293
+ */
294
+
295
+ export async function acceptContinuityLearning({
296
+ root = process.cwd(), id, proof, now = new Date(), catalog: providedCatalog = null
297
+ }) {
298
+ if (!ID_RE.test(id || "")) throw new Error("id is required");
299
+ if (!proof || proof.mode !== "automatic-continuity-low-risk" || proof.localOptIn !== true) {
300
+ throw new Error("continuity promotion requires a recorded local opt-in proof");
301
+ }
302
+ const timestamp = date(now, "now");
303
+ return mutation(root, (state, _catalog, learningPath) => {
304
+ const candidate = state.candidates.find((entry) => entry.id === id);
305
+ if (!candidate) throw new Error(`unknown learning candidate: ${id}`);
306
+ if (revokedEvidence(state, candidate)) throw new Error("learning evidence was revoked; propose a new candidate before promotion");
307
+ if (candidate.status === "accepted") return { candidate, learningPath, unchanged: true };
308
+ if (candidate.status !== "candidate") throw new Error("only an active candidate can be promoted");
309
+ if (!CONTINUITY_AUTO_KINDS.has(candidate.kind)) throw new Error("learning kind is not eligible for continuity promotion");
310
+ const minConfidence = number(proof.minConfidence, "proof.minConfidence", 0.9, 1);
311
+ const minEvidence = integer(proof.minEvidence, "proof.minEvidence", 1, 10);
312
+ const minDirectness = number(proof.minDirectness, "proof.minDirectness", 0.9, 1);
313
+ const directness = number(proof.directness, "proof.directness", 0, 1);
314
+ const evidenceCount = distinctEvidence(candidate);
315
+ if (candidate.confidence < minConfidence || directness < minDirectness || evidenceCount < minEvidence) {
316
+ throw new Error("continuity candidate does not meet the recorded promotion thresholds");
317
+ }
318
+ const accepted = acceptCandidate(state, candidate, timestamp, true, {
319
+ mode: "automatic-continuity-low-risk",
320
+ localOptIn: true,
321
+ minConfidence,
322
+ minEvidence,
323
+ minDirectness,
324
+ directness,
325
+ evidenceCount,
326
+ evaluatedAt: timestamp,
327
+ authority: "context-only"
328
+ });
329
+ return { candidate: accepted, learningPath, unchanged: false };
330
+ }, providedCatalog);
331
+ }
332
+
333
+ export async function rollbackLearning({ root = process.cwd(), id, reason, now = new Date() }) {
334
+ if (!ID_RE.test(id || "")) throw new Error("id is required");
335
+ const rollbackReason = safeText(reason, "reason", 500);
336
+ const timestamp = date(now, "now");
337
+ return mutation(root, (state, _catalog, learningPath) => {
338
+ const candidate = state.candidates.find((entry) => entry.id === id);
339
+ if (!candidate || candidate.status !== "accepted") throw new Error("only an accepted learning can be rolled back");
340
+ const result = rollbackCandidate(state, candidate, rollbackReason, timestamp, "manual");
341
+ return { ...result, learningPath };
342
+ });
343
+ }