create-walle 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +88 -41
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +7 -5
  6. package/package.json +10 -4
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
  11. package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
  12. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  13. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  14. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  16. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  17. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  18. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  19. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  20. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  21. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  22. package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
  23. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  24. package/template/coding-task-manager/db.js +705 -286
  25. package/template/coding-task-manager/git-utils.js +44 -0
  26. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  27. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  28. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  29. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  30. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  31. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  32. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  33. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  34. package/template/coding-task-manager/lib/api/machines.js +461 -0
  35. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  36. package/template/coding-task-manager/lib/api/providers.js +53 -6
  37. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  38. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  39. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  40. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  41. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  42. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  43. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  44. package/template/coding-task-manager/lib/auth-context.js +32 -2
  45. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  46. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  47. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  48. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  49. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  50. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  51. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  52. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  53. package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
  54. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  55. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  56. package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
  57. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  58. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  59. package/template/coding-task-manager/lib/codex-local-links.js +267 -0
  60. package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
  61. package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
  62. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  63. package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
  64. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  65. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  66. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  67. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  68. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  69. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  70. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  71. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  72. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  73. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  74. package/template/coding-task-manager/lib/db-domains.js +21 -2
  75. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  76. package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
  77. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  78. package/template/coding-task-manager/lib/feature-manifest.js +1 -3
  79. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  80. package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
  81. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  82. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  83. package/template/coding-task-manager/lib/headless-term-service.js +60 -23
  84. package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
  85. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  86. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  87. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  88. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  89. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  90. package/template/coding-task-manager/lib/host-stats.js +328 -0
  91. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  92. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  93. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  94. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  95. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  96. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  97. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  98. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  99. package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
  100. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  101. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  102. package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
  103. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  104. package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
  105. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
  106. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  107. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  108. package/template/coding-task-manager/lib/login-tripwire.js +18 -0
  109. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  110. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  111. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  112. package/template/coding-task-manager/lib/machine-self.js +66 -0
  113. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
  114. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  115. package/template/coding-task-manager/lib/media-journal.js +84 -27
  116. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  117. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  118. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  119. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
  120. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  121. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  122. package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
  123. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  124. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  125. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  126. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  127. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  128. package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
  129. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  130. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  131. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  132. package/template/coding-task-manager/lib/native-history-control.js +62 -14
  133. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  134. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  135. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  136. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  137. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  138. package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
  139. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  140. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  141. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  142. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  143. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  144. package/template/coding-task-manager/lib/raw-history-block.js +3 -1
  145. package/template/coding-task-manager/lib/read-pool-client.js +36 -24
  146. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  147. package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
  148. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  149. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  150. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  151. package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
  152. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  153. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  154. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  155. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  156. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  157. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  158. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  159. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  160. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  161. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  162. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  163. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  164. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  165. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  166. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  167. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  168. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  169. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  170. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  171. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  172. package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
  173. package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
  174. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  175. package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
  176. package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
  177. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  178. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  179. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  180. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  181. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  182. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  183. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  184. package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
  185. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  186. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  187. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  188. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  189. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  190. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  191. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  192. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  193. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  194. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  195. package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
  196. package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
  197. package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
  198. package/template/coding-task-manager/lib/session-standup.js +67 -4
  199. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  200. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  201. package/template/coding-task-manager/lib/session-status.js +2 -2
  202. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  203. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  204. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  205. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  206. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  207. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  208. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  209. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  210. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  211. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  212. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  213. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  214. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  215. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  216. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  217. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  218. package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
  219. package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
  220. package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
  221. package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
  222. package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
  223. package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
  224. package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
  225. package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
  226. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  227. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  228. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  229. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  230. package/template/coding-task-manager/lib/ws/input.js +1 -0
  231. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  232. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  233. package/template/coding-task-manager/lib/ws-router.js +68 -1
  234. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  235. package/template/coding-task-manager/oauth-proxy.js +99 -55
  236. package/template/coding-task-manager/package-lock.json +24 -0
  237. package/template/coding-task-manager/package.json +3 -0
  238. package/template/coding-task-manager/protocol/message-table.json +0 -10
  239. package/template/coding-task-manager/protocol/messages.js +0 -10
  240. package/template/coding-task-manager/protocol/ws-schema.json +0 -20
  241. package/template/coding-task-manager/protocol/ws-schema.md +0 -2
  242. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  243. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  244. package/template/coding-task-manager/public/css/app.css +279 -2
  245. package/template/coding-task-manager/public/css/reviews.css +4 -0
  246. package/template/coding-task-manager/public/index.html +297 -240
  247. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  248. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  249. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  250. package/template/coding-task-manager/public/js/background-work.js +13 -7
  251. package/template/coding-task-manager/public/js/client-health-core.js +81 -0
  252. package/template/coding-task-manager/public/js/client-health-main.js +61 -0
  253. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  254. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  255. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  256. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  257. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  258. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  259. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  260. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  261. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  262. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  263. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
  264. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  265. package/template/coding-task-manager/public/js/protocol.js +0 -10
  266. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  267. package/template/coding-task-manager/public/js/reviews.js +7 -3
  268. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  269. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  270. package/template/coding-task-manager/public/js/row-join.js +18 -3
  271. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  272. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  273. package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
  274. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  275. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  276. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  277. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  278. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  279. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  280. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  281. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  282. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  283. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  284. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  285. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
  286. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  287. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  288. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  289. package/template/coding-task-manager/public/m/app.css +226 -8
  290. package/template/coding-task-manager/public/m/app.js +502 -9
  291. package/template/coding-task-manager/public/m/index.html +38 -1
  292. package/template/coding-task-manager/public/m/machines.js +1313 -0
  293. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  294. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  295. package/template/coding-task-manager/public/m/sw.js +5 -0
  296. package/template/coding-task-manager/queue-engine.js +13 -0
  297. package/template/coding-task-manager/server/approvals.js +5 -6
  298. package/template/coding-task-manager/server/boot.js +136 -17
  299. package/template/coding-task-manager/server.js +964 -425
  300. package/template/coding-task-manager/session-integrity.js +7 -9
  301. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  302. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  303. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  304. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  305. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  306. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  307. package/template/package-lock.json +2 -2
  308. package/template/package.json +1 -1
  309. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  310. package/template/shared/conversation-storage-v2-contract.js +42 -6
  311. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  312. package/template/shared/git-remote-key.js +83 -0
  313. package/template/shared/host-identity.js +98 -2
  314. package/template/shared/owner-write-safety.js +135 -0
  315. package/template/shared/public-runtime.js +13 -5
  316. package/template/shared/release-schema.json +3 -2
  317. package/template/shared/report-citations.js +14 -0
  318. package/template/shared/sqlite-write-lock.js +361 -223
  319. package/template/shared/storage/contract.js +35 -3
  320. package/template/wall-e/agent.js +21 -0
  321. package/template/wall-e/api-walle.js +5 -0
  322. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  323. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  324. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  325. package/template/wall-e/brain/migrations.js +51 -22
  326. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  327. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  328. package/template/wall-e/brain/schema-ensures.js +22 -0
  329. package/template/wall-e/brain.js +1 -1
  330. package/template/wall-e/coding/host-capabilities.js +2 -2
  331. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  332. package/template/wall-e/coding/session-journal.js +28 -40
  333. package/template/wall-e/coding-orchestrator.js +23 -26
  334. package/template/wall-e/fly.toml +1 -1
  335. package/template/wall-e/http/auth.js +18 -1
  336. package/template/wall-e/http/blog-views-api.js +318 -0
  337. package/template/wall-e/http/helper-rules-api.js +321 -0
  338. package/template/wall-e/http/model-proxy-providers.js +3 -1
  339. package/template/wall-e/http/telemetry-api.js +12 -0
  340. package/template/wall-e/lib/blog-view-policy.js +88 -0
  341. package/template/wall-e/lib/blog-view-store.js +169 -0
  342. package/template/wall-e/lib/client-config-lock.js +2 -1
  343. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  344. package/template/wall-e/lib/error-kinds.js +10 -1
  345. package/template/wall-e/lib/feature-manifest.js +1 -1
  346. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  347. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  348. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  349. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  350. package/template/wall-e/lib/helper-rules/label.js +247 -0
  351. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  352. package/template/wall-e/lib/helper-rules/store.js +667 -0
  353. package/template/wall-e/lib/mcp-integration.js +5 -6
  354. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  355. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  356. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  357. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  358. package/template/wall-e/llm/default-fallback.js +4 -0
  359. package/template/wall-e/llm/model-catalog.js +13 -4
  360. package/template/wall-e/llm/model-roles.js +6 -0
  361. package/template/wall-e/llm/portkey-http.js +150 -0
  362. package/template/wall-e/llm/portkey-sync.js +15 -145
  363. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  364. package/template/wall-e/llm/worker-route.js +43 -1
  365. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  366. package/template/wall-e/mcp-protocol-contract.js +41 -1
  367. package/template/wall-e/mcp-server.js +13 -2
  368. package/template/wall-e/mcp-worker-schemas.js +20 -2
  369. package/template/wall-e/memory/ctm-context-client.js +25 -5
  370. package/template/wall-e/shared/release-schema.json +3 -2
  371. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  372. package/template/wall-e/shared/storage/contract.js +35 -3
  373. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  374. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  375. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  376. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  377. package/template/wall-e/tools/local-tools.js +12 -5
  378. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  379. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  380. package/template/wall-e/workers/telemetry-worker.js +8 -0
  381. package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
