@oscharko-dev/keiko-server 0.2.8 → 0.2.9

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 (281) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/chat-handlers.d.ts +18 -2
  3. package/dist/chat-handlers.d.ts.map +1 -1
  4. package/dist/chat-handlers.js +185 -3
  5. package/dist/command-runner-errors.d.ts +17 -0
  6. package/dist/command-runner-errors.d.ts.map +1 -0
  7. package/dist/command-runner-errors.js +37 -0
  8. package/dist/command-runner-evidence.d.ts +23 -0
  9. package/dist/command-runner-evidence.d.ts.map +1 -0
  10. package/dist/command-runner-evidence.js +69 -0
  11. package/dist/command-runner-routes.d.ts +7 -0
  12. package/dist/command-runner-routes.d.ts.map +1 -0
  13. package/dist/command-runner-routes.js +175 -0
  14. package/dist/command-runner.d.ts +29 -0
  15. package/dist/command-runner.d.ts.map +1 -0
  16. package/dist/command-runner.js +348 -0
  17. package/dist/conversation-prompt.d.ts +2 -2
  18. package/dist/conversation-prompt.d.ts.map +1 -1
  19. package/dist/conversation-prompt.js +17 -1
  20. package/dist/csp.d.ts.map +1 -1
  21. package/dist/csp.js +3 -0
  22. package/dist/deps.d.ts +27 -1
  23. package/dist/deps.d.ts.map +1 -1
  24. package/dist/deps.js +288 -13
  25. package/dist/discussion-prompt.d.ts +4 -0
  26. package/dist/discussion-prompt.d.ts.map +1 -0
  27. package/dist/discussion-prompt.js +19 -0
  28. package/dist/editor/agentActionAudit.d.ts +18 -0
  29. package/dist/editor/agentActionAudit.d.ts.map +1 -0
  30. package/dist/editor/agentActionAudit.js +80 -0
  31. package/dist/editor/agentRoutes.d.ts +1 -0
  32. package/dist/editor/agentRoutes.d.ts.map +1 -1
  33. package/dist/editor/agentRoutes.js +292 -55
  34. package/dist/editor/agentSessionRegistry.d.ts +35 -0
  35. package/dist/editor/agentSessionRegistry.d.ts.map +1 -0
  36. package/dist/editor/agentSessionRegistry.js +243 -0
  37. package/dist/editor/completionRoutes.d.ts.map +1 -1
  38. package/dist/editor/completionRoutes.js +5 -10
  39. package/dist/editor/languageRoutes.d.ts +12 -1
  40. package/dist/editor/languageRoutes.d.ts.map +1 -1
  41. package/dist/editor/languageRoutes.js +71 -8
  42. package/dist/editor/languageService.d.ts +3 -2
  43. package/dist/editor/languageService.d.ts.map +1 -1
  44. package/dist/editor/languageService.js +41 -3
  45. package/dist/editor/languageServiceHost.d.ts.map +1 -1
  46. package/dist/editor/languageServiceHost.js +2 -2
  47. package/dist/editor/lsp/hostLanguageOperation.d.ts +17 -0
  48. package/dist/editor/lsp/hostLanguageOperation.d.ts.map +1 -0
  49. package/dist/editor/lsp/hostLanguageOperation.js +436 -0
  50. package/dist/editor/lsp/hostLanguageProviders.d.ts +26 -0
  51. package/dist/editor/lsp/hostLanguageProviders.d.ts.map +1 -0
  52. package/dist/editor/lsp/hostLanguageProviders.js +161 -0
  53. package/dist/editor/lsp/lspFrameCodec.d.ts +13 -0
  54. package/dist/editor/lsp/lspFrameCodec.d.ts.map +1 -0
  55. package/dist/editor/lsp/lspFrameCodec.js +164 -0
  56. package/dist/editor/lsp/lspJsonRpcClient.d.ts +34 -0
  57. package/dist/editor/lsp/lspJsonRpcClient.d.ts.map +1 -0
  58. package/dist/editor/lsp/lspJsonRpcClient.js +173 -0
  59. package/dist/editor/lsp/lspLanguageProvider.d.ts +7 -0
  60. package/dist/editor/lsp/lspLanguageProvider.d.ts.map +1 -0
  61. package/dist/editor/lsp/lspLanguageProvider.js +29 -0
  62. package/dist/editor/lsp/lspLifecycleLedger.d.ts +5 -0
  63. package/dist/editor/lsp/lspLifecycleLedger.d.ts.map +1 -0
  64. package/dist/editor/lsp/lspLifecycleLedger.js +37 -0
  65. package/dist/editor/lsp/lspNodeAdapter.d.ts +31 -0
  66. package/dist/editor/lsp/lspNodeAdapter.d.ts.map +1 -0
  67. package/dist/editor/lsp/lspNodeAdapter.js +230 -0
  68. package/dist/editor/lsp/lspProcessManager.d.ts +24 -0
  69. package/dist/editor/lsp/lspProcessManager.d.ts.map +1 -0
  70. package/dist/editor/lsp/lspProcessManager.js +255 -0
  71. package/dist/editor/lsp/lspRestartThrottle.d.ts +6 -0
  72. package/dist/editor/lsp/lspRestartThrottle.d.ts.map +1 -0
  73. package/dist/editor/lsp/lspRestartThrottle.js +24 -0
  74. package/dist/editor/lsp/lspStatusRoute.d.ts +8 -0
  75. package/dist/editor/lsp/lspStatusRoute.d.ts.map +1 -0
  76. package/dist/editor/lsp/lspStatusRoute.js +22 -0
  77. package/dist/editor/lsp/lspTransport.d.ts +19 -0
  78. package/dist/editor/lsp/lspTransport.d.ts.map +1 -0
  79. package/dist/editor/lsp/lspTransport.js +55 -0
  80. package/dist/editor/lsp/testing/fakeLspProcess.d.ts +23 -0
  81. package/dist/editor/lsp/testing/fakeLspProcess.d.ts.map +1 -0
  82. package/dist/editor/lsp/testing/fakeLspProcess.js +132 -0
  83. package/dist/files.d.ts +45 -0
  84. package/dist/files.d.ts.map +1 -1
  85. package/dist/files.js +631 -7
  86. package/dist/gateway-readiness.js +3 -3
  87. package/dist/gateway-setup.d.ts +2 -0
  88. package/dist/gateway-setup.d.ts.map +1 -1
  89. package/dist/gateway-setup.js +275 -11
  90. package/dist/gitDelivery/actionSheetProjection.d.ts +30 -0
  91. package/dist/gitDelivery/actionSheetProjection.d.ts.map +1 -0
  92. package/dist/gitDelivery/actionSheetProjection.js +206 -0
  93. package/dist/gitDelivery/actionSheetRoutes.d.ts +29 -0
  94. package/dist/gitDelivery/actionSheetRoutes.d.ts.map +1 -0
  95. package/dist/gitDelivery/actionSheetRoutes.js +293 -0
  96. package/dist/gitDelivery/agentOperationsRoutes.d.ts +33 -0
  97. package/dist/gitDelivery/agentOperationsRoutes.d.ts.map +1 -0
  98. package/dist/gitDelivery/agentOperationsRoutes.js +405 -0
  99. package/dist/gitDelivery/commitRoutes.d.ts +23 -0
  100. package/dist/gitDelivery/commitRoutes.d.ts.map +1 -0
  101. package/dist/gitDelivery/commitRoutes.js +204 -0
  102. package/dist/gitDelivery/evidenceRoutes.d.ts +9 -0
  103. package/dist/gitDelivery/evidenceRoutes.d.ts.map +1 -0
  104. package/dist/gitDelivery/evidenceRoutes.js +101 -0
  105. package/dist/gitDelivery/execution.d.ts +38 -0
  106. package/dist/gitDelivery/execution.d.ts.map +1 -0
  107. package/dist/gitDelivery/execution.js +117 -0
  108. package/dist/gitDelivery/localMutationRoutes.d.ts +30 -0
  109. package/dist/gitDelivery/localMutationRoutes.d.ts.map +1 -0
  110. package/dist/gitDelivery/localMutationRoutes.js +165 -0
  111. package/dist/gitDelivery/mergeExecution.d.ts +63 -0
  112. package/dist/gitDelivery/mergeExecution.d.ts.map +1 -0
  113. package/dist/gitDelivery/mergeExecution.js +168 -0
  114. package/dist/gitDelivery/mergeRoutes.d.ts +12 -0
  115. package/dist/gitDelivery/mergeRoutes.d.ts.map +1 -0
  116. package/dist/gitDelivery/mergeRoutes.js +218 -0
  117. package/dist/gitDelivery/mutationEvidenceLedger.d.ts +23 -0
  118. package/dist/gitDelivery/mutationEvidenceLedger.d.ts.map +1 -0
  119. package/dist/gitDelivery/mutationEvidenceLedger.js +87 -0
  120. package/dist/gitDelivery/prExecution.d.ts +54 -0
  121. package/dist/gitDelivery/prExecution.d.ts.map +1 -0
  122. package/dist/gitDelivery/prExecution.js +192 -0
  123. package/dist/gitDelivery/prRoutes.d.ts +12 -0
  124. package/dist/gitDelivery/prRoutes.d.ts.map +1 -0
  125. package/dist/gitDelivery/prRoutes.js +256 -0
  126. package/dist/gitDelivery/pushExecution.d.ts +43 -0
  127. package/dist/gitDelivery/pushExecution.d.ts.map +1 -0
  128. package/dist/gitDelivery/pushExecution.js +124 -0
  129. package/dist/gitDelivery/pushRoutes.d.ts +12 -0
  130. package/dist/gitDelivery/pushRoutes.d.ts.map +1 -0
  131. package/dist/gitDelivery/pushRoutes.js +200 -0
  132. package/dist/gitDelivery/requestGuards.d.ts +15 -0
  133. package/dist/gitDelivery/requestGuards.d.ts.map +1 -0
  134. package/dist/gitDelivery/requestGuards.js +97 -0
  135. package/dist/gitDelivery/syncEvidence.d.ts +37 -0
  136. package/dist/gitDelivery/syncEvidence.d.ts.map +1 -0
  137. package/dist/gitDelivery/syncEvidence.js +85 -0
  138. package/dist/gitDelivery/syncExecution.d.ts +30 -0
  139. package/dist/gitDelivery/syncExecution.d.ts.map +1 -0
  140. package/dist/gitDelivery/syncExecution.js +266 -0
  141. package/dist/gitDelivery/syncRoutes.d.ts +13 -0
  142. package/dist/gitDelivery/syncRoutes.d.ts.map +1 -0
  143. package/dist/gitDelivery/syncRoutes.js +200 -0
  144. package/dist/gitPorcelainStatus.d.ts +15 -0
  145. package/dist/gitPorcelainStatus.d.ts.map +1 -0
  146. package/dist/gitPorcelainStatus.js +104 -0
  147. package/dist/gitRepositoryReads.d.ts +10 -0
  148. package/dist/gitRepositoryReads.d.ts.map +1 -0
  149. package/dist/gitRepositoryReads.js +314 -0
  150. package/dist/gitRepositoryRoutes.d.ts +7 -0
  151. package/dist/gitRepositoryRoutes.d.ts.map +1 -0
  152. package/dist/gitRepositoryRoutes.js +221 -0
  153. package/dist/gitRoutes.d.ts +66 -0
  154. package/dist/gitRoutes.d.ts.map +1 -0
  155. package/dist/gitRoutes.js +543 -0
  156. package/dist/governed-workflow.d.ts +2 -0
  157. package/dist/governed-workflow.d.ts.map +1 -1
  158. package/dist/governed-workflow.js +4 -0
  159. package/dist/grounded-qa.d.ts +11 -0
  160. package/dist/grounded-qa.d.ts.map +1 -1
  161. package/dist/grounded-qa.js +13 -4
  162. package/dist/headers.d.ts +4 -1
  163. package/dist/headers.d.ts.map +1 -1
  164. package/dist/headers.js +11 -4
  165. package/dist/index.d.ts +8 -1
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +9 -1
  168. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts +1 -1
  169. package/dist/qualityIntelligence/figmaSnapshotRoutes.d.ts.map +1 -1
  170. package/dist/qualityIntelligence/figmaSnapshotRoutes.js +1 -1
  171. package/dist/read-handlers.d.ts +5 -0
  172. package/dist/read-handlers.d.ts.map +1 -1
  173. package/dist/read-handlers.js +57 -1
  174. package/dist/routes.d.ts.map +1 -1
  175. package/dist/routes.js +259 -6
  176. package/dist/run-engine.d.ts.map +1 -1
  177. package/dist/run-engine.js +3 -0
  178. package/dist/run-handlers.d.ts.map +1 -1
  179. package/dist/run-handlers.js +74 -4
  180. package/dist/run-request.d.ts +11 -0
  181. package/dist/run-request.d.ts.map +1 -1
  182. package/dist/run-request.js +158 -10
  183. package/dist/runtime/capabilityDetector.d.ts +38 -0
  184. package/dist/runtime/capabilityDetector.d.ts.map +1 -0
  185. package/dist/runtime/capabilityDetector.js +443 -0
  186. package/dist/runtime/capabilityRoutes.d.ts +9 -0
  187. package/dist/runtime/capabilityRoutes.d.ts.map +1 -0
  188. package/dist/runtime/capabilityRoutes.js +45 -0
  189. package/dist/runtime/containerEngineDetector.d.ts +17 -0
  190. package/dist/runtime/containerEngineDetector.d.ts.map +1 -0
  191. package/dist/runtime/containerEngineDetector.js +222 -0
  192. package/dist/runtime/containerRoutes.d.ts +8 -0
  193. package/dist/runtime/containerRoutes.d.ts.map +1 -0
  194. package/dist/runtime/containerRoutes.js +207 -0
  195. package/dist/runtime/containerRunner-errors.d.ts +18 -0
  196. package/dist/runtime/containerRunner-errors.d.ts.map +1 -0
  197. package/dist/runtime/containerRunner-errors.js +42 -0
  198. package/dist/runtime/containerRunner-evidence.d.ts +24 -0
  199. package/dist/runtime/containerRunner-evidence.d.ts.map +1 -0
  200. package/dist/runtime/containerRunner-evidence.js +74 -0
  201. package/dist/runtime/containerRunner.d.ts +37 -0
  202. package/dist/runtime/containerRunner.d.ts.map +1 -0
  203. package/dist/runtime/containerRunner.js +443 -0
  204. package/dist/server.d.ts.map +1 -1
  205. package/dist/server.js +24 -4
  206. package/dist/store/schema.d.ts +1 -1
  207. package/dist/store/schema.d.ts.map +1 -1
  208. package/dist/store/schema.js +62 -1
  209. package/dist/task-workspace/active-store.d.ts +21 -0
  210. package/dist/task-workspace/active-store.d.ts.map +1 -0
  211. package/dist/task-workspace/active-store.js +55 -0
  212. package/dist/task-workspace/authorization.d.ts +7 -0
  213. package/dist/task-workspace/authorization.d.ts.map +1 -0
  214. package/dist/task-workspace/authorization.js +54 -0
  215. package/dist/task-workspace/binding.d.ts +3 -0
  216. package/dist/task-workspace/binding.d.ts.map +1 -0
  217. package/dist/task-workspace/binding.js +22 -0
  218. package/dist/task-workspace/cleanup.d.ts +4 -0
  219. package/dist/task-workspace/cleanup.d.ts.map +1 -0
  220. package/dist/task-workspace/cleanup.js +428 -0
  221. package/dist/task-workspace/errors.d.ts +14 -0
  222. package/dist/task-workspace/errors.d.ts.map +1 -0
  223. package/dist/task-workspace/errors.js +81 -0
  224. package/dist/task-workspace/evidence.d.ts +32 -0
  225. package/dist/task-workspace/evidence.d.ts.map +1 -0
  226. package/dist/task-workspace/evidence.js +52 -0
  227. package/dist/task-workspace/field-safety.d.ts +3 -0
  228. package/dist/task-workspace/field-safety.d.ts.map +1 -0
  229. package/dist/task-workspace/field-safety.js +42 -0
  230. package/dist/task-workspace/health.d.ts +4 -0
  231. package/dist/task-workspace/health.d.ts.map +1 -0
  232. package/dist/task-workspace/health.js +163 -0
  233. package/dist/task-workspace/lifecycle.d.ts +3 -0
  234. package/dist/task-workspace/lifecycle.d.ts.map +1 -0
  235. package/dist/task-workspace/lifecycle.js +248 -0
  236. package/dist/task-workspace/locks.d.ts +13 -0
  237. package/dist/task-workspace/locks.d.ts.map +1 -0
  238. package/dist/task-workspace/locks.js +44 -0
  239. package/dist/task-workspace/managed-root.d.ts +7 -0
  240. package/dist/task-workspace/managed-root.d.ts.map +1 -0
  241. package/dist/task-workspace/managed-root.js +98 -0
  242. package/dist/task-workspace/mutex.d.ts +8 -0
  243. package/dist/task-workspace/mutex.d.ts.map +1 -0
  244. package/dist/task-workspace/mutex.js +82 -0
  245. package/dist/task-workspace/naming.d.ts +15 -0
  246. package/dist/task-workspace/naming.d.ts.map +1 -0
  247. package/dist/task-workspace/naming.js +0 -0
  248. package/dist/task-workspace/provisioning.d.ts +3 -0
  249. package/dist/task-workspace/provisioning.d.ts.map +1 -0
  250. package/dist/task-workspace/provisioning.js +528 -0
  251. package/dist/task-workspace/reconciliation.d.ts +15 -0
  252. package/dist/task-workspace/reconciliation.d.ts.map +1 -0
  253. package/dist/task-workspace/reconciliation.js +274 -0
  254. package/dist/task-workspace/repair.d.ts +3 -0
  255. package/dist/task-workspace/repair.d.ts.map +1 -0
  256. package/dist/task-workspace/repair.js +286 -0
  257. package/dist/task-workspace/routes.d.ts +19 -0
  258. package/dist/task-workspace/routes.d.ts.map +1 -0
  259. package/dist/task-workspace/routes.js +481 -0
  260. package/dist/task-workspace/store.d.ts +12 -0
  261. package/dist/task-workspace/store.d.ts.map +1 -0
  262. package/dist/task-workspace/store.js +128 -0
  263. package/dist/task-workspace/types.d.ts +170 -0
  264. package/dist/task-workspace/types.d.ts.map +1 -0
  265. package/dist/task-workspace/types.js +5 -0
  266. package/dist/voice-action-governance.d.ts +23 -0
  267. package/dist/voice-action-governance.d.ts.map +1 -0
  268. package/dist/voice-action-governance.js +126 -0
  269. package/dist/voice-handlers.d.ts +6 -0
  270. package/dist/voice-handlers.d.ts.map +1 -0
  271. package/dist/voice-handlers.js +570 -0
  272. package/dist/voice-realtime-grounded-tool.d.ts +31 -0
  273. package/dist/voice-realtime-grounded-tool.d.ts.map +1 -0
  274. package/dist/voice-realtime-grounded-tool.js +322 -0
  275. package/dist/voice-realtime.d.ts +69 -0
  276. package/dist/voice-realtime.d.ts.map +1 -0
  277. package/dist/voice-realtime.js +787 -0
  278. package/dist/workspace-state-handlers.d.ts +5 -0
  279. package/dist/workspace-state-handlers.d.ts.map +1 -0
  280. package/dist/workspace-state-handlers.js +106 -0
  281. package/package.json +20 -19
