@sema-agent/server 1.322.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (539) hide show
  1. package/dist/approval-hmac.d.ts +40 -0
  2. package/dist/approval-hmac.js +67 -0
  3. package/dist/approval.d.ts +31 -0
  4. package/dist/approval.js +72 -4
  5. package/dist/audit.d.ts +26 -0
  6. package/dist/audit.js +40 -1
  7. package/dist/auth-bridge.d.ts +10 -0
  8. package/dist/auth-bridge.js +47 -4
  9. package/dist/auth-keys.d.ts +19 -0
  10. package/dist/auth-keys.js +13 -0
  11. package/dist/bake-runner/main.d.ts +5 -0
  12. package/dist/bake-runner/main.js +43 -4
  13. package/dist/bake-runner/protocol.d.ts +57 -0
  14. package/dist/bake-runner/protocol.js +58 -3
  15. package/dist/bake-runner/runner.d.ts +54 -0
  16. package/dist/bake-runner/runner.js +87 -5
  17. package/dist/bench/l8/artifact.d.ts +63 -0
  18. package/dist/bench/l8/artifact.js +61 -2
  19. package/dist/bench/l8/escape.d.ts +36 -0
  20. package/dist/bench/l8/escape.js +25 -0
  21. package/dist/bench/l8/index.d.ts +14 -0
  22. package/dist/bench/l8/index.js +14 -0
  23. package/dist/bench/l8/probes.d.ts +74 -0
  24. package/dist/bench/l8/probes.js +60 -0
  25. package/dist/bench/l8/run-probes.d.ts +43 -0
  26. package/dist/bench/l8/run-probes.js +45 -2
  27. package/dist/bench/s1/arms.d.ts +126 -0
  28. package/dist/bench/s1/arms.js +188 -13
  29. package/dist/bench/s1/live-deps.d.ts +66 -0
  30. package/dist/bench/s1/live-deps.js +249 -11
  31. package/dist/bench/s1/oracle.d.ts +49 -0
  32. package/dist/bench/s1/oracle.js +36 -1
  33. package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
  34. package/dist/bench/s1/repair-oracle-adapter.js +30 -0
  35. package/dist/bench/s1/reviewer.d.ts +38 -0
  36. package/dist/bench/s1/reviewer.js +34 -0
  37. package/dist/bench/s1/row.d.ts +77 -0
  38. package/dist/bench/s1/row.js +62 -0
  39. package/dist/bench/s1/run-firm.d.ts +47 -0
  40. package/dist/bench/s1/run-firm.js +70 -4
  41. package/dist/bench/s1/runner-ctx.d.ts +71 -0
  42. package/dist/bench/s1/runner-ctx.js +36 -0
  43. package/dist/bench/s1/tasks.d.ts +108 -0
  44. package/dist/bench/s1/tasks.js +115 -0
  45. package/dist/boot-reclaim.d.ts +19 -0
  46. package/dist/boot-reclaim.js +20 -0
  47. package/dist/brain.d.ts +25 -0
  48. package/dist/brain.js +61 -1
  49. package/dist/budget.d.ts +66 -0
  50. package/dist/budget.js +128 -4
  51. package/dist/capabilities/builtin-tools.d.ts +3 -0
  52. package/dist/capabilities/builtin-tools.js +3 -0
  53. package/dist/capabilities/center-plugins.d.ts +17 -1
  54. package/dist/capabilities/center-plugins.js +45 -4
  55. package/dist/capabilities/center-prompts.d.ts +46 -0
  56. package/dist/capabilities/center-prompts.js +57 -3
  57. package/dist/capabilities/code-review-council.d.ts +28 -0
  58. package/dist/capabilities/code-review-council.js +40 -7
  59. package/dist/capabilities/collab-wire.d.ts +2 -0
  60. package/dist/capabilities/collab-workflows.d.ts +7 -0
  61. package/dist/capabilities/collab-workflows.js +44 -1
  62. package/dist/capabilities/oa-tools.d.ts +6 -0
  63. package/dist/capabilities/oa-tools.js +10 -0
  64. package/dist/capabilities/prompt.d.ts +14 -0
  65. package/dist/capabilities/prompt.js +14 -0
  66. package/dist/capabilities/prompts/code-review.d.ts +14 -0
  67. package/dist/capabilities/prompts/code-review.js +14 -0
  68. package/dist/capabilities/prompts/identity.d.ts +6 -0
  69. package/dist/capabilities/prompts/identity.js +6 -0
  70. package/dist/capabilities/prompts/team.d.ts +4 -0
  71. package/dist/capabilities/prompts/team.js +4 -0
  72. package/dist/capabilities/repo-tools.d.ts +11 -0
  73. package/dist/capabilities/repo-tools.js +8 -0
  74. package/dist/capabilities/sandbox-file-send.d.ts +95 -0
  75. package/dist/capabilities/sandbox-file-send.js +91 -3
  76. package/dist/capabilities/scenarios.d.ts +130 -0
  77. package/dist/capabilities/scenarios.js +180 -7
  78. package/dist/capabilities/select-environment-tool.d.ts +7 -0
  79. package/dist/capabilities/select-environment-tool.js +30 -0
  80. package/dist/capabilities/send-user-file-tool.d.ts +33 -0
  81. package/dist/capabilities/send-user-file-tool.js +65 -1
  82. package/dist/capabilities/skills.d.ts +29 -0
  83. package/dist/capabilities/skills.js +13 -2
  84. package/dist/capabilities/team.d.ts +22 -0
  85. package/dist/capabilities/team.js +22 -2
  86. package/dist/capabilities/tool-defer.d.ts +26 -0
  87. package/dist/capabilities/tool-defer.js +3 -0
  88. package/dist/config-center/apply-effective.d.ts +65 -0
  89. package/dist/config-center/apply-effective.js +204 -12
  90. package/dist/config-center/http-client.d.ts +34 -0
  91. package/dist/config-center/http-client.js +68 -0
  92. package/dist/config-center/restart-signal.d.ts +22 -0
  93. package/dist/config-center/restart-signal.js +39 -0
  94. package/dist/config-center/skills-mcp.d.ts +20 -1
  95. package/dist/config-center/skills-mcp.js +42 -7
  96. package/dist/config-center/types.d.ts +92 -1
  97. package/dist/config-lkg.d.ts +12 -0
  98. package/dist/config-lkg.js +41 -0
  99. package/dist/config-provider.d.ts +78 -0
  100. package/dist/config-provider.js +165 -2
  101. package/dist/config-types.d.ts +546 -0
  102. package/dist/config.d.ts +64 -0
  103. package/dist/config.js +455 -60
  104. package/dist/degenerate-instrument.d.ts +38 -0
  105. package/dist/degenerate-instrument.js +55 -3
  106. package/dist/elicitation.d.ts +39 -0
  107. package/dist/elicitation.js +87 -3
  108. package/dist/env-facts.d.ts +61 -0
  109. package/dist/env-facts.js +73 -7
  110. package/dist/fleet/fleet-bus.d.ts +185 -0
  111. package/dist/fleet/fleet-bus.js +333 -27
  112. package/dist/fleet/subagent-tail-bus.d.ts +11 -0
  113. package/dist/fleet/subagent-tail-bus.js +33 -2
  114. package/dist/fleet-client.d.ts +38 -0
  115. package/dist/fleet-client.js +44 -9
  116. package/dist/fleet-lease.d.ts +46 -0
  117. package/dist/fleet-lease.js +73 -5
  118. package/dist/hooks/branch-transcript.d.ts +8 -0
  119. package/dist/hooks/branch-transcript.js +33 -0
  120. package/dist/hooks/cc-stop-prompt.d.ts +42 -0
  121. package/dist/hooks/cc-stop-prompt.js +43 -1
  122. package/dist/hooks/hook-llm.d.ts +15 -0
  123. package/dist/hooks/hook-llm.js +47 -0
  124. package/dist/hooks/hook-runner.d.ts +89 -0
  125. package/dist/hooks/hook-runner.js +327 -18
  126. package/dist/http/idempotency.d.ts +31 -0
  127. package/dist/http/idempotency.js +34 -0
  128. package/dist/http/principal-gate.d.ts +17 -0
  129. package/dist/http/principal-gate.js +35 -3
  130. package/dist/http/route-ctx.d.ts +36 -0
  131. package/dist/http/routes/approvals-assistant.d.ts +25 -0
  132. package/dist/http/routes/approvals-assistant.js +253 -23
  133. package/dist/http/routes/attachments.js +22 -4
  134. package/dist/http/routes/capabilities.d.ts +7 -0
  135. package/dist/http/routes/capabilities.js +202 -1
  136. package/dist/http/routes/fleet.d.ts +6 -0
  137. package/dist/http/routes/fleet.js +94 -5
  138. package/dist/http/routes/images.d.ts +17 -0
  139. package/dist/http/routes/images.js +222 -14
  140. package/dist/http/routes/leader.d.ts +7 -0
  141. package/dist/http/routes/leader.js +10 -0
  142. package/dist/http/routes/memory-policy.d.ts +6 -0
  143. package/dist/http/routes/memory-policy.js +38 -6
  144. package/dist/http/routes/notify-wake.d.ts +10 -0
  145. package/dist/http/routes/notify-wake.js +19 -3
  146. package/dist/http/routes/observability.d.ts +6 -0
  147. package/dist/http/routes/observability.js +19 -2
  148. package/dist/http/routes/runs.d.ts +15 -0
  149. package/dist/http/routes/runs.js +464 -41
  150. package/dist/http/routes/session-sync.d.ts +15 -0
  151. package/dist/http/routes/session-sync.js +229 -19
  152. package/dist/http/routes/sessions-list.d.ts +6 -0
  153. package/dist/http/routes/sessions-list.js +35 -4
  154. package/dist/http/routes/sessions.d.ts +14 -0
  155. package/dist/http/routes/sessions.js +176 -31
  156. package/dist/http/routes/side-query.d.ts +8 -0
  157. package/dist/http/routes/side-query.js +26 -1
  158. package/dist/http/routes/tasks.d.ts +18 -0
  159. package/dist/http/routes/tasks.js +501 -38
  160. package/dist/http/routes/trace-usage.d.ts +10 -0
  161. package/dist/http/routes/trace-usage.js +66 -14
  162. package/dist/http/routes/workflows.d.ts +12 -0
  163. package/dist/http/routes/workflows.js +180 -19
  164. package/dist/http/run-meta.d.ts +11 -0
  165. package/dist/http/run-meta.js +6 -0
  166. package/dist/http/send.d.ts +19 -0
  167. package/dist/http/send.js +11 -0
  168. package/dist/http/server.d.ts +255 -0
  169. package/dist/http/server.js +921 -75
  170. package/dist/http/sse-log.d.ts +15 -0
  171. package/dist/http/sse-log.js +16 -0
  172. package/dist/http/tar.d.ts +9 -0
  173. package/dist/http/tar.js +21 -5
  174. package/dist/http/wire-gate.d.ts +9 -0
  175. package/dist/http/wire-gate.js +9 -0
  176. package/dist/http/wire-types.d.ts +187 -0
  177. package/dist/http/workspace-content.d.ts +8 -0
  178. package/dist/http/workspace-content.js +10 -0
  179. package/dist/images/bake-validate.d.ts +63 -0
  180. package/dist/images/bake-validate.js +71 -1
  181. package/dist/images/manifest.d.ts +19 -0
  182. package/dist/images/manifest.js +6 -1
  183. package/dist/index.d.ts +1 -5
  184. package/dist/index.js +10 -5
  185. package/dist/key-resolver.d.ts +23 -0
  186. package/dist/key-resolver.js +27 -2
  187. package/dist/leader/diffout.d.ts +23 -0
  188. package/dist/leader/diffout.js +8 -1
  189. package/dist/leader/diffup.d.ts +32 -0
  190. package/dist/leader/diffup.js +43 -0
  191. package/dist/leader/endpoint.d.ts +20 -0
  192. package/dist/leader/endpoint.js +25 -2
  193. package/dist/leader/fanout.d.ts +60 -0
  194. package/dist/leader/fanout.js +29 -3
  195. package/dist/leader/grader-env-factory.d.ts +74 -0
  196. package/dist/leader/grader-env-factory.js +62 -3
  197. package/dist/leader/leader.d.ts +97 -0
  198. package/dist/leader/leader.js +151 -4
  199. package/dist/leader/merge.d.ts +94 -0
  200. package/dist/leader/merge.js +112 -9
  201. package/dist/leader/planner.d.ts +52 -0
  202. package/dist/leader/planner.js +47 -3
  203. package/dist/leader/repair-oracle.d.ts +61 -0
  204. package/dist/leader/repair-oracle.js +59 -1
  205. package/dist/leader/repair-wire.d.ts +78 -0
  206. package/dist/leader/repair-wire.js +79 -2
  207. package/dist/leader/wire.d.ts +79 -0
  208. package/dist/leader/wire.js +255 -8
  209. package/dist/lsp/e2b-bridge.d.ts +16 -0
  210. package/dist/lsp/e2b-bridge.js +32 -4
  211. package/dist/lsp/e2b-manager.d.ts +10 -0
  212. package/dist/lsp/e2b-manager.js +18 -2
  213. package/dist/lsp/lsp-frames.d.ts +12 -0
  214. package/dist/lsp/lsp-frames.js +12 -0
  215. package/dist/lsp/manager.d.ts +12 -0
  216. package/dist/lsp/manager.js +35 -2
  217. package/dist/lsp/types.d.ts +10 -0
  218. package/dist/lsp/ws-transport.d.ts +12 -0
  219. package/dist/lsp/ws-transport.js +53 -5
  220. package/dist/lsp-evict.d.ts +14 -0
  221. package/dist/lsp-evict.js +15 -1
  222. package/dist/main.js +2132 -96
  223. package/dist/memory-export.d.ts +1 -0
  224. package/dist/memory-export.js +4 -0
  225. package/dist/memory-scope.d.ts +49 -0
  226. package/dist/memory-scope.js +87 -4
  227. package/dist/memory-sync-client.d.ts +24 -0
  228. package/dist/memory-sync-client.js +41 -2
  229. package/dist/memory-sync.d.ts +23 -0
  230. package/dist/memory-sync.js +70 -1
  231. package/dist/model-select.d.ts +39 -0
  232. package/dist/model-select.js +42 -1
  233. package/dist/observability/cost-quota.d.ts +20 -0
  234. package/dist/observability/cost-quota.js +4 -0
  235. package/dist/observability/cost-taxonomy.d.ts +45 -0
  236. package/dist/observability/cost-taxonomy.js +34 -0
  237. package/dist/observability/logger.d.ts +1 -0
  238. package/dist/observability/logger.js +6 -0
  239. package/dist/observability/metrics.d.ts +13 -0
  240. package/dist/observability/metrics.js +63 -0
  241. package/dist/observability/otel-exporter.d.ts +9 -0
  242. package/dist/observability/otel-exporter.js +11 -3
  243. package/dist/observability/principal-context.d.ts +9 -0
  244. package/dist/observability/principal-context.js +9 -0
  245. package/dist/observability/prompt-manifest.d.ts +37 -0
  246. package/dist/observability/prompt-manifest.js +35 -1
  247. package/dist/observability/rate-limit.d.ts +10 -0
  248. package/dist/observability/rate-limit.js +1 -0
  249. package/dist/observability/tool-trace.d.ts +24 -0
  250. package/dist/observability/tool-trace.js +41 -1
  251. package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
  252. package/dist/orchestration/hardened-vm-runner.js +119 -4
  253. package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
  254. package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
  255. package/dist/orchestration/hardened-vm-worker.js +27 -0
  256. package/dist/orchestration/subagent-steer.d.ts +44 -0
  257. package/dist/orchestration/subagent-steer.js +45 -1
  258. package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
  259. package/dist/orchestration/workflow-agent-steer.js +80 -1
  260. package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
  261. package/dist/orchestration/workflow-completion-inbox.js +283 -32
  262. package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
  263. package/dist/orchestration/workflow-notify-journal.js +187 -13
  264. package/dist/parked-decide.d.ts +61 -0
  265. package/dist/parked-decide.js +58 -1
  266. package/dist/per-task-image.d.ts +54 -0
  267. package/dist/per-task-image.js +41 -0
  268. package/dist/plan-cache-probe.d.ts +35 -0
  269. package/dist/plan-cache-probe.js +18 -3
  270. package/dist/plugins/approval-exemption-store.d.ts +7 -0
  271. package/dist/plugins/approval-exemption-store.js +28 -2
  272. package/dist/plugins/approval-store-sql.d.ts +64 -0
  273. package/dist/plugins/approval-store-sql.js +32 -0
  274. package/dist/plugins/background-agent-store-sql.d.ts +55 -0
  275. package/dist/plugins/background-agent-store-sql.js +91 -4
  276. package/dist/plugins/background-shell-support.d.ts +98 -0
  277. package/dist/plugins/background-shell-support.js +117 -13
  278. package/dist/plugins/blob-backend.d.ts +104 -0
  279. package/dist/plugins/blob-backend.js +163 -6
  280. package/dist/plugins/breaker-state-sql.d.ts +65 -1
  281. package/dist/plugins/breaker-state-sql.js +46 -8
  282. package/dist/plugins/caching-session-store.d.ts +42 -0
  283. package/dist/plugins/caching-session-store.js +63 -3
  284. package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
  285. package/dist/plugins/checkpoint-store-sql.js +239 -11
  286. package/dist/plugins/file-outcome-sink.d.ts +4 -0
  287. package/dist/plugins/file-outcome-sink.js +9 -0
  288. package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
  289. package/dist/plugins/file-resume-anchor-store.js +43 -4
  290. package/dist/plugins/file-run-store.d.ts +90 -0
  291. package/dist/plugins/file-run-store.js +145 -13
  292. package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
  293. package/dist/plugins/file-snapshot-store-sql.js +178 -10
  294. package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
  295. package/dist/plugins/file-workflow-journal-store.js +10 -0
  296. package/dist/plugins/fork-routing-session-store.d.ts +47 -0
  297. package/dist/plugins/fork-routing-session-store.js +51 -3
  298. package/dist/plugins/host-platform.d.ts +69 -0
  299. package/dist/plugins/host-platform.js +81 -2
  300. package/dist/plugins/image-bake-store-sql.d.ts +136 -1
  301. package/dist/plugins/image-bake-store-sql.js +241 -7
  302. package/dist/plugins/image-index-sql.d.ts +46 -0
  303. package/dist/plugins/image-index-sql.js +113 -4
  304. package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
  305. package/dist/plugins/k8s-bg-scripts.js +88 -4
  306. package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
  307. package/dist/plugins/k8s-exec-protocol.js +41 -0
  308. package/dist/plugins/local-checkpoint-store.d.ts +40 -0
  309. package/dist/plugins/local-checkpoint-store.js +86 -7
  310. package/dist/plugins/local-session-store.d.ts +157 -0
  311. package/dist/plugins/local-session-store.js +289 -34
  312. package/dist/plugins/local-task-attachment-store.js +14 -2
  313. package/dist/plugins/mailbox-store-sql.d.ts +38 -0
  314. package/dist/plugins/mailbox-store-sql.js +39 -6
  315. package/dist/plugins/memory-engine-pg.d.ts +30 -0
  316. package/dist/plugins/memory-engine-pg.js +122 -4
  317. package/dist/plugins/memory-engine-tidb.d.ts +26 -0
  318. package/dist/plugins/memory-engine-tidb.js +115 -5
  319. package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
  320. package/dist/plugins/memory-engine-vector-util.js +5 -0
  321. package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
  322. package/dist/plugins/memory-resume-anchor-store.js +17 -0
  323. package/dist/plugins/memory-run-store.d.ts +63 -0
  324. package/dist/plugins/memory-run-store.js +73 -8
  325. package/dist/plugins/memory-session-policy-store.d.ts +17 -0
  326. package/dist/plugins/memory-session-policy-store.js +17 -0
  327. package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
  328. package/dist/plugins/memory-sync-store-pg.js +45 -2
  329. package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
  330. package/dist/plugins/memory-sync-store-tidb.js +31 -1
  331. package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
  332. package/dist/plugins/outcome-ledger-sql.js +97 -3
  333. package/dist/plugins/pg-approval-store.d.ts +7 -0
  334. package/dist/plugins/pg-approval-store.js +7 -0
  335. package/dist/plugins/pg-breaker-state.d.ts +6 -0
  336. package/dist/plugins/pg-breaker-state.js +6 -0
  337. package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
  338. package/dist/plugins/pg-checkpoint-store.js +8 -0
  339. package/dist/plugins/pg-cost-quota.d.ts +17 -0
  340. package/dist/plugins/pg-cost-quota.js +6 -0
  341. package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
  342. package/dist/plugins/pg-file-snapshot-store.js +6 -0
  343. package/dist/plugins/pg-image-bake.d.ts +9 -0
  344. package/dist/plugins/pg-image-bake.js +9 -0
  345. package/dist/plugins/pg-image-index.d.ts +9 -0
  346. package/dist/plugins/pg-image-index.js +9 -0
  347. package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
  348. package/dist/plugins/pg-outcome-ledger.js +9 -0
  349. package/dist/plugins/pg-pool.d.ts +50 -0
  350. package/dist/plugins/pg-pool.js +78 -0
  351. package/dist/plugins/pg-query.d.ts +11 -0
  352. package/dist/plugins/pg-rate-limiter.d.ts +23 -0
  353. package/dist/plugins/pg-rate-limiter.js +12 -2
  354. package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
  355. package/dist/plugins/pg-resume-anchor-store.js +5 -0
  356. package/dist/plugins/pg-run-store.d.ts +7 -0
  357. package/dist/plugins/pg-run-store.js +7 -0
  358. package/dist/plugins/pg-safe-json.d.ts +13 -0
  359. package/dist/plugins/pg-safe-json.js +40 -4
  360. package/dist/plugins/pg-session-policy-store.d.ts +5 -0
  361. package/dist/plugins/pg-session-policy-store.js +5 -0
  362. package/dist/plugins/pg-session-storage.d.ts +72 -0
  363. package/dist/plugins/pg-session-storage.js +152 -20
  364. package/dist/plugins/pg-session-store.d.ts +10 -0
  365. package/dist/plugins/pg-session-store.js +10 -0
  366. package/dist/plugins/pg-tool-result-store.d.ts +7 -0
  367. package/dist/plugins/pg-tool-result-store.js +7 -0
  368. package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
  369. package/dist/plugins/pg-workflow-journal-store.js +7 -0
  370. package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
  371. package/dist/plugins/pg-workflow-run-store.js +7 -0
  372. package/dist/plugins/posix-shell-fs.d.ts +3 -0
  373. package/dist/plugins/posix-shell-fs.js +31 -1
  374. package/dist/plugins/remote-env-adb.d.ts +25 -0
  375. package/dist/plugins/remote-env-adb.js +95 -12
  376. package/dist/plugins/remote-env-e2b.d.ts +146 -0
  377. package/dist/plugins/remote-env-e2b.js +371 -40
  378. package/dist/plugins/remote-env-file-error.d.ts +34 -0
  379. package/dist/plugins/remote-env-file-error.js +37 -0
  380. package/dist/plugins/remote-env-host.d.ts +151 -0
  381. package/dist/plugins/remote-env-host.js +468 -70
  382. package/dist/plugins/remote-env-k8s.d.ts +135 -0
  383. package/dist/plugins/remote-env-k8s.js +318 -32
  384. package/dist/plugins/remote-env-local-docker.d.ts +65 -0
  385. package/dist/plugins/remote-env-local-docker.js +149 -24
  386. package/dist/plugins/remote-env-ssh.d.ts +35 -0
  387. package/dist/plugins/remote-env-ssh.js +117 -20
  388. package/dist/plugins/remote-scratchpad.d.ts +38 -0
  389. package/dist/plugins/remote-scratchpad.js +32 -2
  390. package/dist/plugins/remote-shell.d.ts +22 -0
  391. package/dist/plugins/remote-shell.js +27 -1
  392. package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
  393. package/dist/plugins/resume-anchor-store-sql.js +11 -0
  394. package/dist/plugins/roster-store-sql.d.ts +27 -0
  395. package/dist/plugins/roster-store-sql.js +54 -0
  396. package/dist/plugins/run-store-sql.d.ts +194 -0
  397. package/dist/plugins/run-store-sql.js +210 -9
  398. package/dist/plugins/s3-presign.d.ts +20 -0
  399. package/dist/plugins/s3-presign.js +49 -2
  400. package/dist/plugins/scheduler-support.d.ts +12 -0
  401. package/dist/plugins/scheduler-support.js +75 -3
  402. package/dist/plugins/send-file-ledger.d.ts +15 -0
  403. package/dist/plugins/send-file-ledger.js +53 -4
  404. package/dist/plugins/send-user-file.d.ts +77 -0
  405. package/dist/plugins/send-user-file.js +92 -4
  406. package/dist/plugins/session-policy-store-sql.d.ts +15 -0
  407. package/dist/plugins/session-policy-store-sql.js +75 -3
  408. package/dist/plugins/session-store.d.ts +45 -0
  409. package/dist/plugins/session-store.js +51 -0
  410. package/dist/plugins/sql-driver.d.ts +77 -0
  411. package/dist/plugins/sql-driver.js +4 -0
  412. package/dist/plugins/sql-escape.d.ts +8 -0
  413. package/dist/plugins/sql-escape.js +8 -0
  414. package/dist/plugins/sql-row-helpers.d.ts +25 -0
  415. package/dist/plugins/sql-row-helpers.js +25 -0
  416. package/dist/plugins/store-backend.d.ts +103 -0
  417. package/dist/plugins/store-backend.js +120 -27
  418. package/dist/plugins/store-contracts.d.ts +62 -0
  419. package/dist/plugins/store-contracts.js +3 -0
  420. package/dist/plugins/task-attachment-store.d.ts +60 -0
  421. package/dist/plugins/task-attachment-store.js +49 -3
  422. package/dist/plugins/task-list-store-sql.d.ts +25 -0
  423. package/dist/plugins/task-list-store-sql.js +21 -0
  424. package/dist/plugins/tidb-approval-store.d.ts +6 -0
  425. package/dist/plugins/tidb-approval-store.js +6 -0
  426. package/dist/plugins/tidb-breaker-state.d.ts +5 -0
  427. package/dist/plugins/tidb-breaker-state.js +5 -0
  428. package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
  429. package/dist/plugins/tidb-checkpoint-store.js +7 -0
  430. package/dist/plugins/tidb-cost-quota.d.ts +6 -0
  431. package/dist/plugins/tidb-cost-quota.js +1 -0
  432. package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
  433. package/dist/plugins/tidb-file-snapshot-store.js +6 -0
  434. package/dist/plugins/tidb-image-bake.d.ts +8 -0
  435. package/dist/plugins/tidb-image-bake.js +8 -0
  436. package/dist/plugins/tidb-image-index.d.ts +8 -0
  437. package/dist/plugins/tidb-image-index.js +8 -0
  438. package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
  439. package/dist/plugins/tidb-outcome-ledger.js +10 -0
  440. package/dist/plugins/tidb-pool.d.ts +56 -0
  441. package/dist/plugins/tidb-pool.js +181 -1
  442. package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
  443. package/dist/plugins/tidb-rate-limiter.js +5 -2
  444. package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
  445. package/dist/plugins/tidb-resume-anchor-store.js +5 -0
  446. package/dist/plugins/tidb-run-store.d.ts +7 -0
  447. package/dist/plugins/tidb-run-store.js +7 -0
  448. package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
  449. package/dist/plugins/tidb-session-policy-store.js +5 -0
  450. package/dist/plugins/tidb-session-storage.d.ts +43 -0
  451. package/dist/plugins/tidb-session-storage.js +62 -3
  452. package/dist/plugins/tidb-session-store.d.ts +142 -0
  453. package/dist/plugins/tidb-session-store.js +264 -16
  454. package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
  455. package/dist/plugins/tidb-tool-result-store.js +7 -0
  456. package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
  457. package/dist/plugins/tidb-workflow-journal-store.js +7 -0
  458. package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
  459. package/dist/plugins/tidb-workflow-run-store.js +8 -0
  460. package/dist/plugins/tool-result-store-sql.d.ts +58 -0
  461. package/dist/plugins/tool-result-store-sql.js +38 -2
  462. package/dist/plugins/web-search.d.ts +35 -0
  463. package/dist/plugins/web-search.js +45 -9
  464. package/dist/plugins/workflow-journal-limits.d.ts +10 -0
  465. package/dist/plugins/workflow-journal-limits.js +10 -0
  466. package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
  467. package/dist/plugins/workflow-journal-store-sql.js +59 -3
  468. package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
  469. package/dist/plugins/workflow-run-store-sql.js +84 -6
  470. package/dist/plugins/worktree-isolation.d.ts +45 -0
  471. package/dist/plugins/worktree-isolation.js +105 -6
  472. package/dist/plugins/write-behind-counter.d.ts +56 -1
  473. package/dist/plugins/write-behind-counter.js +53 -14
  474. package/dist/principal-jwt.d.ts +24 -0
  475. package/dist/principal-jwt.js +60 -5
  476. package/dist/project-memory.d.ts +22 -0
  477. package/dist/project-memory.js +144 -15
  478. package/dist/prompts-domain-validate.d.ts +28 -0
  479. package/dist/prompts-domain-validate.js +42 -1
  480. package/dist/question.d.ts +33 -0
  481. package/dist/question.js +69 -2
  482. package/dist/resource-suspend.d.ts +21 -0
  483. package/dist/resource-suspend.js +16 -0
  484. package/dist/router/route-orchestration.d.ts +78 -0
  485. package/dist/router/route-orchestration.js +77 -0
  486. package/dist/run-local.d.ts +32 -0
  487. package/dist/run-local.js +172 -8
  488. package/dist/runs.d.ts +189 -4
  489. package/dist/runs.js +465 -24
  490. package/dist/runtime-caps-resolver.d.ts +106 -0
  491. package/dist/runtime-caps-resolver.js +89 -4
  492. package/dist/runtime-governance.d.ts +84 -0
  493. package/dist/runtime-governance.js +127 -1
  494. package/dist/sandbox-pkg-source.d.ts +23 -0
  495. package/dist/sandbox-pkg-source.js +37 -0
  496. package/dist/sealed-key.d.ts +54 -0
  497. package/dist/sealed-key.js +68 -3
  498. package/dist/security.d.ts +108 -0
  499. package/dist/security.js +145 -6
  500. package/dist/sema-registry.d.ts +21 -0
  501. package/dist/sema-registry.js +21 -0
  502. package/dist/session-leaf-bus.d.ts +31 -0
  503. package/dist/session-leaf-bus.js +32 -0
  504. package/dist/session-sync-content.d.ts +46 -0
  505. package/dist/session-sync-content.js +47 -1
  506. package/dist/session-sync-kernel.d.ts +89 -0
  507. package/dist/session-sync-kernel.js +48 -3
  508. package/dist/session-sync.d.ts +125 -0
  509. package/dist/session-sync.js +181 -8
  510. package/dist/session-titler.d.ts +26 -0
  511. package/dist/session-titler.js +41 -7
  512. package/dist/session-watch.d.ts +53 -1
  513. package/dist/session-watch.js +73 -9
  514. package/dist/sighup-idle.d.ts +30 -0
  515. package/dist/sighup-idle.js +12 -1
  516. package/dist/spec-fields.d.ts +91 -3
  517. package/dist/spec-fields.js +116 -4
  518. package/dist/task-cwd.d.ts +50 -0
  519. package/dist/task-cwd.js +68 -1
  520. package/dist/task-mcp.d.ts +47 -0
  521. package/dist/task-mcp.js +29 -0
  522. package/dist/task-settings.d.ts +175 -0
  523. package/dist/task-settings.js +226 -11
  524. package/dist/task-workflow.d.ts +67 -0
  525. package/dist/task-workflow.js +75 -8
  526. package/dist/tool-approval.d.ts +91 -0
  527. package/dist/tool-approval.js +215 -0
  528. package/dist/trace/artifacts.d.ts +9 -0
  529. package/dist/trace/artifacts.js +30 -5
  530. package/dist/trace/core-keyset-guard.d.ts +18 -0
  531. package/dist/trace/ledger-sink.d.ts +43 -0
  532. package/dist/trace/ledger-sink.js +64 -5
  533. package/dist/trace/project.d.ts +133 -0
  534. package/dist/trace/project.js +189 -2
  535. package/dist/trace/redact.d.ts +19 -0
  536. package/dist/trace/redact.js +66 -9
  537. package/dist/usage-analytics.d.ts +26 -0
  538. package/dist/usage-analytics.js +26 -3
  539. package/package.json +1 -1
