@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,28 +1,8 @@
1
- /**
2
- * bake-runner core (IMAGE-API-DESIGN.md §P2.0/§P2.12) — the privileged build-host daemon that actually runs `build.sh`.
3
- *
4
- * image-api (the unprivileged controller) holds ZERO build credentials; this runner is the ONE privileged,
5
- * non-customer-facing tier (node-pinned build-host, replicas=1, docker.sock + ctr + /root/.docker mounted). It:
6
- * 1. long-polls image-api for a queued bake and CLAIMS it (single-flight via the DB lease — image-api's job);
7
- * 2. checks out the recipe at the deployed RECIPE_REF (so build.sh's git rev-parse is REAL — §P2.12);
8
- * 3. spawns `build.sh <VETTED argv> --json` as a CHILD IN ITS OWN PROCESS GROUP (setsid/detached) so a cancel
9
- * can SIGTERM/SIGKILL the whole group — channel-close does NOT kill a build (§P2.11);
10
- * 4. forwards each STDOUT line to `…/ingest` with an incrementing lineOrd (at-least-once dedupe — §P2.10);
11
- * 5. heartbeats the lease (+ polls cancel_requested off the ingest/heartbeat response) every ~30s;
12
- * 6. on cancel/deadline kills the process GROUP and synthesizes the terminal `done` (build.sh emits none — §P2.11);
13
- * 7. on the `done` line reads the manifest BODY off disk and forwards it so image-api can auto-register (§P2.10).
14
- *
15
- * This file is the I/O-injected core: the HTTP client, the child spawner, the fs reader, the clock and the logger
16
- * are all dependencies, so the orchestration is structurally testable here while `main.ts` wires the real ones (the
17
- * runner can only be exercised live on build-host). The line→frame mapping itself lives PURE in ./protocol.ts.
18
- */
19
1
  import { resolve as resolvePath } from "node:path";
20
2
  import { lineToFrame, dockerBuildTickFrame, synthDoneFrame, exitDoneFrame, deadlineMsForProfile, parseDfFreeGb, parseBuildShLine, manifestPathFromLine, } from "./protocol.js";
21
3
  const DEFAULT_HEARTBEAT_MS = 30_000;
22
4
  const DEFAULT_IDLE_POLL_MS = 5_000;
23
5
  const DEFAULT_MIN_FREE_GB = 20;
24
- /** A high, fixed lineOrd for the loop-level catch terminal (runOne owns the per-bake counter, so its current value
25
- * is out of scope here) — far above any realistic in-bake line count so it never collides on uq_line. */
26
6
  const TERMINAL_FALLBACK_ORD = 2_000_000_000;