@@ -0,0 +1,222 @@
1
+ // Issue #1388 (Epic #1491, ADR-0070, D1) — opt-in ACTIVE container engine probe. Distinct from the
2
+ // #1385 metadata-only detector (capabilityDetector.ts), which stays passive on the hot path; this
3
+ // module is invoked ONLY on explicit request (GET /api/containers/capability) and actively runs
4
+ // `docker version` / `docker info` (and the podman equivalents) THROUGH the single governed
5
+ // runCommand spawn boundary (deny-by-default CONTAINER_TASK_RULES, no shell, name-allowlisted env +
6
+ // ephemeral HOME, byte-capped + redacted output, timeout SIGTERM→SIGKILL). The host CLI process uses
7
+ // network:"inherit" because it must reach the local daemon socket.
8
+ //
9
+ // NEVER THROWS: every outcome — including a probe timeout (probe-timed-out) or an unparseable
10
+ // response (probe-failed) — is a structured ContainerEngineStatus. The KEIKO_CONTAINERS_DISABLED
11
+ // kill-switch short-circuits every engine to policy-blocked WITHOUT spawning.
12
+ import { CommandDeniedError, CommandTimeoutError, DEFAULT_SANDBOX_POLICY, runCommand, } from "@oscharko-dev/keiko-tools";
13
+ import { nodeSpawnFn } from "@oscharko-dev/keiko-tools/internal/exec";
14
+ import { CONTAINER_ENGINE_IDS, CONTAINER_RUNTIME_SCHEMA_VERSION, CONTAINER_TASK_RULES, } from "@oscharko-dev/keiko-contracts";
15
+ export const DEFAULT_CONTAINER_PROBE_DEADLINE_MS = 4_000; // generous: real daemon round-trip
16
+ export const SUPPORTED_DOCKER_MAJOR = 20; // engine-version floor; below → "unsupported"
17
+ const PER_CALL_TIMEOUT_MS = 2_000; // per `version`/`info` call; the overall deadline bounds both
18
+ export const KEIKO_CONTAINERS_DISABLED_ENV = "KEIKO_CONTAINERS_DISABLED";
19
+ // runCommand needs a workspace for cwd containment; the probe never mounts anything, so a minimal
20
+ // host-level descriptor is sufficient. The container surface is host-level (capability is not
21
+ // project-scoped), so the detector accepts an optional workspace and synthesizes a stub otherwise.
22
+ function hostWorkspace(workspace) {
23
+ if (workspace !== undefined) {
24
+ return workspace;
25
+ }
26
+ return {
27
+ root: process.cwd(),
28
+ name: undefined,
29
+ version: undefined,
30
+ testFramework: "unknown",
31
+ sourceDirs: [],
32
+ testDirs: [],
33
+ languages: [],
34
+ ignoreLines: [],
35
+ };
36
+ }
37
+ // Static remediation catalog (D1): a fixed string per state, NEVER raw engine/OS error text.
38
+ const REMEDIATION_HINTS = {
39
+ available: undefined,
40
+ missing: "Install the container engine or make it available on PATH.",
41
+ "not-running": "Start Docker Desktop or the container daemon.",
42
+ "permission-denied": "Grant your user access to the container socket (e.g. the docker group).",
43
+ unsupported: "Upgrade the container engine to a supported version.",
44
+ "policy-blocked": "Container execution is disabled by policy on this host.",
45
+ };
46
+ function disabledByKillSwitch(processEnv) {
47
+ const raw = processEnv[KEIKO_CONTAINERS_DISABLED_ENV];
48
+ return typeof raw === "string" && raw.length > 0;
49
+ }
50
+ // Parses the leading integer of a `version --format {{.Server.Version}}` line. A value like
51
+ // "24.0.7" → 24. Returns undefined when no leading integer is present (unparseable → probe-failed).
52
+ function parseMajorVersion(raw) {
53
+ const match = /^\s*v?(\d+)\b/.exec(raw);
54
+ if (match === null) {
55
+ return undefined;
56
+ }
57
+ const major = Number.parseInt(match[1] ?? "", 10);
58
+ return Number.isInteger(major) ? major : undefined;
59
+ }
60
+ function isNotFoundDenial(error) {
61
+ // runCommand throws CommandDeniedError with "not found on PATH" for a missing binary; any other
62
+ // denial is a policy decision (allowlist/cwd), surfaced as policy-blocked.
63
+ return error.message.includes("not found on PATH");
64
+ }
65
+ const DAEMON_DOWN_SIGNATURES = [
66
+ "cannot connect to the docker daemon",
67
+ "is the docker daemon running",
68
+ "connect: connection refused",
69
+ "error during connect",
70
+ "cannot connect to podman",
71
+ "no such file or directory", // socket path absent
72
+ ];
73
+ const PERMISSION_SIGNATURES = ["permission denied", "eacces", "got permission denied"];
74
+ function matchesSignature(haystack, signatures) {
75
+ const lower = haystack.toLowerCase();
76
+ return signatures.some((needle) => lower.includes(needle));
77
+ }
78
+ // Maps a settled (resolved) `version` CommandResult to an engine resolution. exitCode 0 with a
79
+ // parseable in-range version continues to the daemon-liveness `info` step; everything else is a
80
+ // structured unavailable state per the D1 table.
81
+ function resolveFromVersionResult(result) {
82
+ const combined = `${result.stdout}\n${result.stderr}`;
83
+ if (result.exitCode !== 0) {
84
+ if (matchesSignature(combined, PERMISSION_SIGNATURES)) {
85
+ return { state: "permission-denied", unavailableReason: "executable-not-runnable" };
86
+ }
87
+ return { state: "not-running", unavailableReason: "daemon-not-running" };
88
+ }
89
+ const major = parseMajorVersion(result.stdout);
90
+ if (major === undefined) {
91
+ return { state: "unsupported", unavailableReason: "probe-failed" };
92
+ }
93
+ if (major < SUPPORTED_DOCKER_MAJOR) {
94
+ return {
95
+ state: "unsupported",
96
+ version: result.stdout.trim(),
97
+ unavailableReason: "unsupported-version",
98
+ };
99
+ }
100
+ return { state: "available", version: result.stdout.trim() };
101
+ }
102
+ // Maps a thrown error from the `version` call. CommandTimeoutError → probe-timed-out (state stays
103
+ // "missing" so an unreachable engine never reads as available); a not-found denial → missing; any
104
+ // other denial → policy-blocked.
105
+ function resolveFromVersionError(error) {
106
+ if (error instanceof CommandTimeoutError) {
107
+ return { state: "missing", unavailableReason: "probe-timed-out" };
108
+ }
109
+ if (error instanceof CommandDeniedError) {
110
+ return isNotFoundDenial(error)
111
+ ? { state: "missing", unavailableReason: "executable-not-found" }
112
+ : { state: "policy-blocked", unavailableReason: "policy-blocked" };
113
+ }
114
+ return { state: "unsupported", unavailableReason: "probe-failed" };
115
+ }
116
+ function buildProbeDeps(deps) {
117
+ const policy = deps.policy ?? DEFAULT_SANDBOX_POLICY;
118
+ return {
119
+ workspace: hostWorkspace(deps.workspace),
120
+ // network:"inherit" — the host CLI process must reach the local daemon socket (D4). The CONTAINER
121
+ // is isolated by --network none at run time; detection runs only `version`/`info`.
122
+ policy: { ...policy, network: "inherit" },
123
+ commandRules: CONTAINER_TASK_RULES,
124
+ spawn: nodeSpawnFn,
125
+ processEnv: deps.processEnv,
126
+ now: deps.now,
127
+ };
128
+ }
129
+ // Confirms daemon liveness via `info`. A clean exit keeps "available"; a non-zero exit or a daemon-
130
+ // down signature downgrades to not-running; an error is mapped like the version error.
131
+ async function confirmDaemon(engine, runDeps, run, signal, base) {
132
+ try {
133
+ const result = await run({
134
+ command: engine,
135
+ args: ["info", "--format", "{{.ServerErrors}}"],
136
+ cwd: undefined,
137
+ timeoutMs: PER_CALL_TIMEOUT_MS,
138
+ signal,
139
+ }, runDeps);
140
+ const combined = `${result.stdout}\n${result.stderr}`;
141
+ if (result.exitCode !== 0 || matchesSignature(combined, DAEMON_DOWN_SIGNATURES)) {
142
+ return { state: "not-running", unavailableReason: "daemon-not-running" };
143
+ }
144
+ return base;
145
+ }
146
+ catch (error) {
147
+ if (error instanceof CommandTimeoutError) {
148
+ return { state: "not-running", unavailableReason: "probe-timed-out" };
149
+ }
150
+ return { state: "not-running", unavailableReason: "daemon-not-running" };
151
+ }
152
+ }
153
+ async function probeEngine(engine, deps, runDeps) {
154
+ const controller = new AbortController();
155
+ let versionResolution;
156
+ try {
157
+ const result = await deps.runCommand({
158
+ command: engine,
159
+ args: ["version", "--format", "{{.Server.Version}}"],
160
+ cwd: undefined,
161
+ timeoutMs: PER_CALL_TIMEOUT_MS,
162
+ signal: controller.signal,
163
+ }, runDeps);
164
+ versionResolution = resolveFromVersionResult(result);
165
+ }
166
+ catch (error) {
167
+ return resolveFromVersionError(error);
168
+ }
169
+ if (versionResolution.state !== "available") {
170
+ return versionResolution;
171
+ }
172
+ return confirmDaemon(engine, runDeps, deps.runCommand, controller.signal, versionResolution);
173
+ }
174
+ function statusFromResolution(engine, resolution) {
175
+ const hint = REMEDIATION_HINTS[resolution.state];
176
+ return {
177
+ engine,
178
+ state: resolution.state,
179
+ ...(resolution.version === undefined ? {} : { version: resolution.version }),
180
+ ...(resolution.unavailableReason === undefined
181
+ ? {}
182
+ : { unavailableReason: resolution.unavailableReason }),
183
+ ...(hint === undefined ? {} : { remediationHint: hint }),
184
+ };
185
+ }
186
+ function killSwitchStatus(engine) {
187
+ return {
188
+ engine,
189
+ state: "policy-blocked",
190
+ unavailableReason: "policy-blocked",
191
+ remediationHint: REMEDIATION_HINTS["policy-blocked"],
192
+ };
193
+ }
194
+ // Active container-engine probe (D1). NEVER throws; returns a structured ContainerCapabilityResponse.
195
+ export async function detectContainerEngines(deps) {
196
+ const generatedAtMs = deps.now();
197
+ const deadlineMs = deps.deadlineMs ?? DEFAULT_CONTAINER_PROBE_DEADLINE_MS;
198
+ const engines = deps.engines ?? CONTAINER_ENGINE_IDS;
199
+ // Kill-switch: short-circuit EVERY engine to policy-blocked with NO spawn (assert in tests).
200
+ if (disabledByKillSwitch(deps.processEnv)) {
201
+ return {
202
+ schemaVersion: CONTAINER_RUNTIME_SCHEMA_VERSION,
203
+ generatedAtMs,
204
+ deadlineMs,
205
+ engines: engines.map((engine) => killSwitchStatus(engine)),
206
+ anyAvailable: false,
207
+ };
208
+ }
209
+ const runDeps = buildProbeDeps(deps);
210
+ const statuses = [];
211
+ for (const engine of engines) {
212
+ const resolution = await probeEngine(engine, deps, runDeps);
213
+ statuses.push(statusFromResolution(engine, resolution));
214
+ }
215
+ return {
216
+ schemaVersion: CONTAINER_RUNTIME_SCHEMA_VERSION,
217
+ generatedAtMs,
218
+ deadlineMs,
219
+ engines: statuses,
220
+ anyAvailable: statuses.some((status) => status.state === "available"),
221
+ };
222
+ }
@@ -0,0 +1,8 @@
1
+ import type { UiHandlerDeps } from "../deps.js";
2
+ import { type HandlerOutcome, type RouteContext, type RouteResult } from "../routes.js";
3
+ export declare function handleContainerCapability(ctx: RouteContext, deps: UiHandlerDeps): Promise<RouteResult>;
4
+ export declare function handleContainerCatalog(ctx: RouteContext, deps: UiHandlerDeps): Promise<RouteResult>;
5
+ export declare function handleCreateContainerRun(ctx: RouteContext, deps: UiHandlerDeps): Promise<RouteResult>;
6
+ export declare function handleDeleteContainerRun(ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
7
+ export declare function handleContainerEvents(ctx: RouteContext, deps: UiHandlerDeps): HandlerOutcome;
8
+ //# sourceMappingURL=containerRoutes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerRoutes.d.ts","sourceRoot":"","sources":["../../src/runtime/containerRoutes.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAiGtB,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAoBtB;AAID,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAiBtB;AAKD,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAQ5F;AAID,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,GAAG,cAAc,CAQ5F"}
@@ -0,0 +1,207 @@
1
+ // Issue #1388 (Epic #1491, ADR-0070, D5) — five /api/containers/* BFF route handlers for the governed
2
+ // container pilot. CSRF is enforced by the server's state-changing-request gate (POST/DELETE flow
3
+ // through it); GET routes are read-only and exempt. SSE framing mirrors /api/commands/*/events.
4
+ // Layer-2 redaction is applied to the run RESULT and to EVERY SSE frame. The capability route
5
+ // additionally requires a registered project root when `root` is supplied.
6
+ //
7
+ // GET /api/containers/capability?root=… active engine probe → ContainerCapabilityResponse
8
+ // GET /api/containers/catalog?projectId=… catalog (503 when no engine)
9
+ // POST /api/containers/runs run a catalog task → structured result (redacted)
10
+ // DELETE /api/containers/runs/:runId cancel an in-flight run
11
+ // GET /api/containers/events SSE stream of run lifecycle events (redacted)
12
+ import { parseContainerRunRequest } from "@oscharko-dev/keiko-contracts";
13
+ import { ContainerRunnerError } from "./containerRunner-errors.js";
14
+ import { SSE_HEADERS, readyMessage } from "../sse.js";
15
+ import { errorBody, STREAMING, } from "../routes.js";
16
+ const MAX_CONTAINER_BODY_BYTES = 16_000;
17
+ class BodyTooLargeError extends Error {
18
+ constructor() {
19
+ super("container runner request body too large");
20
+ this.name = "BodyTooLargeError";
21
+ }
22
+ }
23
+ function noRunnerDeps() {
24
+ return {
25
+ status: 503,
26
+ body: errorBody("CONTAINER_ENGINE_UNAVAILABLE", "Container runner is not configured for this BFF."),
27
+ };
28
+ }
29
+ function requireRunner(deps) {
30
+ return deps.containerRunner ?? noRunnerDeps();
31
+ }
32
+ function isRouteResult(value) {
33
+ return typeof value.status === "number";
34
+ }
35
+ function toRouteResult(error) {
36
+ return { status: error.status, body: errorBody(error.code, error.message) };
37
+ }
38
+ function readBody(req) {
39
+ return new Promise((resolve, reject) => {
40
+ const chunks = [];
41
+ let total = 0;
42
+ let capped = false;
43
+ req.on("data", (chunk) => {
44
+ total += chunk.length;
45
+ if (total > MAX_CONTAINER_BODY_BYTES) {
46
+ if (!capped) {
47
+ capped = true;
48
+ chunks.length = 0;
49
+ reject(new BodyTooLargeError());
50
+ req.resume();
51
+ }
52
+ return;
53
+ }
54
+ chunks.push(chunk);
55
+ });
56
+ req.on("end", () => {
57
+ if (!capped)
58
+ resolve(Buffer.concat(chunks).toString("utf8"));
59
+ });
60
+ req.on("error", reject);
61
+ });
62
+ }
63
+ async function readJsonObject(req) {
64
+ const raw = await readBody(req);
65
+ if (raw.length === 0)
66
+ return {};
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(raw);
70
+ }
71
+ catch {
72
+ throw new ContainerRunnerError("BAD_REQUEST", "Request body is not valid JSON.");
73
+ }
74
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
75
+ throw new ContainerRunnerError("BAD_REQUEST", "Request body must be a JSON object.");
76
+ }
77
+ return parsed;
78
+ }
79
+ async function runHandler(work) {
80
+ try {
81
+ return await work();
82
+ }
83
+ catch (error) {
84
+ if (error instanceof BodyTooLargeError) {
85
+ return {
86
+ status: 413,
87
+ body: errorBody("PAYLOAD_TOO_LARGE", "Request body exceeds the size limit."),
88
+ };
89
+ }
90
+ if (error instanceof ContainerRunnerError)
91
+ return toRouteResult(error);
92
+ throw error;
93
+ }
94
+ }
95
+ function redactValue(deps, value) {
96
+ return deps.redactor(value);
97
+ }
98
+ // GET /api/containers/capability — active daemon probe. When `root` is supplied it MUST be a
99
+ // registered project (403 WORKSPACE_NOT_REGISTERED otherwise); `root` absent is allowed because the
100
+ // capability is host-level (no workspace mount context needed for detection). The response is redacted.
101
+ export async function handleContainerCapability(ctx, deps) {
102
+ const guard = requireRunner(deps);
103
+ if (isRouteResult(guard))
104
+ return guard;
105
+ const rawRoot = ctx.url.searchParams.get("root");
106
+ const root = rawRoot === null ? undefined : rawRoot.trim();
107
+ if (root !== undefined && root.length > 0) {
108
+ if (!deps.store.listProjects().some((project) => project.path === root)) {
109
+ return {
110
+ status: 403,
111
+ body: errorBody("WORKSPACE_NOT_REGISTERED", "The workspace directory is not a registered project."),
112
+ };
113
+ }
114
+ }
115
+ return runHandler(async () => {
116
+ const capability = await guard.capability(root ?? "");
117
+ return { status: 200, body: redactValue(deps, capability) };
118
+ });
119
+ }
120
+ // GET /api/containers/catalog — the allowlisted catalog for a project. 503 when no engine is
121
+ // available so the surface degrades gracefully rather than throwing.
122
+ export async function handleContainerCatalog(ctx, deps) {
123
+ const guard = requireRunner(deps);
124
+ if (isRouteResult(guard))
125
+ return guard;
126
+ return runHandler(async () => {
127
+ const projectId = ctx.url.searchParams.get("projectId");
128
+ if (projectId === null || projectId.length === 0) {
129
+ throw new ContainerRunnerError("BAD_REQUEST", "Query parameter 'projectId' is required.");
130
+ }
131
+ const catalog = await guard.listCatalog(projectId);
132
+ if (!catalog.engineAvailable) {
133
+ throw new ContainerRunnerError("CONTAINER_ENGINE_UNAVAILABLE", "No container engine is available.");
134
+ }
135
+ return { status: 200, body: redactValue(deps, catalog) };
136
+ });
137
+ }
138
+ // POST /api/containers/runs — run a catalog task. The result carries the structured outcome; Layer-2
139
+ // redaction is applied to stdout/stderr before the body reaches the browser. An engine-unavailable
140
+ // governance failure throws CONTAINER_ENGINE_UNAVAILABLE (503) before any run id is minted.
141
+ export async function handleCreateContainerRun(ctx, deps) {
142
+ const guard = requireRunner(deps);
143
+ if (isRouteResult(guard))
144
+ return guard;
145
+ return runHandler(async () => {
146
+ const body = await readJsonObject(ctx.req);
147
+ const parsed = parseContainerRunRequest(body);
148
+ if (!parsed.ok) {
149
+ throw new ContainerRunnerError("BAD_REQUEST", parsed.errors.join("; "));
150
+ }
151
+ const input = {
152
+ projectId: parsed.value.projectId,
153
+ taskId: parsed.value.taskId,
154
+ ...(parsed.value.timeoutMs === undefined ? {} : { timeoutMs: parsed.value.timeoutMs }),
155
+ ...(parsed.value.requestId === undefined ? {} : { requestId: parsed.value.requestId }),
156
+ };
157
+ const raw = await guard.execute(input);
158
+ const redactStr = (value) => {
159
+ const redacted = deps.redactor(value);
160
+ return typeof redacted === "string" ? redacted : value;
161
+ };
162
+ const result = { ...raw, stdout: redactStr(raw.stdout), stderr: redactStr(raw.stderr) };
163
+ return { status: 200, body: result };
164
+ });
165
+ }
166
+ export function handleDeleteContainerRun(ctx, deps) {
167
+ const guard = requireRunner(deps);
168
+ if (isRouteResult(guard))
169
+ return guard;
170
+ const runId = ctx.params.runId ?? "";
171
+ if (!guard.abort(runId)) {
172
+ return { status: 404, body: errorBody("RUN_NOT_FOUND", "Container run not found.") };
173
+ }
174
+ return { status: 200, body: { ok: true } };
175
+ }
176
+ // SSE — one runner event becomes one message with `event: container:<kind>` and a redacted JSON
177
+ // payload. A synthetic `ready` is emitted first so the client can transition from connecting to live.
178
+ export function handleContainerEvents(ctx, deps) {
179
+ const guard = requireRunner(deps);
180
+ if (isRouteResult(guard))
181
+ return guard;
182
+ openContainerSseStream(ctx.res, guard, deps.redactor);
183
+ ctx.req.on("close", () => {
184
+ ctx.res.end();
185
+ });
186
+ return STREAMING;
187
+ }
188
+ function openContainerSseStream(res, manager, redactor) {
189
+ res.writeHead(200, SSE_HEADERS);
190
+ let seq = 0;
191
+ const unsubscribe = manager.subscribe((event) => {
192
+ seq += 1;
193
+ writeContainerEvent(res, event, seq, redactor);
194
+ });
195
+ res.write(readyMessage());
196
+ res.on("close", () => {
197
+ unsubscribe();
198
+ });
199
+ }
200
+ function writeContainerEvent(res, event, seq, redactor) {
201
+ const redacted = redactor(event);
202
+ const data = JSON.stringify(redacted);
203
+ const frame = `id: ${String(seq)}\nevent: container:${event.kind}\ndata: ${data}\n\n`;
204
+ if (!res.write(frame)) {
205
+ res.destroy();
206
+ }
207
+ }
@@ -0,0 +1,18 @@
1
+ export declare const CONTAINER_RUNNER_ERROR_CODES: {
2
+ readonly PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND";
3
+ readonly TASK_NOT_FOUND: "TASK_NOT_FOUND";
4
+ readonly IMAGE_NOT_ALLOWED: "IMAGE_NOT_ALLOWED";
5
+ readonly RUN_LIMIT_EXCEEDED: "RUN_LIMIT_EXCEEDED";
6
+ readonly RUN_NOT_FOUND: "RUN_NOT_FOUND";
7
+ readonly BAD_REQUEST: "BAD_REQUEST";
8
+ readonly PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE";
9
+ readonly CONTAINER_ENGINE_UNAVAILABLE: "CONTAINER_ENGINE_UNAVAILABLE";
10
+ readonly INTERNAL: "INTERNAL";
11
+ };
12
+ export type ContainerRunnerErrorCode = (typeof CONTAINER_RUNNER_ERROR_CODES)[keyof typeof CONTAINER_RUNNER_ERROR_CODES];
13
+ export declare class ContainerRunnerError extends Error {
14
+ readonly code: ContainerRunnerErrorCode;
15
+ readonly status: number;
16
+ constructor(code: ContainerRunnerErrorCode, message: string);
17
+ }
18
+ //# sourceMappingURL=containerRunner-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerRunner-errors.d.ts","sourceRoot":"","sources":["../../src/runtime/containerRunner-errors.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,4BAA4B;;;;;;;;;;CAU/B,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAC;AAcnF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAgB,IAAI,EAAE,wBAAwB,CAAC;IAC/C,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM;CAMnE"}
@@ -0,0 +1,42 @@
1
+ // Issue #1388 (Epic #1491, ADR-0070) — typed failure modes for the governed container runner.
2
+ // Callers switch on `code`; messages are static strings that never leak filesystem paths, raw
3
+ // engine/OS error text, or the constructed argv into the HTTP response or SSE payload. Mirrors the
4
+ // #1387 command-runner error model.
5
+ //
6
+ // Only PRE-spawn GOVERNANCE failures surface as a thrown ContainerRunnerError (the route maps each
7
+ // to a 4xx/5xx envelope) — most importantly CONTAINER_ENGINE_UNAVAILABLE, thrown BEFORE any run id
8
+ // is minted so there is no run/event/evidence for a run that never started. Every actual EXECUTION
9
+ // outcome (non-zero exit, timeout, cancellation, image-missing, denied spawn) is reported as a
10
+ // ContainerRunResult with a `failureReason`, never as a thrown error.
11
+ export const CONTAINER_RUNNER_ERROR_CODES = {
12
+ PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND",
13
+ TASK_NOT_FOUND: "TASK_NOT_FOUND",
14
+ IMAGE_NOT_ALLOWED: "IMAGE_NOT_ALLOWED",
15
+ RUN_LIMIT_EXCEEDED: "RUN_LIMIT_EXCEEDED",
16
+ RUN_NOT_FOUND: "RUN_NOT_FOUND",
17
+ BAD_REQUEST: "BAD_REQUEST",
18
+ PAYLOAD_TOO_LARGE: "PAYLOAD_TOO_LARGE",
19
+ CONTAINER_ENGINE_UNAVAILABLE: "CONTAINER_ENGINE_UNAVAILABLE",
20
+ INTERNAL: "INTERNAL",
21
+ };
22
+ const STATUS_MAP = {
23
+ PROJECT_NOT_FOUND: 404,
24
+ TASK_NOT_FOUND: 404,
25
+ IMAGE_NOT_ALLOWED: 403,
26
+ RUN_LIMIT_EXCEEDED: 429,
27
+ RUN_NOT_FOUND: 404,
28
+ BAD_REQUEST: 400,
29
+ PAYLOAD_TOO_LARGE: 413,
30
+ CONTAINER_ENGINE_UNAVAILABLE: 503,
31
+ INTERNAL: 500,
32
+ };
33
+ export class ContainerRunnerError extends Error {
34
+ code;
35
+ status;
36
+ constructor(code, message) {
37
+ super(message);
38
+ this.name = "ContainerRunnerError";
39
+ this.code = code;
40
+ this.status = STATUS_MAP[code];
41
+ }
42
+ }
@@ -0,0 +1,24 @@
1
+ import type { EvidenceManifest, EvidenceStore } from "@oscharko-dev/keiko-evidence";
2
+ import type { ContainerEngineId, ContainerFailureReason, ContainerTaskKind } from "@oscharko-dev/keiko-contracts";
3
+ export declare const CONTAINER_RUN_EVIDENCE_KIND: "container-run";
4
+ export type ContainerRunEvidenceEntry = EvidenceManifest;
5
+ export interface ContainerRunEvidenceInput {
6
+ readonly runId: string;
7
+ readonly projectId: string;
8
+ readonly taskId: string;
9
+ readonly kind: ContainerTaskKind;
10
+ readonly engine: ContainerEngineId;
11
+ readonly imageId: string;
12
+ readonly argCount: number;
13
+ readonly exitCode: number | null;
14
+ readonly durationMs: number;
15
+ readonly timedOut: boolean;
16
+ readonly truncated: boolean;
17
+ readonly failureReason: ContainerFailureReason;
18
+ readonly stdoutBytes: number;
19
+ readonly stderrBytes: number;
20
+ readonly startedAt: number;
21
+ }
22
+ export declare function buildContainerRunEvidenceEntry(input: ContainerRunEvidenceInput): ContainerRunEvidenceEntry;
23
+ export declare function appendContainerRunEvidence(store: EvidenceStore, entry: ContainerRunEvidenceEntry, redact: (input: string) => string): string;
24
+ //# sourceMappingURL=containerRunner-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerRunner-evidence.d.ts","sourceRoot":"","sources":["../../src/runtime/containerRunner-evidence.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAIpF,OAAO,KAAK,EACV,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AAEvC,eAAO,MAAM,2BAA2B,EAAG,eAAwB,CAAC;AAEpE,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAEzD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAEnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAkBD,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,yBAAyB,GAC/B,yBAAyB,CAwC3B;AAMD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,yBAAyB,EAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAChC,MAAM,CAGR"}
@@ -0,0 +1,74 @@
1
+ // Issue #1388 (Epic #1491, ADR-0070, D6) — content-free container-run evidence. Each finished run
2
+ // writes a standard EvidenceManifest via the existing EvidenceStore.put port so the shared evidence
3
+ // list/detail APIs can parse it. It carries COUNTS and ENUMS ONLY — never the constructed docker/
4
+ // podman argv, never the raw image ref free-text (only the closed-catalog image id), never the
5
+ // workspace path, never any container output (ADR-0048 content-free invariant). Mirrors
6
+ // command-runner-evidence.ts.
7
+ import { deepRedactStrings } from "@oscharko-dev/keiko-evidence";
8
+ import { EVIDENCE_SCHEMA_VERSION } from "@oscharko-dev/keiko-evidence";
9
+ import { HARNESS_VERSION } from "@oscharko-dev/keiko-harness";
10
+ export const CONTAINER_RUN_EVIDENCE_KIND = "container-run";
11
+ function containerRunOutcome(input) {
12
+ switch (input.failureReason) {
13
+ case "none":
14
+ return "completed";
15
+ case "timed-out":
16
+ return "limit-exceeded";
17
+ case "cancelled":
18
+ return "cancelled";
19
+ default:
20
+ return "failed";
21
+ }
22
+ }
23
+ // PURE. Builds the on-disk manifest from a finished run. Identifiers/counts only; the docker argv,
24
+ // the raw image ref, the workspace path, and all output are excluded. The caller supplies
25
+ // runId/startedAt (no clock, no randomness here) so evidence stays deterministic.
26
+ export function buildContainerRunEvidenceEntry(input) {
27
+ const runId = input.runId;
28
+ return {
29
+ evidenceSchemaVersion: EVIDENCE_SCHEMA_VERSION,
30
+ run: {
31
+ runId,
32
+ fingerprint: runId,
33
+ harnessVersion: HARNESS_VERSION,
34
+ taskType: CONTAINER_RUN_EVIDENCE_KIND,
35
+ outcome: containerRunOutcome(input),
36
+ startedAt: input.startedAt,
37
+ finishedAt: input.startedAt + input.durationMs,
38
+ durationMs: input.durationMs,
39
+ },
40
+ model: { modelId: "container-runner", costClass: "unknown" },
41
+ usageTotals: { promptTokens: 0, completionTokens: 0, requestCount: 0, totalLatencyMs: 0 },
42
+ context: {
43
+ workspaceRoot: input.projectId,
44
+ totalCandidates: 0,
45
+ usedBytes: 0,
46
+ budgetBytes: 0,
47
+ droppedForBudget: 0,
48
+ entries: [],
49
+ },
50
+ stateTransitions: [],
51
+ toolCalls: [],
52
+ commandExecutions: [
53
+ {
54
+ seq: 1,
55
+ ts: input.startedAt,
56
+ // The container ENGINE is the executable; the catalog image id rides in argCount/identity,
57
+ // never as a free-text leaf. argCount is the docker argv length, never the argv tokens.
58
+ executable: input.engine,
59
+ argCount: input.argCount,
60
+ exitCode: input.exitCode,
61
+ timedOut: input.timedOut,
62
+ durationMs: input.durationMs,
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ // Defense in depth: applies the live redactor to every string leaf before serializing. All known
68
+ // leaves (engine, projectId, runId, taskId) are structurally safe today; a future schema addition
69
+ // inherits the redaction automatically. Mirrors appendCommandRunEvidence. Best-effort at the call
70
+ // site: a write hiccup must never corrupt a real run result.
71
+ export function appendContainerRunEvidence(store, entry, redact) {
72
+ const safe = deepRedactStrings(entry, redact);
73
+ return store.put(safe.run.runId, JSON.stringify(safe, null, 2));
74
+ }
@@ -0,0 +1,37 @@
1
+ import { type RunCommandDeps, type SandboxPolicy } from "@oscharko-dev/keiko-tools";
2
+ import { type ContainerCapabilityResponse, type ContainerExecutionPolicy, type ContainerResourceLimits, type ContainerRunResult, type ContainerRunnerEvent, type ContainerTask, type ContainerTaskCatalog } from "@oscharko-dev/keiko-contracts";
3
+ import type { EvidenceStore } from "@oscharko-dev/keiko-evidence";
4
+ import type { UiStore } from "../store/index.js";
5
+ export declare const DEFAULT_CONTAINER_RESOURCE_LIMITS: ContainerResourceLimits;
6
+ export declare const DEFAULT_CONTAINER_EXECUTION_POLICY: ContainerExecutionPolicy;
7
+ export declare const DEFAULT_CONTAINER_TASKS: readonly ContainerTask[];
8
+ export declare function buildContainerRunArgv(task: ContainerTask, policy: ContainerExecutionPolicy, workspaceRoot: string): readonly string[];
9
+ export interface ContainerRunInput {
10
+ readonly projectId: string;
11
+ readonly taskId: string;
12
+ readonly timeoutMs?: number | undefined;
13
+ readonly requestId?: string | undefined;
14
+ }
15
+ export type ContainerRunnerEventEmitter = (event: ContainerRunnerEvent) => void;
16
+ export interface ContainerRunnerManager {
17
+ readonly capability: (projectId: string) => Promise<ContainerCapabilityResponse>;
18
+ readonly listCatalog: (projectId: string) => Promise<ContainerTaskCatalog>;
19
+ readonly execute: (input: ContainerRunInput) => Promise<ContainerRunResult>;
20
+ readonly abort: (runId: string) => boolean;
21
+ readonly subscribe: (listener: ContainerRunnerEventEmitter) => () => void;
22
+ readonly inFlightCount: () => number;
23
+ }
24
+ export interface ContainerRunnerManagerOptions {
25
+ readonly store: UiStore;
26
+ readonly evidenceStore?: EvidenceStore | undefined;
27
+ readonly policy?: SandboxPolicy | undefined;
28
+ readonly executionPolicy?: ContainerExecutionPolicy | undefined;
29
+ readonly catalog?: readonly ContainerTask[] | undefined;
30
+ readonly processEnv?: NodeJS.ProcessEnv | undefined;
31
+ readonly redactor?: ((input: string) => string) | undefined;
32
+ readonly runDeps?: Partial<RunCommandDeps> | undefined;
33
+ readonly detect?: ((projectId: string) => Promise<ContainerCapabilityResponse>) | undefined;
34
+ readonly now?: (() => number) | undefined;
35
+ }
36
+ export declare function createContainerRunnerManager(opts: ContainerRunnerManagerOptions): ContainerRunnerManager;
37
+ //# sourceMappingURL=containerRunner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerRunner.d.ts","sourceRoot":"","sources":["../../src/runtime/containerRunner.ts"],"names":[],"mappings":"AAaA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAE7B,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAOlE,OAAO,KAAK,EAAW,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,eAAO,MAAM,iCAAiC,EAAE,uBAI/C,CAAC;AAQF,eAAO,MAAM,kCAAkC,EAAE,wBAQhD,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,SAAS,aAAa,EAc1D,CAAC;AAOH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,wBAAwB,EAChC,aAAa,EAAE,MAAM,GACpB,SAAS,MAAM,EAAE,CA6BnB;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,MAAM,2BAA2B,GAAG,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAEhF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACjF,QAAQ,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3E,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC5E,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,MAAM,IAAI,CAAC;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAEvD,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAAC,GAAG,SAAS,CAAC;IAC5F,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC3C;AAuaD,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,6BAA6B,GAClC,sBAAsB,CAExB"}