@@ -1,8 +1,56 @@
1
+ /**
2
+ * Sandbox-image-pool INDEX store (IMAGE-API-DESIGN.md §1, pool design §8) — SINGLE-FILE DUAL-DIALECT
3
+ * (design/158 A12 定型半场). ONE implementation, TWO dialects; the historical `TiDBImageIndex` /
4
+ * `PgImageIndex` class names survive as thin ctor subclasses so every consumer (store-backend.ts,
5
+ * pg-pool.ts, the unit + real-DB suites) is untouched.
6
+ *
7
+ * One row per published/building image DIGEST — the immutable runtime ref (design §6: refs are @sha256:...).
8
+ * A queryable projection of build.sh's replay-sufficient `sandbox-manifest.json` + the publish lifecycle
9
+ * `status` (building → published → deprecated / failed). This is the SINGLE SOURCE OF TRUTH for "what can a
10
+ * sandbox run"; `POST /v1/images/select` resolves a profile → digest off it (the §7 per-sandbox hot path).
11
+ *
12
+ * Visibility is enforced IN THE QUERY (Codex A1, mirrors the assistant per-user scoping): a tenant principal
13
+ * sees `public` rows + its OWN `tenant` rows only; an operator sees all. mapRow/ImageRow are the row-mapping
14
+ * surface shared by BOTH dialects — they live in the backend-neutral store-contracts.ts (design/158 A3 沉底).
15
+ *
16
+ * ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
17
+ * - `?` placeholders vs positional `$n` (and the placeholder-arity fallout in `list`/`setStatus`, which
18
+ * track a running `$n` index; TiDB's `?` needs no such bookkeeping).
19
+ * - upsert form: `ON DUPLICATE KEY UPDATE … VALUES(col)` vs `ON CONFLICT (id) DO UPDATE SET … EXCLUDED.col`.
20
+ * - the MONOTONIC de-publish guard (IMAGE-API-DESIGN.md §P2.10 / wq64gmm5e): `IF(status='published', old,
21
+ * new)` vs `CASE WHEN … status = 'published' THEN old ELSE new END` — status/visibility/tenant_id/signed
22
+ * are ALL preserved on a live published row in BOTH dialects.
23
+ * - JSON columns: TiDB `JSON.stringify` verbatim vs PG `pgSafeJsonStringify` + `$n::jsonb` casts (lossy —
24
+ * this is an observation-layer blob, not a trust-boundary payload, so `pgSafeJsonStringify`'s U+FFFD
25
+ * replacement is acceptable; codex R15).
26
+ * - PG-only identity-axis validation (codex R17-H1/R19): `repo` (the `idFor` hash input + the `(repo,digest)`
27
+ * unique-key equivalence premise) and `tenantId` reject unstorable bytes UP FRONT (`pgHasUnstorable` →
28
+ * `PgUnstorableError`) rather than silently mangling them — a rewrite would fork the hash id from the
29
+ * stored column (same `(repo,digest)` collides the unique key while `ON CONFLICT (id)` doesn't match,
30
+ * wedging registration permanently). TiDB has no equivalent NUL/lone-surrogate constraint, so it stores
31
+ * verbatim. Same-family scalar sanitize (tag/recipeGitSha/generatorVersion/supersedes — codex R16) is
32
+ * PG-only for the same reason; identity columns (repo/tenantId/digest/manifestSha) are reject-not-rewrite.
33
+ * - null-safe ordering trick: `ORDER BY (build_date IS NULL), build_date DESC` vs `ORDER BY build_date DESC
34
+ * NULLS LAST` (same semantics, dialect-native syntax).
35
+ * - capability-flag predicate: `JSON_EXTRACT(capabilities, '$.key') = true` vs `capabilities -> 'key' =
36
+ * 'true'::jsonb` (NOT a `::boolean` cast, which hard-errors on non-boolean jsonb and aborts the query;
37
+ * the jsonb equality yields false instead, matching MySQL's exclude-don't-throw semantics for a present
38
+ * but non-boolean capability value, e.g. `nestedBuildMode`).
39
+ * - affectedRows vs rowCount, BIGINT (`size_bytes`) read back as a STRING on PG (via SqlDriver / mapImageRow).
40
+ * - schema ownership: TiDB DDL lives in tidb-pool.ts; the PG DDL is exported from HERE
41
+ * (PG_IMAGE_INDEX_SCHEMA + ensureSchema, self-contained for the integration test).
42
+ */
1
43
  import { createHash } from "node:crypto";