@@ -0,0 +1,667 @@
1
+ 'use strict';
2
+
3
+ // Lead-cost routing WP-5a (design §6.6/§6.8, contract §2): the helper-rules record's ONLY
4
+ // writer. The four write ops are registered as CUSTOM_BRAIN_CALLS (loops/brain-custom-writes.js)
5
+ // and whitelisted (brain/owner-call-whitelist.js), so every caller reaches them through
6
+ // brainWrite(method, ...) — the loop holds a read-write brain, the scheduler worker routes the
7
+ // same call to the brain owner. This module takes the handle as its first argument and never
8
+ // requires '../../brain' at module top, so a test can hand it a temp database.
9
+ //
10
+ // The reads (readCellState / readCell / readTrailingOutcomes / listCells) are synchronous and
11
+ // pure-ish: they never create the tables and never throw — a missing table (a brain built
12
+ // before migration 100) reads as 'on', the same fail-open default lib/helper-rules/cell-state.js
13
+ // uses, so nothing that consults a cell can be broken by the record's absence.
14
+
15
+ const { LABEL_VERSION, labelFromFacts } = require('./label');
16
+ const {
17
+ EXPLORE_OPEN_TTL_MS,
18
+ REASONS,
19
+ EXPLORE_DEMOTION_REASONS,
20
+ WINDOW,
21
+ evaluateCell,
22
+ } = require('./evaluate');
23
+ // The kv watermark migration 101 seeds (`helper_rules.relabelled_to`). lib/ -> brain/ is a
24
+ // leaf-to-leaf edge: helper-rules-schema.js requires nothing, so there is no cycle.
25
+ const { RELABELLED_TO_KEY } = require('../../brain/helper-rules-schema');
26
+
27
+ const CELL_STATES = Object.freeze(['on', 'off', 'probe']);
28
+ const KIND_BY_PREFIX = Object.freeze({ 'wait-watch': 'wait-watch', explore: 'explore' });
29
+ const DEFAULT_LEAD_BURST_USD = 0.46; // mirrors brain/helper-rules-schema.js's lead_burst_usd seed
30
+ const LEAD_BURST_USD_KEY = 'helper_rules.lead_burst_usd';
31
+
32
+ function kindFromCellKey(cellKey) {
33
+ const prefix = String(cellKey || '').split('|')[0];
34
+ return KIND_BY_PREFIX[prefix] || 'wait-watch';
35
+ }
36
+
37
+ // --- round-2 facts plumbing (D-1/D-5) -----------------------------------------------
38
+
39
+ /** The row's stored facts object, or null. Never throws: unreadable facts are "no facts". */
40
+ function _parseFacts(value) {
41
+ if (value == null || value === '') return null;
42
+ if (typeof value === 'object') return value;
43
+ try {
44
+ const parsed = JSON.parse(String(value));
45
+ return parsed && typeof parsed === 'object' ? parsed : null;
46
+ } catch {
47
+ // HANDLE: a row whose facts_json cannot be parsed carries no observed inputs -- it stays as
48
+ // stored and is never re-labelled or counted as measured.
49
+ return null;
50
+ }
51
+ }
52
+
53
+ /** `recordOutcome` accepts the facts as an object or as the JSON string the hook sends. */
54
+ function _factsJsonOf(outcome) {
55
+ const raw = outcome.facts_json;
56
+ if (raw == null) return null;
57
+ if (typeof raw === 'string') return raw === '' ? null : raw;
58
+ if (typeof raw === 'object') return JSON.stringify(raw);
59
+ return null;
60
+ }
61
+
62
+ /** D-5: the incoming facts fill nulls in the stored ones -- they never blank a known value. */
63
+ function _nonNullEntries(facts) {
64
+ const out = {};
65
+ if (!facts || typeof facts !== 'object') return out;
66
+ for (const [key, value] of Object.entries(facts)) {
67
+ if (value !== null && value !== undefined) out[key] = value;
68
+ }
69
+ return out;
70
+ }
71
+
72
+ function _toMs(value) {
73
+ if (value == null) return null;
74
+ if (typeof value === 'number') return Number.isFinite(value) ? value : null;
75
+ const text = String(value).trim();
76
+ if (!text) return null;
77
+ // SQLite's datetime('now') writes 'YYYY-MM-DD HH:MM:SS' in UTC -- Date.parse reads that as local.
78
+ const iso = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}$/.test(text) ? `${text.replace(' ', 'T')}Z` : text;
79
+ const ms = Date.parse(iso);
80
+ return Number.isFinite(ms) ? ms : null;
81
+ }
82
+
83
+ // --- watcher spend from the usage ledger (D-9, WP-4b) -------------------------------
84
+
85
+ // The one feature string the CTM watcher writes (worker-wait-watcher.js WATCH_FEATURE) and the
86
+ // ledger's window tail: a tick that started inside the window can be journaled a little after the
87
+ // last outcome row was written.
88
+ const WATCHER_LEDGER_FEATURE = 'lead-wait-watch';
89
+ const WATCHER_USD_TAIL_MS = 10 * 60 * 1000;
90
+
91
+ /** SQLite's `datetime('now')` text form (UTC, no milliseconds) for a comparison against occurred_at. */
92
+ function _sqliteUtc(ms) {
93
+ return new Date(ms).toISOString().slice(0, 19).replace('T', ' ');
94
+ }
95
+
96
+ /**
97
+ * D-9: the watcher spend the cell's trailing window accrued, read from `model_usage_ledger` — the
98
+ * table the ticks already write (`feature='lead-wait-watch'`, WP-4a stores the watched worker's
99
+ * ctm_session_id as `session_id`, WP-1 stores the same id on every outcome row as `worker_id`).
100
+ * The window is the union of the rows' own created_at bounds plus WATCHER_USD_TAIL_MS.
101
+ * @returns {{usd:number, rows:number}|null} null when the window names no worker, or no ledger row matched
102
+ */
103
+ function readWatcherUsd(d, outcomes) {
104
+ if (!d || typeof d.prepare !== 'function') return null;
105
+ const rows = Array.isArray(outcomes) ? outcomes : [];
106
+ const workerIds = [...new Set(rows.map((row) => (row && row.worker_id != null ? String(row.worker_id) : '')).filter(Boolean))];
107
+ if (!workerIds.length) return null;
108
+ const times = rows.map((row) => _toMs(row && row.created_at)).filter((ms) => ms !== null);
109
+ if (!times.length) return null;
110
+ try {
111
+ const placeholders = workerIds.map(() => '?').join(', ');
112
+ const row = d.prepare(`
113
+ SELECT SUM(cost_usd) AS usd, COUNT(*) AS n FROM model_usage_ledger
114
+ WHERE feature = ? AND session_id IN (${placeholders}) AND occurred_at >= ? AND occurred_at <= ?
115
+ `).get(WATCHER_LEDGER_FEATURE, ...workerIds, _sqliteUtc(Math.min(...times)), _sqliteUtc(Math.max(...times) + WATCHER_USD_TAIL_MS));
116
+ const matched = Number(row && row.n) || 0;
117
+ if (matched <= 0) return null;
118
+ return { usd: Number(row.usd) || 0, rows: matched };
119
+ } catch {
120
+ // expected: a brain predating model_usage_ledger has no watcher spend to read — the metric stays null
121
+ return null;
122
+ }
123
+ }
124
+
125
+ function _readKv(d, key) {
126
+ try {
127
+ const row = d.prepare('SELECT value FROM kv_store WHERE key = ?').get(String(key));
128
+ return row ? row.value : null;
129
+ } catch {
130
+ // expected: kv_store is absent on a bare test handle -- no watermark means "never relabelled"
131
+ return null;
132
+ }
133
+ }
134
+
135
+ function _writeKv(d, key, value) {
136
+ d.prepare(`
137
+ INSERT INTO kv_store (key, value, updated_at) VALUES (?, ?, datetime('now'))
138
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
139
+ `).run(String(key), String(value));
140
+ }
141
+
142
+ /** The label version the one-time relabel/recovery pass has already run for (D-6); 0 when unknown. */
143
+ function readRelabelledTo(d) {
144
+ const value = Number.parseInt(_readKv(d, RELABELLED_TO_KEY), 10);
145
+ return Number.isFinite(value) ? value : 0;
146
+ }
147
+
148
+ /**
149
+ * D-7: how many of one cell's explore rows carry no measurement -- rows whose facts derive
150
+ * `unmeasured`, plus rows with no facts at all that are no longer open (a row that never
151
+ * produced observable inputs can never be a closed hand-off or fallback).
152
+ */
153
+ function _unmeasuredCount(d, cellKey) {
154
+ const rows = d.prepare(`
155
+ SELECT decision, facts_json FROM helper_outcomes WHERE cell_key = ? AND kind = 'explore'
156
+ `).all(String(cellKey));
157
+ let count = 0;
158
+ for (const row of rows) {
159
+ const facts = _parseFacts(row.facts_json);
160
+ if (facts) {
161
+ if (labelFromFacts('explore', facts).decision === 'unmeasured') count += 1;
162
+ } else if (row.decision !== 'open') {
163
+ count += 1;
164
+ }
165
+ }
166
+ return count;
167
+ }
168
+
169
+ // --- reads -------------------------------------------------------------------------
170
+
171
+ /** `helper_rule_cells` row, or null. Never throws (a missing table is "no row"). */
172
+ function readCell(d, cellKey) {
173
+ if (!d || typeof d.prepare !== 'function') return null;
174
+ try {
175
+ return d.prepare('SELECT * FROM helper_rule_cells WHERE cell_key = ?').get(String(cellKey)) || null;
176
+ } catch {
177
+ // expected: helper_rule_cells is absent until migration 100 runs — no row means 'on'
178
+ return null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * The one reader callers gate on. A missing table, a missing row, an unparsable state or any
184
+ * read error is 'on' (fail-open); a user veto is always 'off'.
185
+ * @returns {'on'|'off'|'probe'}
186
+ */
187
+ function readCellState(d, cellKey) {
188
+ const row = readCell(d, cellKey);
189
+ if (!row) return 'on';
190
+ if (Number(row.user_veto) === 1) return 'off';
191
+ const state = typeof row.state === 'string' ? row.state.trim() : '';
192
+ return CELL_STATES.includes(state) ? state : 'on';
193
+ }
194
+
195
+ /** The trailing window of a cell's outcomes, most recent first. Never throws. */
196
+ function readTrailingOutcomes(d, cellKey, limit = WINDOW) {
197
+ if (!d || typeof d.prepare !== 'function') return [];
198
+ const count = Number.isFinite(Number(limit)) && Number(limit) > 0 ? Math.floor(Number(limit)) : WINDOW;
199
+ try {
200
+ return d.prepare('SELECT * FROM helper_outcomes WHERE cell_key = ? ORDER BY seq DESC LIMIT ?')
201
+ .all(String(cellKey), count) || [];
202
+ } catch {
203
+ // expected: helper_outcomes is absent until migration 100 runs — an empty window changes nothing
204
+ return [];
205
+ }
206
+ }
207
+
208
+ /** Every cell row (the panel and the daily tick enumerate from here). Never throws. */
209
+ function listCells(d) {
210
+ if (!d || typeof d.prepare !== 'function') return [];
211
+ try {
212
+ return d.prepare('SELECT * FROM helper_rule_cells ORDER BY cell_key').all() || [];
213
+ } catch {
214
+ // expected: helper_rule_cells is absent until migration 100 runs — no cells yet
215
+ return [];
216
+ }
217
+ }
218
+
219
+ /** The WP-0 census measurement, falling back to the seeded value when the row is missing. */
220
+ function readLeadBurstUsd(d) {
221
+ try {
222
+ const row = d.prepare("SELECT value FROM kv_store WHERE key = ?").get(LEAD_BURST_USD_KEY);
223
+ const value = Number.parseFloat(row?.value);
224
+ return Number.isFinite(value) ? value : DEFAULT_LEAD_BURST_USD;
225
+ } catch {
226
+ // expected: kv_store is absent on a bare test handle — the seeded default is the contract value
227
+ return DEFAULT_LEAD_BURST_USD;
228
+ }
229
+ }
230
+
231
+ // --- the evaluator seam ------------------------------------------------------------
232
+
233
+ /**
234
+ * Evaluate one cell and persist the verdict. Writes `helper_rule_cells` (state, metrics,
235
+ * timestamps) and, ONLY when the state changed, one `helper_rule_events` row.
236
+ * @returns {{cell_key:string, state:string, changed:boolean}}
237
+ */
238
+ function evaluateAndApply(d, cellKey) {
239
+ const key = String(cellKey);
240
+ const stored = readCell(d, key);
241
+ const cell = stored || { cell_key: key, kind: kindFromCellKey(key), state: 'on', user_veto: 0, state_changed_at: null };
242
+ const outcomes = readTrailingOutcomes(d, key, WINDOW);
243
+ const verdict = evaluateCell(cell, outcomes, { now: Date.now(), leadBurstUsd: readLeadBurstUsd(d), watcherUsd: readWatcherUsd(d, outcomes) });
244
+ const previousState = stored && typeof stored.state === 'string' && stored.state ? stored.state : 'on';
245
+ const changed = verdict.state !== previousState;
246
+ const metricsJson = JSON.stringify(verdict.metrics);
247
+
248
+ // One upsert for both cases: a changed state restamps state_changed_at (the probe timer's
249
+ // zero point); an unchanged one keeps it and only refreshes metrics/updated_at.
250
+ d.prepare(`
251
+ INSERT INTO helper_rule_cells (cell_key, kind, state, user_veto, metrics_json, state_changed_at, updated_at)
252
+ VALUES (@cell_key, @kind, @state, @user_veto, @metrics_json, datetime('now'), datetime('now'))
253
+ ON CONFLICT(cell_key) DO UPDATE SET
254
+ state = excluded.state,
255
+ metrics_json = excluded.metrics_json,
256
+ state_changed_at = CASE WHEN helper_rule_cells.state = excluded.state
257
+ THEN COALESCE(helper_rule_cells.state_changed_at, excluded.state_changed_at)
258
+ ELSE excluded.state_changed_at END,
259
+ updated_at = excluded.updated_at
260
+ `).run({
261
+ cell_key: key,
262
+ kind: cell.kind || kindFromCellKey(key),
263
+ state: verdict.state,
264
+ user_veto: Number(cell.user_veto) === 1 ? 1 : 0,
265
+ metrics_json: metricsJson,
266
+ });
267
+
268
+ if (changed) {
269
+ d.prepare('INSERT INTO helper_rule_events (cell_key, from_state, to_state, reason, evidence_json) VALUES (?, ?, ?, ?, ?)')
270
+ .run(key, previousState, verdict.state, verdict.reason || 'state_change', metricsJson);
271
+ }
272
+
273
+ return { cell_key: key, state: verdict.state, changed };
274
+ }
275
+
276
+ // --- owner-path writes (CUSTOM_BRAIN_CALLS) ----------------------------------------
277
+
278
+ /**
279
+ * Record one watcher wake or exploration outcome. `seq` is per cell (re-labelling the same
280
+ * `id` replaces the row and keeps its place), then the cell is re-evaluated in the SAME
281
+ * transaction so the panel never sees an outcome the rules have not accounted for.
282
+ * @param {{id:string, cell_key:string, kind?:string, label?:string, decision?:string,
283
+ * helper_usd?:number, lead_usd?:number, seconds_to_report?:number,
284
+ * reread_rate?:number, report_rows?:number, grade_json?:string,
285
+ * worker_id?:string, facts_json?:string|object, label_version?:number}} outcome
286
+ * Round 2 (§6.4 S-13): `worker_id` makes the row addressable by worker (D-5),
287
+ * `facts_json` keeps the observed inputs the label is re-derivable from (D-1), and
288
+ * `label_version` defaults to LABEL_VERSION when facts are stored, 1 when they are not.
289
+ * @returns {{seq:number, cell:{cell_key:string, state:string, changed:boolean}}}
290
+ */
291
+ function recordOutcome(d, outcome = {}) {
292
+ const cellKey = String(outcome.cell_key || '');
293
+ const id = String(outcome.id || '');
294
+ if (!cellKey) throw new TypeError('helperRulesRecordOutcome: cell_key is required');
295
+ if (!id) throw new TypeError('helperRulesRecordOutcome: id is required');
296
+ const factsJson = _factsJsonOf(outcome);
297
+ const labelVersion = Number.isInteger(outcome.label_version) ? outcome.label_version : (factsJson ? LABEL_VERSION : 1);
298
+ const run = d.transaction(() => {
299
+ const existing = d.prepare('SELECT seq FROM helper_outcomes WHERE id = ?').get(id);
300
+ const row = d.prepare('SELECT COALESCE(MAX(seq), 0) AS max_seq FROM helper_outcomes WHERE cell_key = ?').get(cellKey);
301
+ const seq = existing ? Number(existing.seq) : Number(row?.max_seq || 0) + 1;
302
+ d.prepare(`
303
+ INSERT OR REPLACE INTO helper_outcomes
304
+ (id, cell_key, kind, seq, label, decision, helper_usd, lead_usd, seconds_to_report, reread_rate, report_rows, grade_json, worker_id, facts_json, label_version, created_at)
305
+ VALUES
306
+ (@id, @cell_key, @kind, @seq, @label, @decision, @helper_usd, @lead_usd, @seconds_to_report, @reread_rate, @report_rows, @grade_json, @worker_id, @facts_json, @label_version, datetime('now'))
307
+ `).run({
308
+ id,
309
+ cell_key: cellKey,
310
+ kind: outcome.kind || kindFromCellKey(cellKey),
311
+ seq,
312
+ label: outcome.label ?? null,
313
+ decision: outcome.decision ?? null,
314
+ helper_usd: outcome.helper_usd ?? null,
315
+ lead_usd: outcome.lead_usd ?? null,
316
+ seconds_to_report: outcome.seconds_to_report ?? null,
317
+ reread_rate: outcome.reread_rate ?? null,
318
+ report_rows: outcome.report_rows ?? null,
319
+ grade_json: outcome.grade_json ?? null,
320
+ worker_id: outcome.worker_id ?? null,
321
+ facts_json: factsJson,
322
+ label_version: labelVersion,
323
+ });
324
+ return { seq, cell: evaluateAndApply(d, cellKey) };
325
+ });
326
+ return run.immediate();
327
+ }
328
+
329
+ /**
330
+ * Close an exploration row (explore cells only — the wait-watch rows are labelled at insert).
331
+ * A missing id is not an error: WP-5b's dispatch can race a row it never opened.
332
+ * `seq` is returned alongside the cell because it is the spot-check grader's sampling counter
333
+ * (WP-6: every 5th completed hand-off per cell), and the caller cannot read it otherwise.
334
+ * @returns {{closed:boolean, seq?:number, cell?:object}}
335
+ */
336
+ function closeOutcome(d, id, patch = {}) {
337
+ const key = String(id || '');
338
+ if (!key) return { closed: false };
339
+ const run = d.transaction(() => {
340
+ const result = d.prepare(`
341
+ UPDATE helper_outcomes
342
+ SET decision = COALESCE(?, decision),
343
+ helper_usd = COALESCE(?, helper_usd),
344
+ seconds_to_report = COALESCE(?, seconds_to_report),
345
+ report_rows = COALESCE(?, report_rows)
346
+ WHERE id = ?
347
+ `).run(
348
+ patch.decision ?? null,
349
+ patch.helper_usd ?? null,
350
+ patch.seconds_to_report ?? null,
351
+ patch.report_rows ?? null,
352
+ key,
353
+ );
354
+ if (!result.changes) return { closed: false };
355
+ const row = d.prepare('SELECT cell_key, seq FROM helper_outcomes WHERE id = ?').get(key);
356
+ return { closed: true, seq: Number(row.seq), cell: evaluateAndApply(d, row.cell_key) };
357
+ });
358
+ return run.immediate();
359
+ }
360
+
361
+ /**
362
+ * D-5 (§6.4 S-13): close the open explore row for one worker, resolved here rather than by an
363
+ * in-memory id — a Wall-E restart, an evicted `openHandoffs` entry or a second process cannot
364
+ * orphan the row. The label is DERIVED (D-1): the caller hands over the terminal facts, the
365
+ * stored open-time facts fill every null, and `labelFromFacts` decides. A worker with no open
366
+ * row is not an error (the same race `closeOutcome` tolerates).
367
+ * @param {string} workerId the row's `worker_id` (the delegation's ctm_session_id)
368
+ * @param {{facts?:object}} [patch] the terminal facts (§6.2)
369
+ * @returns {{closed:boolean, id?:string, seq?:number, cell?:object, labelled?:object, facts?:object}}
370
+ */
371
+ function closeOutcomeByWorker(d, workerId, { facts } = {}) {
372
+ const worker = String(workerId || '');
373
+ if (!worker) return { closed: false };
374
+ const run = d.transaction(() => {
375
+ const row = d.prepare(`
376
+ SELECT id, cell_key, seq, facts_json FROM helper_outcomes
377
+ WHERE worker_id = ? AND kind = 'explore' AND decision = 'open'
378
+ ORDER BY seq DESC LIMIT 1
379
+ `).get(worker);
380
+ if (!row) return { closed: false };
381
+ // The stored facts keep opened_at / brief_path / harness; the incoming ones fill every
382
+ // remaining field. A null in the incoming object never blanks a known stored value.
383
+ const merged = { ...(_parseFacts(row.facts_json) || {}), ..._nonNullEntries(facts) };
384
+ const labelled = labelFromFacts('explore', merged);
385
+ d.prepare(`
386
+ UPDATE helper_outcomes
387
+ SET decision = ?, helper_usd = ?, seconds_to_report = ?, report_rows = ?,
388
+ facts_json = ?, label_version = ?
389
+ WHERE id = ?
390
+ `).run(
391
+ labelled.decision,
392
+ labelled.helper_usd ?? null,
393
+ labelled.seconds_to_report ?? null,
394
+ labelled.report_rows ?? null,
395
+ JSON.stringify(merged),
396
+ LABEL_VERSION,
397
+ row.id,
398
+ );
399
+ return {
400
+ closed: true,
401
+ id: row.id,
402
+ seq: Number(row.seq),
403
+ cell: evaluateAndApply(d, row.cell_key),
404
+ labelled,
405
+ facts: merged,
406
+ };
407
+ });
408
+ return run.immediate();
409
+ }
410
+
411
+ // D-5: the recomputed columns of one explore row — everything except the stored inputs and the
412
+ // version stamp. Compared field by field so `relabelled` counts the rows this pass actually FIXED
413
+ // rather than the rows it merely visited.
414
+ function _exploreDiffers(row, labelled) {
415
+ return row.decision !== labelled.decision
416
+ || (row.helper_usd ?? null) !== (labelled.helper_usd ?? null)
417
+ || (row.seconds_to_report ?? null) !== (labelled.seconds_to_report ?? null)
418
+ || (row.report_rows ?? null) !== (labelled.report_rows ?? null);
419
+ }
420
+
421
+ /**
422
+ * D-5/D-6 (§6.4 S-13): recompute every stored row whose facts exist and whose `label_version`
423
+ * is behind `to`, then run the one-time recovery pass. Idempotent by construction — a second run
424
+ * has no candidates and the kv watermark stops the recovery — so it is safe to call on every boot
425
+ * or from the daily tick.
426
+ *
427
+ * A row with `facts_json IS NULL` is NEVER touched (D-1): its inputs were not kept, so there is
428
+ * nothing to recompute from and it must not be silently rewritten.
429
+ * @param {{to?:number}} [options]
430
+ * @returns {{scanned:number, relabelled:number, unmeasured:number, cells_reset:string[]}}
431
+ */
432
+ function relabel(d, { to = LABEL_VERSION } = {}) {
433
+ const target = Number.isFinite(Number(to)) ? Math.trunc(Number(to)) : LABEL_VERSION;
434
+ const run = d.transaction(() => {
435
+ const rows = d.prepare(`
436
+ SELECT id, cell_key, kind, decision, label, helper_usd, seconds_to_report, report_rows, facts_json
437
+ FROM helper_outcomes
438
+ WHERE facts_json IS NOT NULL AND facts_json <> '' AND label_version < ?
439
+ ORDER BY cell_key, seq
440
+ `).all(target);
441
+ const updateExplore = d.prepare(`
442
+ UPDATE helper_outcomes
443
+ SET decision = ?, helper_usd = ?, seconds_to_report = ?, report_rows = ?, label_version = ?
444
+ WHERE id = ?
445
+ `);
446
+ const updateWatch = d.prepare('UPDATE helper_outcomes SET label = ?, label_version = ? WHERE id = ?');
447
+ const changedByCell = new Map();
448
+ let relabelled = 0;
449
+ for (const row of rows) {
450
+ const facts = _parseFacts(row.facts_json);
451
+ if (!facts) continue; // HANDLE: unreadable facts are not inputs -- leave the row as stored
452
+ const labelled = labelFromFacts(row.kind, facts);
453
+ if (row.kind === 'explore') {
454
+ if (_exploreDiffers(row, labelled)) {
455
+ relabelled += 1;
456
+ changedByCell.set(row.cell_key, (changedByCell.get(row.cell_key) || 0) + 1);
457
+ }
458
+ updateExplore.run(
459
+ labelled.decision,
460
+ labelled.helper_usd ?? null,
461
+ labelled.seconds_to_report ?? null,
462
+ labelled.report_rows ?? null,
463
+ target,
464
+ row.id,
465
+ );
466
+ } else {
467
+ if (row.label !== labelled.label) {
468
+ relabelled += 1;
469
+ changedByCell.set(row.cell_key, (changedByCell.get(row.cell_key) || 0) + 1);
470
+ }
471
+ updateWatch.run(labelled.label, target, row.id);
472
+ }
473
+ }
474
+
475
+ let cellsReset = [];
476
+ if (readRelabelledTo(d) < target) {
477
+ cellsReset = _recoverDemotedCells(d, target, changedByCell);
478
+ }
479
+ _writeKv(d, RELABELLED_TO_KEY, target);
480
+ return {
481
+ scanned: rows.length,
482
+ relabelled,
483
+ unmeasured: _unmeasuredTotal(d, rows),
484
+ cells_reset: cellsReset,
485
+ };
486
+ });
487
+ return run.immediate();
488
+ }
489
+
490
+ /** The global D-7 unmeasured count over every explore row (all versions) of the touched cells. */
491
+ function _unmeasuredTotal(d, rows) {
492
+ const cells = [...new Set(rows.map((row) => row.cell_key))];
493
+ let total = 0;
494
+ for (const cell of cells) {
495
+ if (String(cell).startsWith('explore|')) total += _unmeasuredCount(d, cell);
496
+ }
497
+ return total;
498
+ }
499
+
500
+ /**
501
+ * D-6: the one-time recovery. A cell the EVALUATOR demoted (its last event reason is one of the
502
+ * demotion reasons) was demoted on round-1 labels; those verdicts are void (P3), so the cell goes
503
+ * back to `on` with one audit event and is re-evaluated once — a cell that still fails on v2
504
+ * labels goes straight back off with a fresh, truthful reason. A vetoed cell and every wait-watch
505
+ * cell are never touched.
506
+ */
507
+ function _recoverDemotedCells(d, target, changedByCell) {
508
+ const cells = d.prepare(`
509
+ SELECT cell_key, state FROM helper_rule_cells
510
+ WHERE cell_key LIKE 'explore|%' AND user_veto = 0 AND state IN ('off', 'probe')
511
+ ORDER BY cell_key
512
+ `).all();
513
+ const reset = [];
514
+ for (const cell of cells) {
515
+ const lastEvent = d.prepare('SELECT reason FROM helper_rule_events WHERE cell_key = ? ORDER BY id DESC LIMIT 1')
516
+ .get(cell.cell_key);
517
+ const previousReason = lastEvent && typeof lastEvent.reason === 'string' ? lastEvent.reason : null;
518
+ if (!previousReason || !EXPLORE_DEMOTION_REASONS.has(previousReason)) continue;
519
+ d.prepare(`
520
+ UPDATE helper_rule_cells SET state = 'on', state_changed_at = datetime('now'), updated_at = datetime('now')
521
+ WHERE cell_key = ?
522
+ `).run(cell.cell_key);
523
+ d.prepare('INSERT INTO helper_rule_events (cell_key, from_state, to_state, reason, evidence_json) VALUES (?, ?, ?, ?, ?)')
524
+ .run(cell.cell_key, cell.state, 'on', REASONS.LABEL_V2_RESET, JSON.stringify({
525
+ relabelled: changedByCell.get(cell.cell_key) || 0,
526
+ unmeasured: _unmeasuredCount(d, cell.cell_key),
527
+ previous_reason: previousReason,
528
+ }));
529
+ evaluateAndApply(d, cell.cell_key);
530
+ reset.push(cell.cell_key);
531
+ }
532
+ return reset;
533
+ }
534
+
535
+ /**
536
+ * D-10 (§6.4 S-13): an explore row still open after `ttlMs` was never closed by the hook — a
537
+ * Wall-E restart, a lead that stopped polling, or a worker that outlived its lead. It is closed
538
+ * as `abandoned` (excluded from `closed` by D-7) with `facts_json.abandoned_at`, so the panel can
539
+ * show it instead of an ever-growing "open" set.
540
+ * @param {{nowMs?:number, ttlMs?:number}} [options]
541
+ * @returns {{abandoned:number}}
542
+ */
543
+ function sweepAbandoned(d, { nowMs = Date.now(), ttlMs = EXPLORE_OPEN_TTL_MS } = {}) {
544
+ const now = Number.isFinite(Number(nowMs)) ? Number(nowMs) : Date.now();
545
+ const ttl = Number.isFinite(Number(ttlMs)) ? Number(ttlMs) : EXPLORE_OPEN_TTL_MS;
546
+ const run = d.transaction(() => {
547
+ const rows = d.prepare(`
548
+ SELECT id, cell_key, seq, facts_json, created_at FROM helper_outcomes
549
+ WHERE kind = 'explore' AND decision = 'open'
550
+ ORDER BY cell_key, seq
551
+ `).all();
552
+ const update = d.prepare('UPDATE helper_outcomes SET decision = ?, facts_json = ?, label_version = ? WHERE id = ?');
553
+ const touched = new Set();
554
+ let abandoned = 0;
555
+ for (const row of rows) {
556
+ const facts = _parseFacts(row.facts_json) || {};
557
+ const openedAt = Number.isFinite(Number(facts.opened_at)) ? Number(facts.opened_at) : _toMs(row.created_at);
558
+ if (openedAt == null || now - openedAt <= ttl) continue;
559
+ update.run('abandoned', JSON.stringify({ ...facts, abandoned_at: now }), LABEL_VERSION, row.id);
560
+ touched.add(row.cell_key);
561
+ abandoned += 1;
562
+ }
563
+ for (const cellKey of touched) evaluateAndApply(d, cellKey);
564
+ return { abandoned };
565
+ });
566
+ return run.immediate();
567
+ }
568
+
569
+ /**
570
+ * WP-6: store the spot-check grader's verdict on one hand-off row, then re-evaluate its cell in
571
+ * the SAME transaction — the explore rule reads `grade_json.sufficient`, so the sufficient rate
572
+ * may never be a write behind the row it belongs to. A missing id is not an error (the grade
573
+ * arrives fire-and-forget and may race a purge).
574
+ * @param {string} id the `helper_outcomes` row (the delegation invocation id)
575
+ * @param {object} gradeJson `{sufficient, coverage, inaccuracies, why}` or `{unavailable:true, error}`
576
+ * @returns {{updated:boolean, cell?:object}}
577
+ */
578
+ function setGrade(d, id, gradeJson) {
579
+ const key = String(id || '');
580
+ if (!key) return { updated: false };
581
+ const run = d.transaction(() => {
582
+ const result = d.prepare('UPDATE helper_outcomes SET grade_json = ? WHERE id = ?')
583
+ .run(JSON.stringify(gradeJson || {}), key);
584
+ if (!result.changes) return { updated: false };
585
+ const row = d.prepare('SELECT cell_key FROM helper_outcomes WHERE id = ?').get(key);
586
+ return { updated: true, cell: evaluateAndApply(d, row.cell_key) };
587
+ });
588
+ return run.immediate();
589
+ }
590
+
591
+ /**
592
+ * WP-7's veto/enable switch. Upserts the cell and always writes one event row
593
+ * (`from_state` = whatever was stored, null on the first write).
594
+ * @returns {object|null} the cell row as stored
595
+ */
596
+ function setCellState(d, cellKey, { state, user_veto, reason, evidence } = {}) {
597
+ const key = String(cellKey || '');
598
+ if (!key) throw new TypeError('helperRulesSetCellState: cell_key is required');
599
+ const nextState = CELL_STATES.includes(state) ? state : 'on';
600
+ const veto = user_veto ? 1 : 0;
601
+ const run = d.transaction(() => {
602
+ const previous = readCell(d, key);
603
+ d.prepare(`
604
+ INSERT INTO helper_rule_cells (cell_key, kind, state, user_veto, state_changed_at, updated_at)
605
+ VALUES (@cell_key, @kind, @state, @user_veto, datetime('now'), datetime('now'))
606
+ ON CONFLICT(cell_key) DO UPDATE SET
607
+ state = excluded.state,
608
+ user_veto = excluded.user_veto,
609
+ state_changed_at = CASE WHEN helper_rule_cells.state = excluded.state
610
+ THEN COALESCE(helper_rule_cells.state_changed_at, excluded.state_changed_at)
611
+ ELSE excluded.state_changed_at END,
612
+ updated_at = excluded.updated_at
613
+ `).run({ cell_key: key, kind: kindFromCellKey(key), state: nextState, user_veto: veto });
614
+ d.prepare('INSERT INTO helper_rule_events (cell_key, from_state, to_state, reason, evidence_json) VALUES (?, ?, ?, ?, ?)')
615
+ .run(key, previous ? previous.state : null, nextState, String(reason || 'manual'), JSON.stringify(evidence || {}));
616
+ return readCell(d, key);
617
+ });
618
+ return run.immediate();
619
+ }
620
+
621
+ /**
622
+ * The daily timer the outcome stream cannot provide: `off → probe` needs elapsed time, not a
623
+ * new row. WP-5b's scheduler registration calls this through brainWrite; the rules are unchanged
624
+ * either way. Round 2 (§6.4 S-16) runs three steps, in this order:
625
+ * 1. `sweepAbandoned` — D-10's 4 h TTL, so a row no lead ever closed stops occupying the window;
626
+ * 2. `relabel` — D-5's recompute + D-6's one-time recovery (a no-op once the kv watermark is
627
+ * current, which is why it can ride the daily tick at all);
628
+ * 3. the evaluator over every explore cell, as before — this is what advances `off → probe`.
629
+ * @returns {{cells: Array<{cell_key:string, state:string, changed:boolean}>, abandoned:number,
630
+ * relabelled:number}}
631
+ */
632
+ function dailyTick(d) {
633
+ const abandoned = sweepAbandoned(d, {});
634
+ const relabelled = relabel(d, {});
635
+ const keys = d.prepare("SELECT cell_key FROM helper_rule_cells WHERE kind = 'explore' OR cell_key LIKE 'explore|%'")
636
+ .all()
637
+ .map((row) => row.cell_key);
638
+ const run = d.transaction(() => keys.map((key) => evaluateAndApply(d, key)));
639
+ return {
640
+ cells: run.immediate(),
641
+ abandoned: abandoned.abandoned,
642
+ relabelled: relabelled.relabelled,
643
+ };
644
+ }
645
+
646
+ module.exports = {
647
+ CELL_STATES,
648
+ DEFAULT_LEAD_BURST_USD,
649
+ LEAD_BURST_USD_KEY,
650
+ closeOutcome,
651
+ closeOutcomeByWorker,
652
+ dailyTick,
653
+ evaluateAndApply,
654
+ kindFromCellKey,
655
+ listCells,
656
+ readCell,
657
+ readCellState,
658
+ readLeadBurstUsd,
659
+ readRelabelledTo,
660
+ readTrailingOutcomes,
661
+ readWatcherUsd,
662
+ recordOutcome,
663
+ relabel,
664
+ setCellState,
665
+ setGrade,
666
+ sweepAbandoned,
667
+ };