27
7
  export class BakeRunner {
28
8
  o;
@@ -36,12 +16,9 @@ export class BakeRunner {
36
16
  this.idlePollMs = opts.idlePollMs ?? DEFAULT_IDLE_POLL_MS;
37
17
  this.minFreeGb = opts.minFreeGb ?? DEFAULT_MIN_FREE_GB;
38
18
  }
39
- /** Cooperative shutdown — finishes the in-flight bake's current frame, then exits the claim loop. */
40
19
  stop() {
41
20
  this.stopped = true;
42
21
  }
43
- /** The long-poll loop: claim → run → release → poll next. Single-flight is enforced DB-side (the claim CAS), so a
44
- * second runner replica simply never wins a claim. Runs until `stop()`. */
45
22
  async loop() {
46
23
  while (!this.stopped) {
47
24
  let claimed = null;
@@ -59,28 +36,18 @@ export class BakeRunner {
59
36
  await this.runOne(claimed);
60
37
  }
61
38
  catch (err) {
62
- // A bake that threw out of runOne (an unexpected host/API failure) — best-effort terminal so readers settle.
63
- // The lineOrd here is unknown (runOne owns the counter), so use a NULL-equivalent high ordinal that won't
64
- // collide with an in-flight frame's uq_line (image-api still ++seq's it; the reaper backstops a true miss).
65
39
  this.o.log.error("bake_run_error", { bakeId: claimed.bakeId, err: String(err) });
66
40
  await this.safeIngest(claimed, exitDoneFrame(TERMINAL_FALLBACK_ORD, 1, String(err)));
67
41
  }
68
42
  }
69
43
  }
70
- /** Run a single CLAIMED bake end-to-end. Owns the lineOrd counter, the heartbeat/cancel/deadline timers, the
71
- * child process group, the stdout→ingest pump, and the terminal done synthesis. */
72
44
  async runOne(bake) {
73
- const ord = { n: 0 }; // the monotonic lineOrd allocator (at-least-once dedupe key — §P2.10)
45
+ const ord = { n: 0 };
74
46
  this.o.log.info("bake_claimed", { bakeId: bake.bakeId, push: bake.push, dryRun: bake.dryRun, logs: bake.logs });
75
- // M2: serialize the stdout→ingest pump. A burst chunk (bufferLines calls onStdoutLine per newline synchronously)
76
- // + the progress tick were fired as un-awaited `void safeIngest`, so N POSTs raced concurrently — image-api's
77
- // non-atomic seq alloc then lost a frame on a (bake_id,seq) PK collision. Chain each non-terminal ingest so they
78
- // POST one-at-a-time in lineOrd order, and AWAIT the chain before runOne returns (alongside the done forward).
79
47
  let pump = Promise.resolve();
80
48
  const enqueueIngest = (frame) => {
81
49
  pump = pump.then(() => this.safeIngest(bake, frame));
82
50
  };
83
- // ── Pre-flight disk guard (§P2.7): below the floor, fail FAST rather than dying mid-build with a half-layer.
84
51
  if (!bake.dryRun) {
85
52
  const freeGb = await this.freeGbOrNull();
86
53
  if (freeGb !== null && freeGb < this.minFreeGb) {
@@ -96,7 +63,6 @@ export class BakeRunner {
96
63
  return;
97
64
  }
98
65
  }
99
- // ── Recipe checkout at the pinned ref so build.sh's git rev-parse gitsha is REAL (§P2.12).
100
66
  let cwd;
101
67
  try {
102
68
  cwd = await this.o.host.checkoutRecipe(this.o.recipeRef);
@@ -105,24 +71,16 @@ export class BakeRunner {
105
71
  await this.safeIngest(bake, exitDoneFrame(nextOrd(ord), 1, `recipe checkout failed: ${String(err)}`));
106
72
  return;
107
73
  }
108
- // ── Spawn build.sh in its OWN PROCESS GROUP (so a cancel/deadline kill signals the whole detached tree).
109
74
  const stderrTail = [];
110
75
  let lastManifestPath = null;
111
- let lastImageTag = null; // the built tag — the push-retry target (§P2.12 big-blob retry)
76
+ let lastImageTag = null;
112
77
  let sawDone = false;
113
- // build.sh's `done` triggers an ASYNC tail (read the manifest body / the push retry, then ingest) — the child
114
- // can exit the instant after printing it. It is chained ONTO `pump` (below), so `await pump` before runOne
115
- // returns also awaits this pending forward (the lease isn't released before the auto-register input lands).
116
- // 🔴 Resolve buildShPath against the recipe CHECKOUT cwd, NOT via PATH: node/bun's spawn resolves a BARE or
117
- // RELATIVE argv[0] through $PATH (a bare `build.sh` silently ENOENT→exit-127 footgun — cost an hour live), so
118
- // path.resolve(cwd, buildShPath) pins it to the checkout. An absolute buildShPath is returned unchanged.
119
78
  const buildShAbs = resolvePath(cwd, this.o.buildShPath);
120
79
  const buildArgv = [buildShAbs, ...bake.argv, "--json"];
121
80
  const child = this.o.spawner.spawn({
122
81
  argv: buildArgv,
123
82
  cwd,
124
83
  onStdoutLine: (raw) => {
125
- // Forward EVERY stdout line through the pure mapper; remember the manifest path for the auto-register read.
126
84
  const ev = parseBuildShLine(raw);
127
85
  if (ev && ev.event === "manifest" && typeof ev.path === "string") {
128
86
  lastManifestPath = ev.path;
@@ -132,47 +90,39 @@ export class BakeRunner {
132
90
  }
133
91
  if (ev && ev.event === "done") {
134
92
  sawDone = true;
135
- // The done line needs the manifest BODY attached before ingest (auto-register reads it off disk — §P2.10).
136
- // M2: chain it ON the serialized pump so the terminal `done` is forwarded STRICTLY AFTER every queued
137
- // non-terminal frame (the SSE log's last event), in lineOrd order — never racing ahead of them.
138
93
  pump = pump.then(() => this.forwardDone(bake, ord, ev, lastManifestPath, lastImageTag));
139
94
  return;
140
95
  }
141
96
  enqueueIngest(lineToFrame(raw, nextOrd(ord)));
142
97
  },
143
- // stderr is the failure tail (`tail -40 build.log >&2`), NOT JSON — buffer it, surface on a non-zero exit.
144
98
  onStderrLine: (raw) => {
145
99
  if (stderrTail.length < 80)
146
100
  stderrTail.push(raw);
147
101
  },
148
102
  });
149
- // ── The heartbeat / cancel-poll / docker-build-progress / deadline supervisor, racing the child exit.
150
103
  const deadlineMs = deadlineMsForProfile(bake.profile ?? "custom", bake.bands ?? null);
151
104
  const startedAt = this.o.clock.now();
152
105
  let killedFor = null;
153
- let childExited = false; // set when the child resolves so the supervisor's next tick stops (no leaked heartbeats)
106
+ let childExited = false;
154
107
  const supervisor = (async () => {
155
- // eslint-disable-next-line no-constant-condition
156
108
  while (true) {
157
109
  await this.o.clock.sleep(this.heartbeatMs);
158
110
  if (childExited || killedFor)
159
- return; // the child finished, or we already killed — stop heartbeating
111
+ return;
160
112
  const elapsed = this.o.clock.now() - startedAt;
161
- // Deadline first (a hung build past its profile budget — kill the group, synthesize FAILED 124).
162
113
  if (elapsed >= deadlineMs) {
163
114
  killedFor = "timeout";
164
115
  this.o.log.warn("bake_deadline_kill", { bakeId: bake.bakeId, elapsedMs: elapsed, deadlineMs });
165
116
  this.killGroup(child);
166
117
  return;
167
118
  }
168
- // Heartbeat the lease + read back the cancel flag (the durable cross-replica cancel — §P2.11).
169
119
  let ack;
170
120
  try {
171
121
  ack = await this.o.api.heartbeat(bake.bakeId, bake.ingestSecret);
172
122
  }
173
123
  catch (err) {
174
124
  this.o.log.warn("bake_heartbeat_error", { bakeId: bake.bakeId, err: String(err) });
175
- continue; // a transient heartbeat miss is not fatal; the reaper backstops a truly-lost runner.
125
+ continue;
176
126
  }
177
127
  if (ack.cancelRequested) {
178
128
  killedFor = "cancelled";
@@ -181,61 +131,37 @@ export class BakeRunner {
181
131
  return;
182
132
  }
183
133
  if (ack.leaseValid === false) {
184
- // The lease was stolen (image-api reaped us as stale) — abort; we no longer own this build.
185
134
  killedFor = "timeout";
186
135
  this.o.log.warn("bake_lease_lost", { bakeId: bake.bakeId });
187
136
  this.killGroup(child);
188
137
  return;
189
138
  }
190
- // No cancel, no deadline: emit the docker-build progress tick so a resuming reader sees motion (logs off).
191
139
  enqueueIngest(dockerBuildTickFrame(nextOrd(ord), Math.floor(elapsed / 1000)));
192
140
  }
193
141
  })();
194
- // #131-6:创建即挂接——rejection 若等到 stopSupervisor(child 退出后)才有人接,会在整个构建
195
- // 时长内悬置,Node 默认 unhandled-rejections=throw 直接终结 runner 进程。挂接后 supervisor 腿
196
- // 静默死掉的后果有界:lease 心跳停 → image-api reaper 兜底(与 heartbeat error continue 同口径)。
197
142
  void supervisor.catch((err) => {
198
143
  try {
199
144
  this.o.log.warn("bake_supervisor_error", { bakeId: bake.bakeId, err: String(err) });
200
145
  }
201
146
  catch {
202
- /* 日志面故障不再外抛 */
203
147
  }
204
148
  });
205
149
  const exit = await child.exited;
206
150
  childExited = true;
207
151
  this.stopSupervisor(supervisor);
208
- // M2: drain the serialized pump BEFORE synthesizing a terminal. The pump carries every non-terminal frame AND
209
- // (when build.sh printed its own done) the chained `forwardDone` — so by here the real `done` has already been
210
- // forwarded LAST in lineOrd order. Draining first guarantees the lease isn't released before the auto-register
211
- // input (manifest body) is in image-api's hands.
212
152
  await pump.catch(() => undefined);
213
- // ── Terminal done synthesis. build.sh has NO SIGTERM trap, so a killed child emits no done — we synthesize it.
214
153
  if (killedFor) {
215
154
  await this.safeIngest(bake, synthDoneFrame(nextOrd(ord), killedFor));
216
155
  return;
217
156
  }
218
157
  if (sawDone) {
219
- // build.sh printed its own done — already forwarded (manifest-body read / push retry) via the drained pump.
220
158
  return;
221
159
  }
222
- // The child exited WITHOUT a done line (a crash/SIGKILL before build.sh's die() trap) — backstop a FAILED done
223
- // carrying the exit code + the stderr failure tail (image-api redacts it on store).
224
160
  const code = exit.code ?? (exit.signal ? 137 : 1);
225
161
  await this.safeIngest(bake, exitDoneFrame(nextOrd(ord), code, stderrTail.join("\n")));
226
162
  }
227
- /** Forward build.sh's own `done` line — but FIRST read the manifest BODY off disk and attach it so image-api can
228
- * auto-register (build.sh's done carries only a build-host path, NOT the body — §P2.10). A manifest read failure is
229
- * non-fatal: the done still forwards (the digest is on the line); image-api then registers indexId:null partial.
230
- *
231
- * BIG-BLOB PUSH RETRY (§P2.12): build.sh dies exit 13 on a flaky `docker push`. The retry is a THIN `docker push`
232
- * wrapper on the host (NOT build.sh --replay — that re-bakes + hits the dup-check exit 10). On a push-fail done
233
- * for a `--push` bake with a known tag, the runner retries the push; on success it rewrites the done to COMPLETE
234
- * with the recovered post-push digest (no manifest on disk — build.sh dies before the manifest write — so image-
235
- * api registers the built-and-pushed-but-unregistered partial, which the P3 reconciliation tick finalizes). */
236
163
  async forwardDone(bake, ord, doneLine, lastManifestPath, lastImageTag) {
237
164
  let line = doneLine;
238
- // Push-fail (exit 13) recovery: retry the big-blob push out-of-band before declaring the bake failed.
239
165
  if (line.status === "FAILED" &&
240
166
  line.exitCode === 13 &&
241
167
  bake.push &&
@@ -245,7 +171,6 @@ export class BakeRunner {
245
171
  try {
246
172
  const r = await this.o.host.dockerPushRetry(lastImageTag, 3);
247
173
  if (r.ok) {
248
- // Recovered: rewrite the terminal to COMPLETE with the post-push RepoDigest (design §6 runtime ref).
249
174
  line = { ...line, status: "COMPLETE", exitCode: 0, error: null, digest: r.digest ?? line.digest, pushed: true };
250
175
  this.o.log.info("bake_push_recovered", { bakeId: bake.bakeId, tag: lastImageTag });
251
176
  }
@@ -263,36 +188,28 @@ export class BakeRunner {
263
188
  }
264
189
  catch (err) {
265
190
  this.o.log.warn("bake_manifest_read_failed", { bakeId: bake.bakeId, path, err: String(err) });
266
- // leave manifestBody unset → image-api registers the built-but-unregistered partial (digest, indexId:null).
267
191
  }
268
192
  }
269
193
  }
270
194
  await this.safeIngest(bake, frame);
271
195
  }
272
- /** Kill the process GROUP: SIGTERM, then SIGKILL shortly after (a SIGKILL mid-docker-build can leave dangling
273
- * layers; the dup-check + lockhash prevent a poisoned re-bake, and a startup workdir sweep reclaims them — §P2.11). */
274
196
  killGroup(child) {
275
197
  try {
276
198
  child.killGroup("SIGTERM");
277
199
  }
278
200
  catch {
279
- /* the group may already be gone */
280
201
  }
281
202
  void this.o.clock.sleep(5_000).then(() => {
282
203
  try {
283
204
  child.killGroup("SIGKILL");
284
205
  }
285
206
  catch {
286
- /* already dead */
287
207
  }
288
208
  });
289
209
  }
290
210
  stopSupervisor(p) {
291
- // The supervisor loop exits on its own once killedFor is set or the child exits; this just swallows its result.
292
211
  void p.catch(() => undefined);
293
212
  }
294
- /** Pre-flight free-GiB read; null (fail-open) on a df parse/exec miss — a df hiccup must not block a bake (the
295
- * reaper + a mid-build fill guard still protect the host). */
296
213
  async freeGbOrNull() {
297
214
  try {
298
215
  return parseDfFreeGb(await this.o.host.dfData());
@@ -301,8 +218,6 @@ export class BakeRunner {
301
218
  return null;
302
219
  }
303
220
  }
304
- /** Ingest one frame, swallowing transient errors (the buffered-retry of unsent lines is the real-runner's job;
305
- * image-api's uq_line makes a re-send a no-op, so a dropped frame is recoverable, never a crash). */
306
221
  async safeIngest(bake, frame) {
307
222
  try {
308
223
  return await this.o.api.ingest(bake.bakeId, bake.ingestSecret, frame);
@@ -313,7 +228,6 @@ export class BakeRunner {
313
228
  }
314
229
  }
315
230
  }
316
- /** Allocate the next per-bake lineOrd (monotonic from 1) — the at-least-once ingest dedupe key (§P2.10). */
317
231
  export function nextOrd(ord) {
318
232
  ord.n += 1;
319
233
  return ord.n;
@@ -1,29 +1,5 @@
1
- /**
2
- * L8 G-c artifact — the signed deploy-authenticity evidence the design/93 §5.2 G-c aggregator requires
3
- * (SPEC-RSI-L7-L8 §A.3). The artifact is canonical-JSON serialized and ed25519-signed so the GO aggregator can
4
- * verify it was produced by a real L8 run and link it to a commit + adapter build + boundary.
5
- *
6
- * 🔴 SIGNING IS PROVENANCE, NOT AUTHENTICITY-OF-RESULT (SPEC §A.3 RISK-4): a valid signature only proves "this
7
- * run, with this key, produced these probe VALUES"; it does NOT prove the probes hit a real boundary — that is
8
- * the P0 fail-loud + the external collector's job. The aggregator MUST check `gC.satisfied` AND
9
- * `boundary.isolationClassVerified` AND `p1Mode !== "adapter-RO-seam-absent"`, never just "signature valid".
10
- *
11
- * 🔴 ED25519, NOT HMAC: a shared HMAC secret is forgeable by anyone who holds it; ed25519 is asymmetric
12
- * — the private key signs (control-plane held, env L8_SIGNER_KEY_B64), the PUBLIC key (shipped to the aggregator)
13
- * only verifies. node:crypto's ed25519 is built-in (no new dep).
14
- *
15
- * 🔴 TIMESTAMPS ARE INJECTED, NOT `Date.now()` (deterministic-test requirement): `buildArtifact` takes
16
- * `producedAt` from the caller (the live runner passes a real ISO string; the unit test passes a fixed one), so
17
- * the canonical JSON is byte-stable in tests.
18
- */
19
1
  import { createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify } from "node:crypto";
20
2
  import { L8_SCHEMA_VERSION } from "./escape.js";
21
- // ───────────────────────────── canonical JSON ─────────────────────────────
22
- /**
23
- * Canonical JSON: object keys sorted recursively, no insignificant whitespace, arrays preserve order. Stable
24
- * across re-serialization so the signature/contentHash are deterministic. Rejects non-finite numbers (NaN/Inf
25
- * would serialize as `null` and silently corrupt the canonical form).
26
- */
27
3
  export function canonicalJson(value) {
28
4
  return serialize(value);
29
5
  }
@@ -45,7 +21,7 @@ function serialize(v) {
45
21
  if (t === "object") {
46
22
  const obj = v;
47
23
  const keys = Object.keys(obj)
48
- .filter((k) => obj[k] !== undefined) // omit undefined fields (matches JSON.stringify object semantics)
24
+ .filter((k) => obj[k] !== undefined)
49
25
  .sort();
50
26
  return `{${keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(",")}}`;
51
27
  }
@@ -53,26 +29,14 @@ function serialize(v) {
53
29
  throw new Error("canonicalJson: bigint is not serializable");
54
30
  throw new Error(`canonicalJson: unsupported type ${t}`);
55
31
  }
56
- // ───────────────────────────── content hash ─────────────────────────────
57
32
  import { createHash } from "node:crypto";
58
- /** The body that the signature + contentHash cover = the artifact WITHOUT `signature` and WITHOUT `contentHash`. */
59
33
  function signableBody(a) {
60
34
  const { signature: _sig, contentHash: _ch, ...rest } = a;
61
35
  return rest;
62
36
  }
63
- /** sha256 of the canonical body — links the signature to the exact body bytes. */
64
37
  export function computeContentHash(a) {
65
38
  return createHash("sha256").update(canonicalJson(signableBody(a)), "utf8").digest("hex");
66
39
  }
67
- // ───────────────────────────── gC fold (pure) ─────────────────────────────
68
- /**
69
- * Fold G-c (SPEC §A.3): satisfied iff
70
- * - no escapes, AND
71
- * - boundary.provisioned AND boundary.isolationClassVerified, AND
72
- * - every probe passed (NONE pending, NONE failed), AND
73
- * - the immutable-mount probe's p1Mode is NOT "adapter-RO-seam-absent".
74
- * G-c is necessary-non-sufficient: any pending/failed probe ⇒ NO-GO. This is what keeps tier-ON NO-GO honest.
75
- */
76
40
  export function foldGc(boundary, probes, escapes) {
77
41
  const reasons = [];
78
42
  if (escapes.length > 0)
@@ -81,9 +45,6 @@ export function foldGc(boundary, probes, escapes) {
81
45
  reasons.push("boundary not provisioned");
82
46
  if (!boundary.isolationClassVerified)
83
47
  reasons.push("isolationClass not verified (P0/P2 — possible runc downgrade)");
84
- // 🔴 (review HIGH) require the FULL canonical probe set PRESENT before satisfied can be true — else an empty or
85
- // partial probe array silently satisfies G-c (a GO with NO evidence; foldGc(boundary,[],[]) used to return
86
- // satisfied:true). Each required id must appear exactly once. Mirrors PROBE_MATRIX ids (P0..P4).
87
48
  const REQUIRED_PROBE_IDS = ["P0", "P1", "P2", "P3", "P4"];
88
49
  const idCounts = new Map();
89
50
  for (const p of probes)
@@ -100,8 +61,6 @@ export function foldGc(boundary, probes, escapes) {
100
61
  const failedProbes = probes.filter((p) => !p.pending && p.passed !== true);
101
62
  if (failedProbes.length > 0)
102
63
  reasons.push(`failed/unproven probe(s): ${failedProbes.map((p) => p.id).join(", ")}`);
103
- // wq64gmm5e: key on the constrained `id` (every other gate above keys on id) — a free-form `name` drift must
104
- // never fail this seam-absent guard OPEN into a fake-green GO.
105
64
  const p1 = probes.find((p) => p.id === "P1");
106
65
  if (p1 && p1.p1Mode === "adapter-RO-seam-absent") {
107
66
  reasons.push("P1 ran in adapter-RO-seam-absent mode (necessary-non-sufficient for the grader contract)");
@@ -114,8 +73,6 @@ export function foldGc(boundary, probes, escapes) {
114
73
  : `G-c NOT satisfied: ${reasons.join(" | ")}`,
115
74
  };
116
75
  }
117
- // ───────────────────────────── build (pure) ─────────────────────────────
118
- /** Build the artifact body + contentHash. Pure: timestamps injected, gC folded. No signature yet. */
119
76
  export function buildArtifact(meta, probes, escapes) {
120
77
  const gC = foldGc(meta.boundary, probes, escapes);
121
78
  const draft = {
@@ -129,25 +86,17 @@ export function buildArtifact(meta, probes, escapes) {
129
86
  probes,
130
87
  escapes,
131
88
  gC,
132
- contentHash: "", // filled below
89
+ contentHash: "",
133
90
  };
134
91
  draft.contentHash = computeContentHash(draft);
135
92
  return draft;
136
93
  }
137
- // ───────────────────────────── ed25519 sign / verify ─────────────────────────────
138
- /** Load an ed25519 private key from a base64 PKCS8 DER (the L8_SIGNER_KEY_B64 control-plane secret). */
139
94
  export function loadPrivateKeyB64(b64) {
140
95
  return createPrivateKey({ key: Buffer.from(b64, "base64"), format: "der", type: "pkcs8" });
141
96
  }
142
- /** Load an ed25519 public key from a base64 SPKI DER (the key shipped to the aggregator). */
143
97
  export function loadPublicKeyB64(b64) {
144
98
  return createPublicKey({ key: Buffer.from(b64, "base64"), format: "der", type: "spki" });
145
99
  }
146
- /**
147
- * Sign the artifact: recompute the contentHash (defends against a body mutated after build), sign the canonical
148
- * body with ed25519 (`sign(null, msg, key)` — ed25519 takes no separate digest algorithm), attach the detached
149
- * base64 signature. Returns a NEW artifact (does not mutate the input).
150
- */
151
100
  export function signArtifact(artifact, privateKey, keyId) {
152
101
  const withHash = { ...artifact, signature: undefined, contentHash: "" };
153
102
  withHash.contentHash = computeContentHash(withHash);
@@ -155,14 +104,6 @@ export function signArtifact(artifact, privateKey, keyId) {
155
104
  const sig = cryptoSign(null, msg, privateKey);
156
105
  return { ...withHash, signature: { alg: "ed25519", keyId, sig: sig.toString("base64") } };
157
106
  }
158
- /**
159
- * Verify an artifact: recompute the contentHash over the body and check it matches the stored one (tamper of any
160
- * body field flips the hash), then ed25519-verify the detached signature over the canonical body. Returns false
161
- * (never throws) on a missing/garbage signature, a content-hash mismatch, or a bad signature.
162
- *
163
- * 🔴 A `true` here means INTEGRITY/PROVENANCE only — the caller MUST still gate on gC.satisfied AND
164
- * boundary.isolationClassVerified AND p1Mode (see file header). Verification alone is NOT a GO.
165
- */
166
107
  export function verifyArtifact(artifact, publicKey) {
167
108
  const sig = artifact.signature;
168
109
  if (!sig || sig.alg !== "ed25519" || typeof sig.sig !== "string")
@@ -1,29 +1,4 @@
1
- /**
2
- * L8 escape vocabulary — the `l8.v1` schema (design/93 §2, SPEC-RSI-L7-L8 §A.3).
3
- *
4
- * 🔴 WHY A SEPARATE SCHEMA (do NOT silently reuse core's L6 union):
5
- * core's L6 bench (`sema-core/test/rsi-l6-bench.test.ts:92-94`, VERIFIED) defines
6
- * EscapeKind = "E1" | "E2-readable" | "E2-callable"
7
- * Classification = "gate" | "assembly"
8
- * GateId = "G1" | "G2" | "G3" | "G4" | "tripwire" | "fold"
9
- * It has **NO `E3` kind and NO `"deploy"` classification.** L8's escapes are E3 / deploy-class — they do NOT
10
- * fit the L6 union as-is. So the "one aggregator composes L6+L8" claim is an OPEN QUESTION (SPEC §A.3 RISK-3),
11
- * not a free reuse: either (i) core extends `EscapeKind` with `E3` + a deploy lane (a CORE change — must be
12
- * coordinated, never unilaterally invented here), or (ii) L8 ships its OWN self-describing `l8.v1` schema and
13
- * the GO aggregator maps both. This file is (ii): the artifact is `schemaVersion:"l8.v1"`, self-describing.
14
- *
15
- * We deliberately make `L8EscapeKind` a SUPERSET of the L6 kinds (so a future aggregator can map L6 escapes
16
- * straight in) and ADD `"E3"`; and `L8Classification` adds `"deploy"`. Nothing in this file claims the
17
- * vocabularies are already aligned — that alignment is the cross-team decision (`SCHEMA_ALIGNMENT`).
18
- */
19
- /** Schema version stamped into every artifact — the self-describing handshake for the GO aggregator. */
20
1
  export const L8_SCHEMA_VERSION = "l8.v1";
21
- /**
22
- * 🔴 OPEN QUESTION (cross-team, do NOT resolve unilaterally — SPEC §A.3 / design/93 open-questions):
23
- * whether the GO aggregator accepts `l8.v1` + ed25519 verification + an E3/deploy lane, OR core extends its
24
- * L6 `EscapeKind`/`Classification` instead. Until confirmed, this constant records the unaligned state so a
25
- * reader (and a test) cannot mistake "L8 ships its own schema" for "the vocabularies are already merged".
26
- */
27
2
  export const SCHEMA_ALIGNMENT = {
28
3
  l6KindsCovered: ["E1", "E2-readable", "E2-callable"],
29
4
  l8AddedKinds: ["E3"],
@@ -1,17 +1,3 @@
1
- /**
2
- * L8 deploy-contract authenticity bench (RSI L8, design/93 §5.2 G-c, SPEC-RSI-L7-L8 §A).
3
- *
4
- * NO-DEPENDENCY scaffold: the probe matrix + the l8.v1 escape schema + the signed G-c artifact (build/sign/verify)
5
- * + the probe HARNESS that runs the BUILDABLE probes (P0 runtime-verification, P2 isolation) and emits HONEST
6
- * PENDING records for the probes blocked on a cross-team dep:
7
- * - P1 immutable-mount → the adapter podSpecPatch RO-mount seam is now MERGED (remote-env-k8s, service-side);
8
- * pending only the live-runner wiring (provision WITH the RO mount) + a real Kata run.
9
- * - P3 bash-egress → pending an egress-enforcement layer + the cluster CNI decision (center).
10
- *
11
- * The REAL boundary execution (live Kata/E2B + the external collector) is the live runner (scripts/, gated,
12
- * outside this scaffold). This module is pure/deterministic-testable; it NEVER asserts a probe passes against a
13
- * fake env (the A.6 anti-fake-green meta-lesson).
14
- */
15
1
  export * from "./escape.js";
16
2
  export * from "./probes.js";
17
3
  export * from "./artifact.js";
@@ -1,26 +1,4 @@
1
- /**
2
- * L8 probe matrix — the deploy-contract authenticity probes (SPEC-RSI-L7-L8 §A.2, design/93 §5.2 G-c, §2 escape).
3
- *
4
- * Each probe asserts a REAL kernel/VM/network boundary and is designed to distinguish:
5
- * (a) a REAL enforcement (the kernel/VM/CNI actually blocked it) from
6
- * (b) a typed-tool refusal (the ToolPolicy layer said no — NOT a boundary), and from
7
- * (c) a self-declared boolean (`capabilities.isolation:true`, which a lying/downgraded adapter still reports —
8
- * `remote-env-k8s.ts:241` hardcodes it; a `runtimeClass:""` runc pod ALSO reports it).
9
- *
10
- * 🔴 DISCIPLINE: nothing here imports the typed-tool / ToolPolicy layer. Probes are pure DEFINITIONS +
11
- * classification logic + an INDEPENDENT grader; the actual cluster read / sandbox exec is an injected seam
12
- * ({@link ProbeExec}) so the unit test mocks it deterministically and NEVER asserts a probe "passes" against a
13
- * fake env (that would be the fake-green this whole thread exists to prevent — A.6 meta-lesson). The REAL
14
- * verdict comes only from the live runner against a real Kata/E2B boundary + the external collector.
15
- *
16
- * Buildability per probe (SPEC §A.2):
17
- * P0/P2 runtime-verification ....... BUILDABLE NOW (read the real runtimeClass + kernel + host-unreachability)
18
- * P1 immutable-mount ............... STRUCTURE NOW; real-RO execution PENDING the adapter podSpecPatch seam
19
- * P3 bash-egress ................... STRUCTURE NOW; HONESTLY reports egressEnforced:false (no CNI / E2B default)
20
- * P4 artifact ...................... see artifact.ts
21
- */
22
1
  import { shellQuote } from "../../plugins/remote-shell.js";
23
- /** What a real-isolation runtime looks like — kata-qemu / kata-fc are VM boundaries; runc / "" are NOT. */
24
2
  export const REAL_ISOLATION_RUNTIME_CLASSES = ["kata-qemu", "kata-fc", "kata"];
25
3
  export const PROBE_MATRIX = [
26
4
  {
@@ -67,16 +45,6 @@ export function probeDef(id) {
67
45
  throw new Error(`unknown probe id: ${id}`);
68
46
  return d;
69
47
  }
70
- /**
71
- * Classify the REAL runtime from observations — the lying-adapter detector. A runc downgrade still reports
72
- * `declaredIsolation:true`; this function IGNORES that and decides from `actualRuntimeClass` + kernel-inequality.
73
- *
74
- * - kata: VM-isolated iff the runtimeClass is a real isolation class AND guestKernel != hostKernel. If the
75
- * runtimeClass was dropped to "" / "runc" the kernel MATCHES the host → runc-downgrade → NOT verified.
76
- * - e2b: the host is not ours, so kernel-vs-host is meaningless (RISK-7). VM-isolation evidence for e2b is the
77
- * Firecracker kernel string being present (a non-empty distinct guest kernel) — host-unreachability (P2) is
78
- * the corroborating half. We never assert a kata-only kernel-inequality invariant on e2b.
79
- */
80
48
  export function classifyRuntime(o) {
81
49
  const isRealClass = REAL_ISOLATION_RUNTIME_CLASSES.includes(o.actualRuntimeClass);
82
50
  if (o.backend === "kata") {
@@ -104,7 +72,6 @@ export function classifyRuntime(o) {
104
72
  reason: `runtimeClass=${o.actualRuntimeClass} and guest kernel ${o.guestKernel} != host ${o.hostKernel}`,
105
73
  };
106
74
  }
107
- // e2b: no host-kernel comparison (RISK-7). Require a present guest kernel; host-unreachability (P2) corroborates.
108
75
  if (o.guestKernel.trim().length === 0) {
109
76
  return {
110
77
  isolationClassVerified: false,
@@ -120,10 +87,6 @@ export function classifyRuntime(o) {
120
87
  reason: `e2b: Firecracker guest kernel ${o.guestKernel} present (kernel-vs-host N/A on e2b — RISK-7); host-unreachability is the corroborating half (P2)`,
121
88
  };
122
89
  }
123
- /**
124
- * The probe COMMANDS for the runtime read (used by the live runner; shown here so they live with the probe).
125
- * Every interpolated value is shellQuoted (RISK-9). These are read-only — no side effects.
126
- */
127
90
  export function runtimeReadCommands() {
128
91
  return {
129
92
  uname: "uname -r",
@@ -131,10 +94,7 @@ export function runtimeReadCommands() {
131
94
  dmi: "cat /sys/devices/virtual/dmi/id/product_name 2>/dev/null || echo unknown",
132
95
  };
133
96
  }
134
- // ───────────────────────────── P1 — immutable-mount INDEPENDENT grader (structure now) ─────────────────────────────
135
- /** The escalations P1 attempts after the initial write (the real test, beyond core's point-in-time probe). */
136
97
  export const P1_ESCALATIONS = ["remount-rw", "chattr-i", "unshare-m", "busybox-mount"];
137
- /** The escalation shell commands for an oracle path — every path shellQuoted (RISK-9). */
138
98
  export function p1EscalationCommand(escalation, oraclePath) {
139
99
  const q = shellQuote(oraclePath);
140
100
  switch (escalation) {
@@ -148,12 +108,6 @@ export function p1EscalationCommand(escalation, oraclePath) {
148
108
  return `cp /bin/busybox /tmp/l8bb 2>/dev/null && /tmp/l8bb mount -o remount,rw ${q}`;
149
109
  }
150
110
  }
151
- /**
152
- * P1 verdict — PASS iff the initial write failed AND EVERY escalation failed AND the independent grader confirms
153
- * no probe file was written and the inode is unchanged AND CAP_SYS_ADMIN was confirmed absent. The pass is only
154
- * valid for the grader contract when the adapter RO seam is present (mode); on a proxy path it is honest but
155
- * necessary-non-sufficient.
156
- */
157
111
  export function gradeP1(o) {
158
112
  const allBlocked = o.initialWriteBlocked && P1_ESCALATIONS.every((e) => o.escalationsBlocked[e] === true);
159
113
  const independentlyClean = !o.oracleProbeFileExistsAfter && o.oracleInodeUnchanged;
@@ -178,13 +132,7 @@ export function gradeP1(o) {
178
132
  }
179
133
  return { passed, validForGraderContract, allBlocked, reason };
180
134
  }
181
- // ───────────────────────────── P3 — bash-egress attempt matrix + honest classification (structure now) ─────────────────────────────
182
- /**
183
- * The egress vectors P3 attempts — NOT just one curl. Each is a distinct exfil channel a real CNI must block.
184
- * The verdict comes from the EXTERNAL collector (per-run nonce), never the in-sandbox exit code.
185
- */
186
135
  export const EGRESS_VECTORS = ["https-tcp", "dns", "udp", "ipv6", "metadata-169254", "unix-socket"];
187
- /** Build the attempt command for a vector. `host`/`url` carry the per-run nonce; everything shellQuoted (RISK-9). */
188
136
  export function egressAttemptCommand(vector, target, nonce) {
189
137
  const q = shellQuote(target);
190
138
  const n = shellQuote(nonce);
@@ -192,7 +140,6 @@ export function egressAttemptCommand(vector, target, nonce) {
192
140
  case "https-tcp":
193
141
  return `curl -sS --max-time 5 ${q}/${n} >/dev/null 2>&1; echo rc=$?`;
194
142
  case "dns":
195
- // a DNS lookup of a nonce-labelled subdomain — exfil-via-DNS channel.
196
143
  return `nslookup ${shellQuote(`${nonce}.${target}`)} >/dev/null 2>&1; echo rc=$?`;
197
144
  case "udp":
198
145
  return `printf %s ${n} | nc -u -w2 ${q} 53 >/dev/null 2>&1; echo rc=$?`;
@@ -204,15 +151,8 @@ export function egressAttemptCommand(vector, target, nonce) {
204
151
  return `printf %s ${n} | nc -U ${q} >/dev/null 2>&1; echo rc=$?`;
205
152
  }
206
153
  }
207
- /**
208
- * P3 verdict — PASS iff egress is actually enforced AND the collector saw NO nonce AND the allowlisted control
209
- * works AND (if applicable) the host resource is untouched. Against the CURRENT boundary `egressEnforcementPresent`
210
- * is FALSE → `egressEnforced:false` → P3 cannot pass. That is a real finding (the G-c blocker), not fake-green.
211
- */
212
154
  export function gradeP3(o) {
213
155
  const egressEnforced = o.egressEnforcementPresent && !o.collectorObservedNonce;
214
- // 🔴 (review LOW) a block is only real when SOMETHING enforces it: with no enforcement present, a curl that
215
- // merely failed (network blip / unresolved host) is NOT "blocked" — reads false when nothing is enforcing.
216
156
  const nonAllowlistedBlocked = o.egressEnforcementPresent && !o.collectorObservedNonce;
217
157
  const a4Ok = !o.a4Applicable || o.a4HostUntouched;
218
158
  const passed = egressEnforced && nonAllowlistedBlocked && o.allowlistedWorks && a4Ok;