2
44
  import { pgSafeJsonStringify, pgSanitizeText, pgHasUnstorable, PgUnstorableError } from "./pg-safe-json.js";
3
- import { mapImageRow as mapRow, } from "./store-contracts.js";
45
+ import {
46
+ // mapRow/ImageRow are the row-mapping surface shared by BOTH SQL twins — they live in the
47
+ // backend-neutral store-contracts.ts (design/158 A3 沉底; no adapter imports its twin).
48
+ mapImageRow as mapRow, } from "./store-contracts.js";
4
49
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
5
50
  export { mapImageRow as mapRow } from "./store-contracts.js";
51
+ /** PG translation of the `sandbox_image_index` DDL in tidb-pool.ts (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3),
52
+ * TINYINT(1)→SMALLINT(布尔列 PG 映射归一,clay 批 2026-07-26 —— 两方言读回同为 number,写入 0/1,
53
+ * 读侧 `!!` 保持,出口 JS 类型不变), inline UNIQUE KEY/KEY → separate CREATE UNIQUE INDEX / CREATE INDEX). */
6
54
  export const PG_IMAGE_INDEX_SCHEMA = [
7
55
  `CREATE TABLE IF NOT EXISTS sandbox_image_index (
8
56
  id VARCHAR(64) NOT NULL,
@@ -33,12 +81,14 @@ export const PG_IMAGE_INDEX_SCHEMA = [
33
81
  `CREATE INDEX IF NOT EXISTS idx_digest ON sandbox_image_index (digest)`,
34
82
  `CREATE INDEX IF NOT EXISTS idx_tenant ON sandbox_image_index (tenant_id, status)`,
35
83
  ];
84
+ /** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
36
85
  export async function ensureSchema(pool) {
37
86
  for (const stmt of PG_IMAGE_INDEX_SCHEMA)
38
87
  await pool.query(stmt);
39
88
  }
40
89
  const MAX_LIMIT = 200;
41
90
  const DEFAULT_LIMIT = 50;
91
+ /** `(visibility='public' OR tenant_id=?)` unless operator. TiDB form — `?` needs no index bookkeeping. */
42
92
  function visibilityClauseTidb(viewer) {
43
93
  if (viewer.operator)
44
94
  return { sql: "1=1", params: [] };
@@ -46,6 +96,7 @@ function visibilityClauseTidb(viewer) {
46
96
  return { sql: "(visibility='public' OR tenant_id=?)", params: [viewer.tenantId] };
47
97
  return { sql: "visibility='public'", params: [] };
48
98
  }
99
+ /** PG form — positional `$n`, with a running index the caller continues from (PG has no anonymous `?`). */
49
100
  function visibilityClausePg(viewer, startIdx) {
50
101
  if (viewer.operator)
51
102
  return { sql: "1=1", params: [], nextIdx: startIdx };
@@ -71,6 +122,7 @@ function decodeCursor(c) {
71
122
  return null;
72
123
  }
73
124
  }
125
+ /** TiDB upsert/registerBuilding params — the array literal is IDENTICAL text at both TiDB call sites. */
74
126
  function tidbUpsertParams(e, id, now) {
75
127
  return [
76
128
  id, e.profile, JSON.stringify(e.bands ?? []), e.repo, e.tag, e.digest,
@@ -85,12 +137,17 @@ const PG_UPSERT_COLS = "(id, profile, bands, repo, tag, digest, toolchain_versio
85
137
  "status, visibility, tenant_id, manifest_sha, recipe_git_sha, generator_version, build_date, supersedes, " +
86
138
  "signed, created_at, updated_at)";
87
139
  const PG_UPSERT_VALUES = "($1,$2,$3::jsonb,$4,$5,$6,$7::jsonb,$8::jsonb,$9::jsonb,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)";
140
+ /** PG upsert/registerBuilding params — deep-sanitized JSON (observation-layer, R15) + same-family scalar
141
+ * sanitize (R16); identity columns (repo/tenantId/digest/manifestSha) are reject-not-rewrite (validated by
142
+ * the caller BEFORE this runs — see the `pgHasUnstorable` guards in `upsert`/`registerBuilding`). */
88
143
  function pgUpsertParams(e, id, now) {
89
144
  return [
90
145
  id,
91
146
  pgSanitizeText(e.profile),
92
- pgSafeJsonStringify(e.bands ?? []),
93
- e.repo,
147
+ pgSafeJsonStringify(e.bands ?? []), // R15:元数据 jsonb 四位=观测层(嵌套值可含外部构建产物文本)
148
+ // codex R16:register 面标量只做类型检查——tag/tenantId/generatorVersion/supersedes 可携脏字节,
149
+ // 同族观测标量一并 sanitize(digest/manifestSha 恒 hex 恒等;identity 值 sanitize 恒等=零行为差)。
150
+ e.repo, // R17-H1:repo=身份(idFor 哈希输入)——入口拒绝式,列存原文(见 upsert/registerBuilding)
94
151
  pgSanitizeText(e.tag),
95
152
  e.digest,
96
153
  pgSafeJsonStringify(e.toolchainVersions ?? {}),
@@ -99,7 +156,7 @@ function pgUpsertParams(e, id, now) {
99
156
  e.sizeBytes,
100
157
  e.status,
101
158
  e.visibility,
102
- e.tenantId ?? null,
159
+ e.tenantId ?? null, // R19:tenantId=租户身份轴——入口拒绝式(改写=可见性归属漂移),列存原文
103
160
  e.manifestSha ?? null,
104
161
  e.recipeGitSha == null ? null : pgSanitizeText(e.recipeGitSha),
105
162
  e.generatorVersion == null ? null : pgSanitizeText(e.generatorVersion),
@@ -110,18 +167,24 @@ function pgUpsertParams(e, id, now) {
110
167
  now,
111
168
  ];
112
169
  }
170
+ /** Dual-dialect INDEX store. See the file header for the dialect-delta ledger. */
113
171
  export class SqlImageIndex {
114
172
  db;
115
173
  constructor(db) {
116
174
  this.db = db;
117
175
  }
176
+ /** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
118
177
  q(tidb, pg) {
119
178
  return this.db.dialect === "tidb" ? tidb : pg;
120
179
  }
180
+ /** Stable index id = sha256(repo@digest) — identity is the digest, not the mutable tag. */
121
181
  static idFor(repo, digest) {
122
182
  return createHash("sha256").update(`${repo}@${digest}`).digest("hex").slice(0, 64);
123
183
  }
184
+ /** Register or update an index row (idempotent on repo+digest). Returns the row id. */
124
185
  async upsert(e) {
186
+ // 🔴 PG-only identity-axis validation (codex R17-H1/R19) — TiDB has no NUL/lone-surrogate column
187
+ // constraint, so it stores repo/tenantId verbatim; PG rejects up front (see file-header dialect delta).
125
188
  if (this.db.dialect === "pg") {
126
189
  if (pgHasUnstorable(e.repo))
127
190
  throw new PgUnstorableError("image repo (identity)");
@@ -153,7 +216,31 @@ export class SqlImageIndex {
153
216
  supersedes = EXCLUDED.supersedes, signed = EXCLUDED.signed, updated_at = EXCLUDED.updated_at`), this.db.dialect === "tidb" ? tidbUpsertParams(e, id, now) : pgUpsertParams(e, id, now));
154
217
  return id;
155
218
  }
219
+ /**
220
+ * STATUS-MONOTONIC register (IMAGE-API-DESIGN.md §P2.10 MUST-FIX) — the auto-register path the bake runner uses.
221
+ *
222
+ * `upsert` does an UNCONDITIONAL upsert: a re-ingested `done` (at-least-once from the runner) or a P2 bake of
223
+ * a digest P3 already PROMOTED would flip a live `published` row back to `building`, and `latestPublished`
224
+ * (`WHERE status='published'`) then DE-PUBLISHES a live, in-use image → sandbox-create 404s. This guarded path
225
+ * NEVER downgrades a `published` row: on a fresh row it inserts (`status:'building'` by default); on an
226
+ * existing row it updates the manifest projection + bumps `updated_at` but writes `status` only when the row
227
+ * isn't already `published` — a late/duplicate `done` for an already-promoted digest is a no-op on the
228
+ * lifecycle, never a regression. Returns the row id.
229
+ *
230
+ * Distinct from the unconditional `upsert` (kept for the seed + the operator `/v1/images/register` path); this
231
+ * is the bake auto-register's ONLY write into the index. Default status is `building` (P2 registers
232
+ * node-local/quarantine `building` artifacts; P3's verify gate is what flips `building → published`).
233
+ *
234
+ * The `IF(...published...)` (TiDB) / `CASE WHEN...published...` (PG) guards are the monotonic de-publish
235
+ * defense: once a row is `published` and live, a re-register must NOT downgrade `status` NOR flip the
236
+ * columns `latestPublished` filters on. wq64gmm5e: guarding only `status` left `visibility`/`tenant_id`
237
+ * (and `signed`) overwritable, so a re-register with visibility=private/tenant or a different tenant_id
238
+ * silently dropped a live row out of every viewer's `latestPublished` — an effective de-publish. ALL FOUR
239
+ * (status/visibility/tenant_id/signed) are gated identically in BOTH dialects; non-published rows still
240
+ * take the freshest projection.
241
+ */
156
242
  async registerBuilding(e) {
243
+ // 🔴 PG-only identity-axis validation (codex R17-H1/R19) — same rationale as `upsert` above.
157
244
  if (this.db.dialect === "pg") {
158
245
  if (pgHasUnstorable(e.repo))
159
246
  throw new PgUnstorableError("image repo (identity)");
@@ -199,10 +286,15 @@ export class SqlImageIndex {
199
286
  const { rows } = await this.db.query(this.q("SELECT * FROM sandbox_image_index WHERE id=? LIMIT 1", "SELECT * FROM sandbox_image_index WHERE id = $1 LIMIT 1"), [id]);
200
287
  return rows[0] ? mapRow(rows[0]) : null;
201
288
  }
289
+ /** By immutable digest — what a running sandbox was built from. */
202
290
  async getByDigest(digest) {
203
291
  const { rows } = await this.db.query(this.q("SELECT * FROM sandbox_image_index WHERE digest=? ORDER BY updated_at DESC LIMIT 1", "SELECT * FROM sandbox_image_index WHERE digest = $1 ORDER BY updated_at DESC LIMIT 1"), [digest]);
204
292
  return rows[0] ? mapRow(rows[0]) : null;
205
293
  }
294
+ /** The newest PUBLISHED, viewer-visible digest for a profile — the `select` resolution.
295
+ * 🔴 genuinely-divergent shape (not just SQL text): TiDB's `visibilityClauseTidb` needs no placeholder
296
+ * index (anonymous `?`); PG's `visibilityClausePg` threads a running `$n` index the caller continues
297
+ * from. Also the null-safe ORDER BY trick differs ((build_date IS NULL) vs NULLS LAST) — same semantics. */
206
298
  async latestPublished(profile, viewer) {
207
299
  if (this.db.dialect === "tidb") {
208
300
  const vis = visibilityClauseTidb(viewer);
@@ -217,6 +309,13 @@ export class SqlImageIndex {
217
309
  ORDER BY build_date DESC NULLS LAST, created_at DESC LIMIT 1`, [profile, ...vis.params]);
218
310
  return rows[0] ? mapRow(rows[0]) : null;
219
311
  }
312
+ /**
313
+ * Paginated, viewer-scoped catalog query (the UI's list). Keyset cursor on (created_at, id) desc.
314
+ * 🔴 genuinely-divergent shape: the PG arm threads an explicit `$n` index through every optional predicate
315
+ * (node-pg has no anonymous placeholder); the TiDB arm just appends `?` in call order. Kept as two full
316
+ * branches rather than a shared placeholder-numbering helper (sql-driver.ts STEP 3/4) — a reviewer sees each
317
+ * dialect's real query shape, not a generated abstraction over it.
318
+ */
220
319
  async list(filter) {
221
320
  const limit = Math.min(Math.max(1, filter.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
222
321
  let rawRows;
@@ -233,6 +332,7 @@ export class SqlImageIndex {
233
332
  params.push(filter.status);
234
333
  }
235
334
  if (filter.capability) {
335
+ // JSON boolean true for the capability key.
236
336
  where.push("JSON_EXTRACT(capabilities, ?) = true");
237
337
  params.push(`$.${filter.capability}`);
238
338
  }
@@ -262,6 +362,11 @@ export class SqlImageIndex {
262
362
  params.push(filter.status);
263
363
  }
264
364
  if (filter.capability) {
365
+ // JSON boolean true for the capability key. Compare at the jsonb layer (NOT a ::boolean cast): a
366
+ // `::boolean` cast HARD-ERRORS on any non-boolean jsonb value (string/number/JSON null) and aborts the
367
+ // whole query, whereas `= 'true'::jsonb` yields false for those — faithfully reproducing MySQL's
368
+ // `JSON_EXTRACT(...) = true` semantics (a present-but-non-boolean cap, e.g.
369
+ // nestedBuildMode="rootless-buildkit", simply excludes the row).
265
370
  where.push(`capabilities -> $${idx++} = 'true'::jsonb`);
266
371
  params.push(filter.capability);
267
372
  }
@@ -287,11 +392,13 @@ export class SqlImageIndex {
287
392
  nextCursor = encodeCursor({ createdAt: last.createdAt, id: last.id });
288
393
  }
289
394
  if (filter.latestOnly) {
395
+ // collapse to the newest row per profile (rows are already build/created-desc within the page).
290
396
  const seen = new Set();
291
397
  entries = entries.filter((e) => (seen.has(e.profile) ? false : (seen.add(e.profile), true)));
292
398
  }
293
399
  return { entries, nextCursor };
294
400
  }
401
+ /** Lifecycle transition (atomic-publish: building→published, or →failed/deprecated). */
295
402
  async setStatus(id, status, opts) {
296
403
  if (this.db.dialect === "tidb") {
297
404
  const sets = ["status=?", "updated_at=?"];
@@ -315,11 +422,13 @@ export class SqlImageIndex {
315
422
  await this.db.query(`UPDATE sandbox_image_index SET ${sets.join(", ")} WHERE id = $${idx}`, params);
316
423
  }
317
424
  }
425
+ /** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
318
426
  export class TiDBImageIndex extends SqlImageIndex {
319
427
  constructor(pool) {
320
428
  super(mysqlDriver(pool));
321
429
  }
322
430
  }
431
+ /** PostgreSQL binding — historical class name + ctor shape preserved. */
323
432
  export class PgImageIndex extends SqlImageIndex {
324
433
  constructor(pool) {
325
434
  super(pgDriver(pool));
@@ -1,9 +1,42 @@
1
+ /** The detached runner (base64'd into the pod, run under `setsid`). Self-reports pgid; streams output to out/err. */
1
2
  export declare function buildBgRunnerScript(dir: string): string;
3
+ /** Short foreground launcher: writes cmd.sh + runner.sh, then `setsid`s the runner detached and returns immediately. */
2
4
  export declare function buildBgLauncherScript(dir: string, cmdShB64: string, runnerShB64: string): string;
5
+ /** Poll script: emit totals + liveness + exit, then the new bytes per stream (base64, bounded by `readCap`). */
3
6
  export declare function buildBgPollScript(dir: string, stdoutCursor: number, stderrCursor: number, readCap: number): string;
7
+ /** KillShell: graceful SIGTERM to the group, then a forced SIGKILL (idempotent — a dead group just errors out).
8
+ * The `|| kill <pid>` fallback covers an ADOPTED exec whose pgid prelude self-reported `$$` without a `setsid`
9
+ * (whether the k8s-exec'd sh is a group leader is runtime-dependent); a launch-mode runner is always setsid'd,
10
+ * so its group kill succeeds and the fallback never fires there. */
4
11
  export declare function buildBgKillScript(dir: string): string;
12
+ /** dispose: forced SIGKILL to the group + remove the job dir (no grace — teardown). Best-effort.
13
+ * ⚠️ Deliberately NO pgid-wait (unlike {@link buildBgKillScript}, review #4): a create-then-abort adopt disposed
14
+ * in the pre-mkdir window escapes THIS script — but every dispose path ends in pod deletion (task-end destroy;
15
+ * suspendVM deletes the pod after the snapshot), so the escape is pod-lifetime-bounded, and a wait here would
16
+ * add 2s × N shells to the suspend/teardown path for a sub-second window. Kill (the model-facing wall) waits. */
5
17
  export declare function buildBgDisposeScript(dir: string): string;
18
+ /**
19
+ * design/116 detach: wrap a foreground exec so it is ADOPTABLE mid-flight, running the command as a real
20
+ * process-group LEADER so an adopted shell's KillShell / BG hard wall reap the WHOLE subtree.
21
+ *
22
+ * 🔴 review MED (why `setsid`, not bare `$$`): the wrapper `sh` that `kubectl exec` starts has a RUNTIME-DEPENDENT
23
+ * process group — under many container runtimes it is NOT its own group leader (its pgid is some ancestor's), so
24
+ * writing `$$` as the pgid and later `kill -$$` targets a group that does not exist → the fallback `kill $$` reaps
25
+ * only the wrapper, orphaning the command's children PAST the §3.6 hard wall (they keep streaming into the adopted
26
+ * shell's buffer until the pod's `activeDeadlineSeconds`). `setsid` starts the command in a NEW session ⇒ a new
27
+ * process group whose leader pid IS the backgrounded job pid, so `kill -<pid>` reaps the command and every
28
+ * descendant. The command's stdout/stderr fds (the exec socket's channels) are INHERITED across setsid — only the
29
+ * controlling terminal is dropped (there is none: kubectl-exec here is non-tty) — so output/exit still flow on the
30
+ * SAME WebSocket, which IS the adopted shell's feed. `wait` propagates the real exit code.
31
+ *
32
+ * The command is base64'd into a pod-side `cmd.sh` (no quoting hazard, mirrors the launch path). The pgid is written
33
+ * atomically (tmp+mv) AFTER the job forks, so a poll never reads a half-written id. Every step is best-effort: a pod
34
+ * without `setsid` (rare on the kata base image) degrades to the `$$` path (the pre-fix behavior), and a read-only
35
+ * /tmp degrades to a pgid-less adopt (kill no-ops; the disposable pod's teardown reaps) — never a broken exec. The
36
+ * EXIT trap removes the dir when the command ends normally, so non-detached execs don't litter the pod.
37
+ */
6
38
  export declare function buildDetachCapableExec(dir: string, command: string): string;
39
+ /** Parsed background poll response. `gone` = the job dir vanished (abnormal). Byte counts drive cursor/dropped accounting. */
7
40
  export interface ParsedBgPoll {
8
41
  gone: boolean;
9
42
  outTotal: number;
@@ -15,5 +48,6 @@ export interface ParsedBgPoll {
15
48
  stderr: string;
16
49
  stderrBytes: number;
17
50
  }
51
+ /** Parse {@link buildBgPollScript} stdout. Markers contain `_` (absent from the base64 alphabet) so they never collide. */
18
52
  export declare function parseBgPollOutput(raw: string): ParsedBgPoll | undefined;
19
53
  //# sourceMappingURL=k8s-bg-scripts.d.ts.map
@@ -1,13 +1,36 @@
1
+ /**
2
+ * design/103 background-shell pod-side scripts (pure, exported for testing) + design/116 detach-capable exec —
3
+ * the k8s adapter's text generation/parsing layer for background shells (`buildBg*Script` ×5 +
4
+ * `buildDetachCapableExec` + `parseBgPollOutput`). Split out of `remote-env-k8s.ts` (design/158 A13,
5
+ * internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
6
+ *
7
+ * The pod-side runner is `setsid`'d into its OWN session so the whole job (cmd + its grandchildren) shares one
8
+ * process group we can group-kill (`kill -<pgid>`); the session leader self-reports its pgid (`$$`) so we never
9
+ * depend on `setsid &`'s `$!` (which is the short-lived parent). The command writes DIRECTLY to pod files (out/err),
10
+ * re-read by byte cursor each poll (path ① — survives the consume-once exec seam).
11
+ *
12
+ * 🔴 Why DIRECT redirect (`> out 2> err`), not a FIFO+`head` cap: on-box kata verification proved a FIFO+`head`
13
+ * pipeline block-buffers `head`'s stdout → the `out` file stays EMPTY until head's stdio buffer fills (KBs) or EOF,
14
+ * so `pollBackground` sees nothing for a slow streamer (the whole dev-server use case). Direct redirect makes the
15
+ * shell write each `echo` straight to the file → immediately pollable (the command's OWN buffering still applies,
16
+ * same as TOC/E2B's pipe). DISK bound: the pod has no native ephemeral cap, so a runaway producer is bounded by
17
+ * (a) the BG-timeout hard wall + pod `activeDeadlineSeconds`, and (b) a control-plane size guard in the driver's
18
+ * read() that reaps a job whose output exceeds `K8S_BG_FILE_CAP` (kata VM isolation already contains the blast to
19
+ * this one disposable pod). per-poll TRANSFER stays bounded by the readCap in buildBgPollScript regardless.
20
+ */
1
21
  import { shellQuote } from "./remote-shell.js";
22
+ /** The detached runner (base64'd into the pod, run under `setsid`). Self-reports pgid; streams output to out/err. */
2
23
  export function buildBgRunnerScript(dir) {
3
24
  const q = (name) => shellQuote(`${dir}/${name}`);
4
25
  return [
26
+ // 🔴 self-report the pgid: $$ is this (session-leader) shell's pid == the session/group id. Atomic write.
5
27
  `echo $$ > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`,
6
- `sh ${q("cmd.sh")} > ${q("out")} 2> ${q("err")} < /dev/null`,
28
+ `sh ${q("cmd.sh")} > ${q("out")} 2> ${q("err")} < /dev/null`, // direct redirect → streaming-visible output
7
29
  `__RC=$?`,
8
- `echo $__RC > ${q("exit.tmp")} 2>/dev/null && mv ${q("exit.tmp")} ${q("exit")} 2>/dev/null`,
30
+ `echo $__RC > ${q("exit.tmp")} 2>/dev/null && mv ${q("exit.tmp")} ${q("exit")} 2>/dev/null`, // exit sentinel = atomic
9
31
  ].join("\n");
10
32
  }
33
+ /** Short foreground launcher: writes cmd.sh + runner.sh, then `setsid`s the runner detached and returns immediately. */
11
34
  export function buildBgLauncherScript(dir, cmdShB64, runnerShB64) {
12
35
  const d = shellQuote(dir);
13
36
  const q = (name) => shellQuote(`${dir}/${name}`);
@@ -15,14 +38,28 @@ export function buildBgLauncherScript(dir, cmdShB64, runnerShB64) {
15
38
  `mkdir -p ${d} && chmod 700 ${d}`,
16
39
  `printf %s '${cmdShB64}' | base64 -d > ${q("cmd.sh")}`,
17
40
  `printf %s '${runnerShB64}' | base64 -d > ${q("runner.sh")}`,
41
+ // detach into a new session (survives the exec socket close — disposable-pod posture / startBackground precedent)
18
42
  `setsid sh ${q("runner.sh")} >/dev/null 2>&1 &`,
43
+ // 🔴 Do NOT report launched until the runner has self-reported its pgid — otherwise a killBackground
44
+ // / timeout landing in the gap finds no pgid, no-ops, yet the manager marks the shell killed → a LEAKED process
45
+ // group. Bounded (~2s); the runner writes pgid as its very FIRST action, so this is one iteration in practice.
19
46
  `__i=0; while [ ! -s ${q("pgid")} ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
47
+ // 🔴 (workflow MED) Only report launched if the runner actually started (pgid present). If setsid is missing or the
48
+ // runner crashed before writing pgid, report failure → the driver surfaces spawn_failed instead of a phantom
49
+ // "running" shell that never ends until the BG timeout.
20
50
  `if [ -s ${q("pgid")} ]; then echo SEMABG_LAUNCHED; else echo SEMABG_NOPGID; fi`,
21
51
  ].join("\n");
22
52
  }
53
+ /** Poll script: emit totals + liveness + exit, then the new bytes per stream (base64, bounded by `readCap`). */
23
54
  export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
24
55
  const d = shellQuote(dir);
25
- const slice = (file, cursor) => [
56
+ const slice = (file, cursor) =>
57
+ // 🔴 (workflow MED) Read is bounded to the wc SNAPSHOT (__T), not to the live EOF — `head -c` caps the tail so a
58
+ // producer writing between `wc -c` and `tail` cannot return bytes past __T (the driver advances the cursor to __T,
59
+ // so reading past it would DUPLICATE next poll AND defeat the readCap transfer bound). ≤readCap new bytes → the
60
+ // whole increment [cursor, __T); otherwise the LAST readCap bytes [__T-readCap, __T) (recent tail, like TOC) and
61
+ // the control plane records the gap as bytesDroppedBeforeCursor.
62
+ [
26
63
  `__N=$((__T - ${cursor}))`,
27
64
  `if [ "$__N" -gt 0 ]; then`,
28
65
  ` if [ "$__N" -le ${readCap} ]; then tail -c +$(( ${cursor} + 1 )) ${d}/${file} 2>/dev/null | head -c "$__N" | base64;`,
@@ -31,6 +68,8 @@ export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
31
68
  ].join("\n");
32
69
  return [
33
70
  `if [ ! -d ${d} ]; then echo SEMABG_GONE; exit 0; fi`,
71
+ // `$(( ... + 0 ))` normalizes the byte count: it both defaults empty→0 AND strips the leading whitespace that
72
+ // BSD/macOS `wc -c < file` emits (GNU/Debian pods don't pad, but keep the META line parseable on either).
34
73
  `__OT=$(wc -c < ${d}/out 2>/dev/null); __OT=$(( \${__OT:-0} + 0 ))`,
35
74
  `__ET=$(wc -c < ${d}/err 2>/dev/null); __ET=$(( \${__ET:-0} + 0 ))`,
36
75
  `__EX=$( [ -f ${d}/exit ] && cat ${d}/exit 2>/dev/null || echo - )`,
@@ -46,15 +85,33 @@ export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
46
85
  `echo SEMABG_X`,
47
86
  ].join("\n");
48
87
  }
88
+ /** KillShell: graceful SIGTERM to the group, then a forced SIGKILL (idempotent — a dead group just errors out).
89
+ * The `|| kill <pid>` fallback covers an ADOPTED exec whose pgid prelude self-reported `$$` without a `setsid`
90
+ * (whether the k8s-exec'd sh is a group leader is runtime-dependent); a launch-mode runner is always setsid'd,
91
+ * so its group kill succeeds and the fallback never fires there. */
49
92
  export function buildBgKillScript(dir) {
50
93
  const d = shellQuote(dir);
51
94
  return [
95
+ // 🔴 review LOW (Fable-5 #4 tightened): an ADOPTED shell (create-then-abort detach) can be killed in the
96
+ // sub-second window BEFORE its detach-capable exec even ran `mkdir` — let alone forked the job + wrote pgid.
97
+ // A bare read (or a dir-existence gate: the dir does not exist YET in that window, indistinguishable from
98
+ // "already ended + EXIT-trap-removed") would skip the kill yet report SEMABG_KILLED — a silent no-op, the
99
+ // command escapes the wall. So wait UNCONDITIONALLY for the pgid file (bounded ~2s, mirroring the LAUNCH
100
+ // path's pre-report pgid gate). Cost of the removed dir-gate: a kill racing the command's natural end (dir
101
+ // already EXIT-trap-removed) idles the full ~2s before no-op'ing — acceptable, because driver.kill only runs
102
+ // while the manager still believes the shell is running (that overlap window is sub-second), and the pod is
103
+ // disposable. For a launch-mode shell pgid is already present → first iteration exits (no-op).
52
104
  `__i=0; while [ ! -s ${d}/pgid ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
53
105
  `__PG=$(cat ${d}/pgid 2>/dev/null); __PG=\${__PG:--}`,
54
106
  `if [ "$__PG" != "-" ]; then kill -TERM -"$__PG" 2>/dev/null || kill -TERM "$__PG" 2>/dev/null; sleep 1; kill -KILL -"$__PG" 2>/dev/null || kill -KILL "$__PG" 2>/dev/null; fi`,
55
107
  `echo SEMABG_KILLED`,
56
108
  ].join("\n");
57
109
  }
110
+ /** dispose: forced SIGKILL to the group + remove the job dir (no grace — teardown). Best-effort.
111
+ * ⚠️ Deliberately NO pgid-wait (unlike {@link buildBgKillScript}, review #4): a create-then-abort adopt disposed
112
+ * in the pre-mkdir window escapes THIS script — but every dispose path ends in pod deletion (task-end destroy;
113
+ * suspendVM deletes the pod after the snapshot), so the escape is pod-lifetime-bounded, and a wait here would
114
+ * add 2s × N shells to the suspend/teardown path for a sub-second window. Kill (the model-facing wall) waits. */
58
115
  export function buildBgDisposeScript(dir) {
59
116
  const d = shellQuote(dir);
60
117
  return [
@@ -64,27 +121,54 @@ export function buildBgDisposeScript(dir) {
64
121
  `echo SEMABG_DISPOSED`,
65
122
  ].join("\n");
66
123
  }
124
+ /**
125
+ * design/116 detach: wrap a foreground exec so it is ADOPTABLE mid-flight, running the command as a real
126
+ * process-group LEADER so an adopted shell's KillShell / BG hard wall reap the WHOLE subtree.
127
+ *
128
+ * 🔴 review MED (why `setsid`, not bare `$$`): the wrapper `sh` that `kubectl exec` starts has a RUNTIME-DEPENDENT
129
+ * process group — under many container runtimes it is NOT its own group leader (its pgid is some ancestor's), so
130
+ * writing `$$` as the pgid and later `kill -$$` targets a group that does not exist → the fallback `kill $$` reaps
131
+ * only the wrapper, orphaning the command's children PAST the §3.6 hard wall (they keep streaming into the adopted
132
+ * shell's buffer until the pod's `activeDeadlineSeconds`). `setsid` starts the command in a NEW session ⇒ a new
133
+ * process group whose leader pid IS the backgrounded job pid, so `kill -<pid>` reaps the command and every
134
+ * descendant. The command's stdout/stderr fds (the exec socket's channels) are INHERITED across setsid — only the
135
+ * controlling terminal is dropped (there is none: kubectl-exec here is non-tty) — so output/exit still flow on the
136
+ * SAME WebSocket, which IS the adopted shell's feed. `wait` propagates the real exit code.
137
+ *
138
+ * The command is base64'd into a pod-side `cmd.sh` (no quoting hazard, mirrors the launch path). The pgid is written
139
+ * atomically (tmp+mv) AFTER the job forks, so a poll never reads a half-written id. Every step is best-effort: a pod
140
+ * without `setsid` (rare on the kata base image) degrades to the `$$` path (the pre-fix behavior), and a read-only
141
+ * /tmp degrades to a pgid-less adopt (kill no-ops; the disposable pod's teardown reaps) — never a broken exec. The
142
+ * EXIT trap removes the dir when the command ends normally, so non-detached execs don't litter the pod.
143
+ */
67
144
  export function buildDetachCapableExec(dir, command) {
68
145
  const d = shellQuote(dir);
69
146
  const q = (name) => shellQuote(`${dir}/${name}`);
70
147
  const cmdB64 = Buffer.from(command, "utf8").toString("base64");
71
148
  const writePgid = (val) => `printf %s ${val} > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`;
72
149
  return [
150
+ // Decode the command into a VARIABLE (not a pod-side file): a read-only-/tmp degrade must still RUN the command
151
+ // (review's degrade-safety case) — a file target under an un-creatable dir would leave nothing to run. `sh -c
152
+ // "$__cmd"` is byte-equivalent to sourcing a cmd.sh whose content is $__cmd. base64 has no quoting hazard.
73
153
  `__cmd=$(printf %s '${cmdB64}' | base64 -d)`,
74
154
  `mkdir -p ${d} 2>/dev/null`,
75
155
  `trap 'rm -rf ${d} 2>/dev/null' EXIT`,
76
156
  `if command -v setsid >/dev/null 2>&1; then`,
157
+ // setsid backgrounds the job as a new-session group leader; $! is that leader's pid == its pgid. Output fds are
158
+ // inherited (the exec socket), only the tty is dropped. `wait` blocks the wrapper until the job ends (or is
159
+ // group-killed) and yields its exit code — so exec/adopt semantics are unchanged, only the reap target is fixed.
77
160
  ` setsid sh -c "$__cmd" &`,
78
161
  ` __pg=$!`,
79
162
  ` ${writePgid('"$__pg"')}`,
80
163
  ` wait "$__pg"; __rc=$?`,
81
164
  `else`,
82
- ` ${writePgid('"$$"')}`,
165
+ ` ${writePgid('"$$"')}`, // no setsid → best-effort $$ (pre-fix behavior; the pod base image ships setsid)
83
166
  ` sh -c "$__cmd"; __rc=$?`,
84
167
  `fi`,
85
168
  `exit $__rc`,
86
169
  ].join("\n");
87
170
  }
171
+ /** Parse {@link buildBgPollScript} stdout. Markers contain `_` (absent from the base64 alphabet) so they never collide. */
88
172
  export function parseBgPollOutput(raw) {
89
173
  if (raw.includes("SEMABG_GONE")) {
90
174
  return { gone: true, outTotal: 0, errTotal: 0, alive: false, stdout: "", stdoutBytes: 0, stderr: "", stderrBytes: 0 };
@@ -1,17 +1,50 @@
1
+ /**
2
+ * The confined, ADDITIVE pod-spec patch ({@link PodSpecPatch}/{@link applyPodSpecPatch}) + the exec-socket TLS
3
+ * shape ({@link execSocketTlsOptions}) + the v4 channel status-frame parse ({@link exitCodeFromStatus}) — the
4
+ * k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` (design/158
5
+ * A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
6
+ */
7
+ /**
8
+ * The confined, ADDITIVE pod-spec patch. Every field is purely additive —
9
+ * none can override the hard isolation invariants. This is the seam the RSI L8 immutable-mount probe + the
10
+ * graderEnvFactory need.
11
+ */
1
12
  export interface PodSpecPatch {
13
+ /** Appended to spec.volumes (e.g. a read-only configMap/secret/emptyDir for the immutable-oracle mount). */
2
14
  volumes?: unknown[];
15
+ /** Appended to the sandbox container's volumeMounts (e.g. { name, mountPath, readOnly:true } — the RO mount). */
3
16
  volumeMounts?: unknown[];
17
+ /** Merged into the POD-level securityContext (runAsNonRoot, fsGroup, seccompProfile, …). */
4
18
  podSecurityContext?: Record<string, unknown>;
19
+ /** Merged into the sandbox CONTAINER's securityContext (readOnlyRootFilesystem, capabilities.drop,
20
+ * allowPrivilegeEscalation:false, …) — the hardening the L8 immutable-mount/isolation probes assert. */
5
21
  containerSecurityContext?: Record<string, unknown>;
22
+ /** Extra labels merged into metadata.labels (e.g. a grader/probe tag). */
6
23
  labels?: Record<string, string>;
7
24
  }
25
+ /**
26
+ * Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
27
+ * APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
28
+ * deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
29
+ * metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
30
+ * restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
31
+ */
8
32
  export declare function applyPodSpecPatch(pod: {
9
33
  metadata: {
10
34
  labels?: Record<string, string>;
11
35
  };
12
36
  spec: Record<string, unknown>;
13
37
  }, patch: PodSpecPatch): void;
38
+ /**
39
+ * TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
40
+ * Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
41
+ * cluster CA even with rejectUnauthorized:false) and reads a Bun-specific `tls` sub-object instead;
42
+ * Node's real `ws` uses the top-level keys and passes the unknown `tls` key harmlessly through to
43
+ * tls.connect. One merged shape serves both. Live-proven on build-host (probe: top-level ca → TLS handshake
44
+ * failed; tls.ca → OPEN).
45
+ */
14
46
  export declare function execSocketTlsOptions(ca: string | undefined, insecureTls: boolean): Record<string, unknown>;
47
+ /** Parse the v4.channel.k8s.io status frame (channel 3) into an exit code, or a typed failure. */
15
48
  export declare function exitCodeFromStatus(statusJson: string): {
16
49
  exitCode: number;
17
50
  } | {
@@ -1,7 +1,26 @@
1
+ /**
2
+ * The confined, ADDITIVE pod-spec patch ({@link PodSpecPatch}/{@link applyPodSpecPatch}) + the exec-socket TLS
3
+ * shape ({@link execSocketTlsOptions}) + the v4 channel status-frame parse ({@link exitCodeFromStatus}) — the
4
+ * k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` (design/158
5
+ * A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
6
+ */
7
+ /**
8
+ * Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
9
+ * APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
10
+ * deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
11
+ * metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
12
+ * restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
13
+ */
1
14
  export function applyPodSpecPatch(pod, patch) {
15
+ // 🔴 wq64gmm5e: a securityContext patch is a HARDENING seam — it may only TIGHTEN, never weaken the isolation
16
+ // the L8 gradeP1 premise depends on. A blind spread let privileged:true / allowPrivilegeEscalation:true /
17
+ // capabilities.add[SYS_ADMIN] / readOnlyRootFilesystem:false through verbatim (and the "cannot weaken" test was
18
+ // fake-green). Fail CLOSED on any privilege-escalating key so the docstring claim is actually enforced.
2
19
  assertHardeningOnly(patch.podSecurityContext, "podSecurityContext");
3
20
  assertHardeningOnly(patch.containerSecurityContext, "containerSecurityContext");
4
21
  if (patch.labels) {
22
+ // Additive + identity-preserving: merge the patch labels, then re-assert the base identity labels LAST so a
23
+ // patch can never clobber the app/managed-by labels the RBAC selectors + reaper key on (wq64gmm5e).
5
24
  const identity = {};
6
25
  for (const k of ["app", "managed-by"]) {
7
26
  const v = pod.metadata.labels?.[k];
@@ -11,6 +30,12 @@ export function applyPodSpecPatch(pod, patch) {
11
30
  pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
12
31
  }
13
32
  const spec = pod.spec;
33
+ // 🔴 Volumes are the SAME isolation-breach class as a privileged securityContext (review: workflow HIGH). Kata
34
+ // passes a hostPath through into the guest VM (virtio-fs/9p host share — NOT blocked by the VM boundary), so a
35
+ // `hostPath:{path:"/"}` volume mounts the k3s NODE root (containerd sock, other pods, host secrets) into the
36
+ // sandbox — defeating the L8 immutable-oracle integrity premise as completely as privileged:true. Reject any
37
+ // host-backed volume source; only pod-local sources (emptyDir/configMap/secret/projected/downwardAPI/ephemeral)
38
+ // are additive-safe.
14
39
  assertVolumesPodLocal(patch.volumes, "volumes");
15
40
  if (patch.volumes?.length)
16
41
  spec.volumes = [...(spec.volumes ?? []), ...patch.volumes];
@@ -24,6 +49,8 @@ export function applyPodSpecPatch(pod, patch) {
24
49
  container.securityContext = { ...(container.securityContext ?? {}), ...patch.containerSecurityContext };
25
50
  }
26
51
  }
52
+ /** Fail-closed direction guard for a securityContext patch: reject any key that WEAKENS isolation. A patch may
53
+ * drop capabilities + tighten, never escalate. Throws (caller surfaces as a provisioning failure). */
27
54
  function assertHardeningOnly(sc, where) {
28
55
  if (!sc)
29
56
  return;
@@ -43,12 +70,16 @@ function assertHardeningOnly(sc, where) {
43
70
  const caps = sc.capabilities;
44
71
  if (caps && Array.isArray(caps.add) && caps.add.length > 0)
45
72
  bad(`capabilities.add ${JSON.stringify(caps.add)} (a hardening patch may only .drop)`);
73
+ // procMount:"Unmasked" re-exposes the masked /proc paths; seccompProfile:"Unconfined" disables syscall filtering —
74
+ // both WEAKEN confinement (review: workflow HIGH secondary), so they belong on the fail-closed list.
46
75
  if (sc.procMount === "Unmasked")
47
76
  bad('procMount:"Unmasked" (re-exposes masked /proc)');
48
77
  const seccomp = sc.seccompProfile;
49
78
  if (seccomp && seccomp.type === "Unconfined")
50
79
  bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
51
80
  }
81
+ /** Fail-closed guard for patch volumes: a Kata guest shares hostPath/csi/flexVolume sources from the NODE, so only
82
+ * POD-LOCAL sources are additive-safe. Reject anything host-backed. Throws (caller surfaces as a provisioning fail). */
52
83
  function assertVolumesPodLocal(volumes, where) {
53
84
  if (!volumes?.length)
54
85
  return;
@@ -63,10 +94,19 @@ function assertVolumesPodLocal(volumes, where) {
63
94
  }
64
95
  }
65
96
  }
97
+ /**
98
+ * TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
99
+ * Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
100
+ * cluster CA even with rejectUnauthorized:false) and reads a Bun-specific `tls` sub-object instead;
101
+ * Node's real `ws` uses the top-level keys and passes the unknown `tls` key harmlessly through to
102
+ * tls.connect. One merged shape serves both. Live-proven on build-host (probe: top-level ca → TLS handshake
103
+ * failed; tls.ca → OPEN).
104
+ */
66
105
  export function execSocketTlsOptions(ca, insecureTls) {
67
106
  const caList = ca ? [ca] : undefined;
68
107
  return { ca: caList, rejectUnauthorized: !insecureTls, tls: { ca: caList, rejectUnauthorized: !insecureTls } };
69
108
  }
109
+ /** Parse the v4.channel.k8s.io status frame (channel 3) into an exit code, or a typed failure. */
70
110
  export function exitCodeFromStatus(statusJson) {
71
111
  try {
72
112
  const s = JSON.parse(statusJson);
@@ -74,6 +114,7 @@ export function exitCodeFromStatus(statusJson) {
74
114
  return { exitCode: 0 };
75
115
  const cause = s.details?.causes?.find((c) => c.reason === "ExitCode");
76
116
  if (cause?.message?.trim()) {
117
+ // trim-guard: Number("")/Number(" ") === 0 — an empty message must NOT read as a fake exit 0 on a Failure
77
118
  const code = Number(cause.message);
78
119
  if (Number.isFinite(code))
79
120
  return { exitCode: code };