@bjoernrochel/sevro 1.0.0 → 1.1.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 (403) hide show
  1. package/CONTRIBUTING.md +34 -4
  2. package/README.md +7 -6
  3. package/dist/examples/basic/graded.json +13 -0
  4. package/dist/examples/basic/host.js +9 -0
  5. package/dist/examples/basic/host.js.map +1 -0
  6. package/dist/examples/basic/prompt-only.json +7 -0
  7. package/dist/examples/recipes/candidate.js +23 -0
  8. package/dist/examples/recipes/candidate.js.map +1 -0
  9. package/dist/examples/recipes/cases/coding-change.json +21 -0
  10. package/dist/examples/recipes/cases/host-tools.json +21 -0
  11. package/dist/examples/recipes/cases/mise.json +21 -0
  12. package/dist/examples/recipes/cases/node.json +21 -0
  13. package/dist/examples/recipes/cases/python-uv.json +21 -0
  14. package/dist/examples/recipes/cases/semantic.json +17 -0
  15. package/dist/examples/recipes/coding-change/check.js +16 -0
  16. package/dist/examples/recipes/coding-change/check.js.map +1 -0
  17. package/dist/examples/recipes/executable.js +20 -0
  18. package/dist/examples/recipes/executable.js.map +1 -0
  19. package/dist/examples/recipes/extension-artifacts.js +56 -0
  20. package/dist/examples/recipes/extension-artifacts.js.map +1 -0
  21. package/dist/examples/recipes/extension-methods.js +167 -0
  22. package/dist/examples/recipes/extension-methods.js.map +1 -0
  23. package/dist/examples/recipes/extension.js +28 -0
  24. package/dist/examples/recipes/extension.js.map +1 -0
  25. package/dist/examples/recipes/modules.js +9 -0
  26. package/dist/examples/recipes/modules.js.map +1 -0
  27. package/dist/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  28. package/dist/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  29. package/dist/examples/recipes/repository-skill/README.md +3 -0
  30. package/dist/examples/recipes/run-inputs.js +144 -0
  31. package/dist/examples/recipes/run-inputs.js.map +1 -0
  32. package/dist/examples/recipes/run-options.js +103 -0
  33. package/dist/examples/recipes/run-options.js.map +1 -0
  34. package/dist/examples/recipes/run.js +62 -0
  35. package/dist/examples/recipes/run.js.map +1 -0
  36. package/dist/examples/recipes/runtime/empty.json +1 -0
  37. package/dist/examples/recipes/runtime/goals.json +1 -0
  38. package/dist/examples/recipes/runtime/mise.json +6 -0
  39. package/dist/examples/recipes/runtime/path.json +1 -0
  40. package/dist/examples/recipes/runtime/plugins.json +4 -0
  41. package/dist/examples/recipes/runtime/python-uv.json +10 -0
  42. package/dist/examples/recipes/semantic-judge.js +27 -0
  43. package/dist/examples/recipes/semantic-judge.js.map +1 -0
  44. package/dist/examples/templates/coding/adapter.js +14 -0
  45. package/dist/examples/templates/coding/adapter.js.map +1 -0
  46. package/dist/examples/templates/coding/case.json +15 -0
  47. package/dist/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  48. package/dist/examples/templates/repository-skill/extension-data.js +61 -0
  49. package/dist/examples/templates/repository-skill/extension-data.js.map +1 -0
  50. package/dist/examples/templates/repository-skill/extension.js +29 -0
  51. package/dist/examples/templates/repository-skill/extension.js.map +1 -0
  52. package/dist/examples/templates/response/adapter.js +13 -0
  53. package/dist/examples/templates/response/adapter.js.map +1 -0
  54. package/dist/examples/templates/response/case.json +13 -0
  55. package/dist/src/advisory.js +86 -0
  56. package/dist/src/advisory.js.map +1 -0
  57. package/dist/src/candidate-artifacts.js +139 -0
  58. package/dist/src/candidate-artifacts.js.map +1 -0
  59. package/dist/src/candidate-codex-state.js +110 -0
  60. package/dist/src/candidate-codex-state.js.map +1 -0
  61. package/dist/src/case-validation.js +92 -0
  62. package/dist/src/case-validation.js.map +1 -0
  63. package/dist/src/cli/cancellation.js +21 -0
  64. package/dist/src/cli/cancellation.js.map +1 -0
  65. package/dist/src/cli/command.js +60 -0
  66. package/dist/src/cli/command.js.map +1 -0
  67. package/dist/src/cli/completion.js +14 -0
  68. package/dist/src/cli/completion.js.map +1 -0
  69. package/dist/src/cli/demo.js +45 -0
  70. package/dist/src/cli/demo.js.map +1 -0
  71. package/dist/src/cli/evaluation.js +40 -0
  72. package/dist/src/cli/evaluation.js.map +1 -0
  73. package/dist/src/cli/extension.js +54 -0
  74. package/dist/src/cli/extension.js.map +1 -0
  75. package/dist/src/cli/help.js +190 -0
  76. package/dist/src/cli/help.js.map +1 -0
  77. package/dist/src/cli/hosts.js +33 -0
  78. package/dist/src/cli/hosts.js.map +1 -0
  79. package/dist/src/cli/init-settings.js +29 -0
  80. package/dist/src/cli/init-settings.js.map +1 -0
  81. package/dist/src/cli/init.js +82 -0
  82. package/dist/src/cli/init.js.map +1 -0
  83. package/dist/src/cli/invocation.js +431 -0
  84. package/dist/src/cli/invocation.js.map +1 -0
  85. package/dist/src/cli/loading.js +110 -0
  86. package/dist/src/cli/loading.js.map +1 -0
  87. package/dist/src/cli/preparation.js +88 -0
  88. package/dist/src/cli/preparation.js.map +1 -0
  89. package/dist/src/cli/presentation.js +61 -0
  90. package/dist/src/cli/presentation.js.map +1 -0
  91. package/dist/src/cli/progress.js +5 -0
  92. package/dist/src/cli/progress.js.map +1 -0
  93. package/dist/src/cli/run-manifest.js +108 -0
  94. package/dist/src/cli/run-manifest.js.map +1 -0
  95. package/dist/src/cli/run-options.js +367 -0
  96. package/dist/src/cli/run-options.js.map +1 -0
  97. package/dist/src/cli/run-overrides.js +51 -0
  98. package/dist/src/cli/run-overrides.js.map +1 -0
  99. package/dist/src/cli/settings.js +127 -0
  100. package/dist/src/cli/settings.js.map +1 -0
  101. package/dist/src/cli/summary.js +28 -0
  102. package/dist/src/cli/summary.js.map +1 -0
  103. package/dist/src/cli.js +20 -0
  104. package/dist/src/cli.js.map +1 -0
  105. package/dist/src/contract-types.js +2 -0
  106. package/dist/src/contract-types.js.map +1 -0
  107. package/dist/src/engine.js +4 -0
  108. package/dist/src/engine.js.map +1 -0
  109. package/dist/src/evaluation/advisory.js +85 -0
  110. package/dist/src/evaluation/advisory.js.map +1 -0
  111. package/dist/src/evaluation/check-plan.js +292 -0
  112. package/dist/src/evaluation/check-plan.js.map +1 -0
  113. package/dist/src/evaluation/collection.js +61 -0
  114. package/dist/src/evaluation/collection.js.map +1 -0
  115. package/dist/src/evaluation/context.js +2 -0
  116. package/dist/src/evaluation/context.js.map +1 -0
  117. package/dist/src/evaluation/grading-host.js +28 -0
  118. package/dist/src/evaluation/grading-host.js.map +1 -0
  119. package/dist/src/evaluation/grading.js +154 -0
  120. package/dist/src/evaluation/grading.js.map +1 -0
  121. package/dist/src/evaluation/invocation-plan.js +312 -0
  122. package/dist/src/evaluation/invocation-plan.js.map +1 -0
  123. package/dist/src/evaluation/plan.js +376 -0
  124. package/dist/src/evaluation/plan.js.map +1 -0
  125. package/dist/src/evaluation/progress.js +14 -0
  126. package/dist/src/evaluation/progress.js.map +1 -0
  127. package/dist/src/evaluation/resources.js +159 -0
  128. package/dist/src/evaluation/resources.js.map +1 -0
  129. package/dist/src/evaluation/run.js +148 -0
  130. package/dist/src/evaluation/run.js.map +1 -0
  131. package/dist/src/evaluation/scheduler.js +27 -0
  132. package/dist/src/evaluation/scheduler.js.map +1 -0
  133. package/dist/src/evaluation/semantic.js +183 -0
  134. package/dist/src/evaluation/semantic.js.map +1 -0
  135. package/dist/src/evaluation/trial.js +593 -0
  136. package/dist/src/evaluation/trial.js.map +1 -0
  137. package/dist/src/evaluation/types.js +20 -0
  138. package/dist/src/evaluation/types.js.map +1 -0
  139. package/dist/src/evidence/host-results.js +67 -0
  140. package/dist/src/evidence/host-results.js.map +1 -0
  141. package/dist/src/evidence/retained-artifacts.js +17 -0
  142. package/dist/src/evidence/retained-artifacts.js.map +1 -0
  143. package/dist/src/evidence/trial-writer.js +131 -0
  144. package/dist/src/evidence/trial-writer.js.map +1 -0
  145. package/dist/src/evidence/usage.js +14 -0
  146. package/dist/src/evidence/usage.js.map +1 -0
  147. package/dist/src/executable.js +20 -0
  148. package/dist/src/executable.js.map +1 -0
  149. package/dist/src/extension-client.js +212 -0
  150. package/dist/src/extension-client.js.map +1 -0
  151. package/dist/src/extension-session.js +236 -0
  152. package/dist/src/extension-session.js.map +1 -0
  153. package/dist/src/fixtures/advisory.js +206 -0
  154. package/dist/src/fixtures/advisory.js.map +1 -0
  155. package/dist/src/fixtures/bin.js +41 -0
  156. package/dist/src/fixtures/bin.js.map +1 -0
  157. package/dist/src/fixtures/cleanup.js +43 -0
  158. package/dist/src/fixtures/cleanup.js.map +1 -0
  159. package/dist/src/fixtures/create.js +94 -0
  160. package/dist/src/fixtures/create.js.map +1 -0
  161. package/dist/src/fixtures/generated.js +181 -0
  162. package/dist/src/fixtures/generated.js.map +1 -0
  163. package/dist/src/fixtures/git-hooks.js +16 -0
  164. package/dist/src/fixtures/git-hooks.js.map +1 -0
  165. package/dist/src/fixtures/preparation.js +201 -0
  166. package/dist/src/fixtures/preparation.js.map +1 -0
  167. package/dist/src/fixtures/repository.js +255 -0
  168. package/dist/src/fixtures/repository.js.map +1 -0
  169. package/dist/src/fixtures/setup.js +175 -0
  170. package/dist/src/fixtures/setup.js.map +1 -0
  171. package/dist/src/fixtures/tools.js +57 -0
  172. package/dist/src/fixtures/tools.js.map +1 -0
  173. package/dist/src/fixtures/workspace-state.js +155 -0
  174. package/dist/src/fixtures/workspace-state.js.map +1 -0
  175. package/dist/src/generated/cli-result.cjs +2 -0
  176. package/dist/src/generated/extension.cjs +2 -0
  177. package/dist/src/generated/report.cjs +2 -0
  178. package/dist/src/generated/run-evidence.cjs +2 -0
  179. package/dist/src/generated/runtime.cjs +2 -0
  180. package/dist/src/git.js +72 -0
  181. package/dist/src/git.js.map +1 -0
  182. package/dist/src/graders/artifact.js +68 -0
  183. package/dist/src/graders/artifact.js.map +1 -0
  184. package/dist/src/graders/git-head.js +112 -0
  185. package/dist/src/graders/git-head.js.map +1 -0
  186. package/dist/src/graders/output.js +345 -0
  187. package/dist/src/graders/output.js.map +1 -0
  188. package/dist/src/graders/semantic.js +132 -0
  189. package/dist/src/graders/semantic.js.map +1 -0
  190. package/dist/src/graders/shell.js +321 -0
  191. package/dist/src/graders/shell.js.map +1 -0
  192. package/dist/src/host-diagnostic-redaction.js +52 -0
  193. package/dist/src/host-diagnostic-redaction.js.map +1 -0
  194. package/dist/src/host-diagnostics.js +248 -0
  195. package/dist/src/host-diagnostics.js.map +1 -0
  196. package/dist/src/hosts/claude-continuation.js +89 -0
  197. package/dist/src/hosts/claude-continuation.js.map +1 -0
  198. package/dist/src/hosts/claude-credential.js +121 -0
  199. package/dist/src/hosts/claude-credential.js.map +1 -0
  200. package/dist/src/hosts/claude-events.js +136 -0
  201. package/dist/src/hosts/claude-events.js.map +1 -0
  202. package/dist/src/hosts/claude-keychain.js +100 -0
  203. package/dist/src/hosts/claude-keychain.js.map +1 -0
  204. package/dist/src/hosts/claude-login-recovery.js +67 -0
  205. package/dist/src/hosts/claude-login-recovery.js.map +1 -0
  206. package/dist/src/hosts/claude-login-refresh.js +146 -0
  207. package/dist/src/hosts/claude-login-refresh.js.map +1 -0
  208. package/dist/src/hosts/claude-login-store.js +124 -0
  209. package/dist/src/hosts/claude-login-store.js.map +1 -0
  210. package/dist/src/hosts/claude-login.js +74 -0
  211. package/dist/src/hosts/claude-login.js.map +1 -0
  212. package/dist/src/hosts/claude-native-goal.js +138 -0
  213. package/dist/src/hosts/claude-native-goal.js.map +1 -0
  214. package/dist/src/hosts/claude-process.js +10 -0
  215. package/dist/src/hosts/claude-process.js.map +1 -0
  216. package/dist/src/hosts/claude-refresh-lock.js +76 -0
  217. package/dist/src/hosts/claude-refresh-lock.js.map +1 -0
  218. package/dist/src/hosts/claude-repository-invocation.js +229 -0
  219. package/dist/src/hosts/claude-repository-invocation.js.map +1 -0
  220. package/dist/src/hosts/claude-settings.js +156 -0
  221. package/dist/src/hosts/claude-settings.js.map +1 -0
  222. package/dist/src/hosts/claude-skill-activations.js +131 -0
  223. package/dist/src/hosts/claude-skill-activations.js.map +1 -0
  224. package/dist/src/hosts/claude-state.js +237 -0
  225. package/dist/src/hosts/claude-state.js.map +1 -0
  226. package/dist/src/hosts/claude-tool-calls.js +150 -0
  227. package/dist/src/hosts/claude-tool-calls.js.map +1 -0
  228. package/dist/src/hosts/claude.js +373 -0
  229. package/dist/src/hosts/claude.js.map +1 -0
  230. package/dist/src/hosts/codex-app-server.js +764 -0
  231. package/dist/src/hosts/codex-app-server.js.map +1 -0
  232. package/dist/src/hosts/codex-configuration.js +91 -0
  233. package/dist/src/hosts/codex-configuration.js.map +1 -0
  234. package/dist/src/hosts/codex-events.js +104 -0
  235. package/dist/src/hosts/codex-events.js.map +1 -0
  236. package/dist/src/hosts/codex-exec-controls.js +434 -0
  237. package/dist/src/hosts/codex-exec-controls.js.map +1 -0
  238. package/dist/src/hosts/codex-invocation.js +54 -0
  239. package/dist/src/hosts/codex-invocation.js.map +1 -0
  240. package/dist/src/hosts/codex-marketplace.js +185 -0
  241. package/dist/src/hosts/codex-marketplace.js.map +1 -0
  242. package/dist/src/hosts/codex-native-calls.js +516 -0
  243. package/dist/src/hosts/codex-native-calls.js.map +1 -0
  244. package/dist/src/hosts/codex-process.js +10 -0
  245. package/dist/src/hosts/codex-process.js.map +1 -0
  246. package/dist/src/hosts/codex-profile.js +206 -0
  247. package/dist/src/hosts/codex-profile.js.map +1 -0
  248. package/dist/src/hosts/codex-session-files.js +89 -0
  249. package/dist/src/hosts/codex-session-files.js.map +1 -0
  250. package/dist/src/hosts/codex-state.js +310 -0
  251. package/dist/src/hosts/codex-state.js.map +1 -0
  252. package/dist/src/hosts/codex.js +527 -0
  253. package/dist/src/hosts/codex.js.map +1 -0
  254. package/dist/src/hosts/isolation-common.js +43 -0
  255. package/dist/src/hosts/isolation-common.js.map +1 -0
  256. package/dist/src/hosts/isolation-roots.js +64 -0
  257. package/dist/src/hosts/isolation-roots.js.map +1 -0
  258. package/dist/src/hosts/isolation.js +12 -0
  259. package/dist/src/hosts/isolation.js.map +1 -0
  260. package/dist/src/hosts/linux-sandbox.js +127 -0
  261. package/dist/src/hosts/linux-sandbox.js.map +1 -0
  262. package/dist/src/hosts/mac-sandbox.js +137 -0
  263. package/dist/src/hosts/mac-sandbox.js.map +1 -0
  264. package/dist/src/hosts/native-controls.js +151 -0
  265. package/dist/src/hosts/native-controls.js.map +1 -0
  266. package/dist/src/hosts/native-request.js +33 -0
  267. package/dist/src/hosts/native-request.js.map +1 -0
  268. package/dist/src/hosts/process-lifecycle.js +167 -0
  269. package/dist/src/hosts/process-lifecycle.js.map +1 -0
  270. package/dist/src/hosts/protected-worktrees.js +81 -0
  271. package/dist/src/hosts/protected-worktrees.js.map +1 -0
  272. package/dist/src/hosts/skill-hook-catalog.js +100 -0
  273. package/dist/src/hosts/skill-hook-catalog.js.map +1 -0
  274. package/dist/src/hosts/skill-hook-handler.js +228 -0
  275. package/dist/src/hosts/skill-hook-handler.js.map +1 -0
  276. package/dist/src/hosts/skill-hooks.js +423 -0
  277. package/dist/src/hosts/skill-hooks.js.map +1 -0
  278. package/dist/src/hosts/workspace-fingerprint.js +72 -0
  279. package/dist/src/hosts/workspace-fingerprint.js.map +1 -0
  280. package/dist/src/identity.js +167 -0
  281. package/dist/src/identity.js.map +1 -0
  282. package/dist/src/instrumentation.js +83 -0
  283. package/dist/src/instrumentation.js.map +1 -0
  284. package/dist/src/native-transcript-state.js +85 -0
  285. package/dist/src/native-transcript-state.js.map +1 -0
  286. package/dist/src/native-transcript-view.js +65 -0
  287. package/dist/src/native-transcript-view.js.map +1 -0
  288. package/dist/src/native-transcripts.js +208 -0
  289. package/dist/src/native-transcripts.js.map +1 -0
  290. package/dist/src/paths.js +13 -0
  291. package/dist/src/paths.js.map +1 -0
  292. package/dist/src/process-lifecycle.js +101 -0
  293. package/dist/src/process-lifecycle.js.map +1 -0
  294. package/dist/src/provenance.js +263 -0
  295. package/dist/src/provenance.js.map +1 -0
  296. package/dist/src/prune.js +228 -0
  297. package/dist/src/prune.js.map +1 -0
  298. package/dist/src/report.js +276 -0
  299. package/dist/src/report.js.map +1 -0
  300. package/dist/src/resolved-case.js +29 -0
  301. package/dist/src/resolved-case.js.map +1 -0
  302. package/dist/src/results-lock.js +40 -0
  303. package/dist/src/results-lock.js.map +1 -0
  304. package/dist/src/results.js +127 -0
  305. package/dist/src/results.js.map +1 -0
  306. package/dist/src/run-owner.js +283 -0
  307. package/dist/src/run-owner.js.map +1 -0
  308. package/dist/src/runner-paths.js +11 -0
  309. package/dist/src/runner-paths.js.map +1 -0
  310. package/dist/src/runtime/config.js +323 -0
  311. package/dist/src/runtime/config.js.map +1 -0
  312. package/dist/src/runtime/discovery-apple.js +65 -0
  313. package/dist/src/runtime/discovery-apple.js.map +1 -0
  314. package/dist/src/runtime/discovery-providers.js +37 -0
  315. package/dist/src/runtime/discovery-providers.js.map +1 -0
  316. package/dist/src/runtime/discovery.js +40 -0
  317. package/dist/src/runtime/discovery.js.map +1 -0
  318. package/dist/src/runtime/hook-processes.js +81 -0
  319. package/dist/src/runtime/hook-processes.js.map +1 -0
  320. package/dist/src/runtime/hooks.js +304 -0
  321. package/dist/src/runtime/hooks.js.map +1 -0
  322. package/dist/src/runtime/paths.js +244 -0
  323. package/dist/src/runtime/paths.js.map +1 -0
  324. package/dist/src/runtime/query.js +76 -0
  325. package/dist/src/runtime/query.js.map +1 -0
  326. package/dist/src/runtime/seeds.js +98 -0
  327. package/dist/src/runtime/seeds.js.map +1 -0
  328. package/dist/src/runtime/shared-cache.js +81 -0
  329. package/dist/src/runtime/shared-cache.js.map +1 -0
  330. package/dist/src/runtime/state.js +106 -0
  331. package/dist/src/runtime/state.js.map +1 -0
  332. package/dist/src/runtime-process.js +148 -0
  333. package/dist/src/runtime-process.js.map +1 -0
  334. package/dist/src/schema-types.js +2 -0
  335. package/dist/src/schema-types.js.map +1 -0
  336. package/dist/src/schema.js +16 -0
  337. package/dist/src/schema.js.map +1 -0
  338. package/dist/src/shell.js +5 -0
  339. package/dist/src/shell.js.map +1 -0
  340. package/dist/src/sqlite-lock.js +18 -0
  341. package/dist/src/sqlite-lock.js.map +1 -0
  342. package/dist/src/storage.js +21 -0
  343. package/dist/src/storage.js.map +1 -0
  344. package/dist/src/transcript-retention.js +109 -0
  345. package/dist/src/transcript-retention.js.map +1 -0
  346. package/dist/src/value-guards.js +26 -0
  347. package/dist/src/value-guards.js.map +1 -0
  348. package/docs/architecture.md +5 -2
  349. package/docs/creating-cases.md +2 -3
  350. package/docs/development-cli.md +12 -7
  351. package/docs/documentation-quality.md +4 -3
  352. package/docs/evaluation-workflow.md +8 -8
  353. package/docs/getting-started.md +7 -6
  354. package/docs/guide-evaluation.md +1 -1
  355. package/docs/identity-v1.md +4 -1
  356. package/docs/installing.md +31 -13
  357. package/docs/licensing.md +2 -2
  358. package/docs/native-hosts.md +1 -1
  359. package/docs/recipes.md +41 -40
  360. package/docs/releases.md +20 -4
  361. package/docs/run-settings-v1.md +3 -3
  362. package/docs/specs/repository-guide.md +1 -1
  363. package/docs/troubleshooting.md +1 -1
  364. package/docs/typescript-quality.md +18 -1
  365. package/examples/recipes/executable.ts +19 -0
  366. package/examples/recipes/extension-artifacts.ts +1 -1
  367. package/examples/recipes/extension.ts +2 -1
  368. package/examples/recipes/modules.ts +9 -0
  369. package/examples/recipes/run-inputs.ts +14 -19
  370. package/examples/recipes/run-options.ts +9 -8
  371. package/examples/recipes/run.ts +21 -7
  372. package/examples/templates/repository-skill/extension-data.ts +1 -1
  373. package/examples/templates/repository-skill/extension.ts +2 -1
  374. package/package.json +14 -6
  375. package/src/cli/demo.ts +3 -2
  376. package/src/cli/help.ts +4 -3
  377. package/src/cli/init-settings.ts +5 -4
  378. package/src/cli/init.ts +9 -8
  379. package/src/cli/invocation.ts +2 -1
  380. package/src/cli/preparation.ts +2 -2
  381. package/src/cli.ts +1 -1
  382. package/src/executable.ts +19 -0
  383. package/src/extension-client.ts +6 -12
  384. package/src/fixtures/setup.ts +3 -2
  385. package/src/git.ts +2 -1
  386. package/src/graders/git-head.ts +3 -2
  387. package/src/graders/shell.ts +4 -3
  388. package/src/hosts/claude-keychain.ts +3 -2
  389. package/src/hosts/claude-login-refresh.ts +3 -2
  390. package/src/hosts/claude-refresh-lock.ts +2 -1
  391. package/src/hosts/isolation-roots.ts +3 -2
  392. package/src/hosts/process-lifecycle.ts +4 -6
  393. package/src/hosts/protected-worktrees.ts +2 -1
  394. package/src/hosts/skill-hook-handler.ts +1 -1
  395. package/src/hosts/skill-hooks.ts +10 -2
  396. package/src/provenance.ts +7 -6
  397. package/src/results-lock.ts +4 -4
  398. package/src/run-owner.ts +15 -9
  399. package/src/runner-paths.ts +21 -0
  400. package/src/runtime/hook-processes.ts +2 -1
  401. package/src/runtime/hooks.ts +9 -7
  402. package/src/runtime-process.ts +194 -0
  403. package/src/sqlite-lock.ts +25 -0
@@ -0,0 +1,175 @@
1
+ import { spawn } from "../runtime-process.js";
2
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { delimiter, isAbsolute, join } from "node:path";
5
+ import { isRecord } from "../value-guards.js";
6
+ import { requireCandidateRuntime } from "../candidate-artifacts.js";
7
+ import { ProcessDeadline, terminateOwnedProcessGroup, } from "../process-lifecycle.js";
8
+ const MAX_COMMAND_BYTES = 64 * 1024;
9
+ const MAX_ENV_BYTES = 16 * 1024;
10
+ const TIMEOUT_MS = 120_000;
11
+ const RESERVED_ENV = new Set([
12
+ "PATH",
13
+ "HOME",
14
+ "TMPDIR",
15
+ "GIT_CONFIG_GLOBAL",
16
+ "GIT_CONFIG_NOSYSTEM",
17
+ "GIT_TERMINAL_PROMPT",
18
+ ]);
19
+ function setupRecord(value) {
20
+ if (!isRecord(value))
21
+ throw new Error("invalid fixture setup");
22
+ return value;
23
+ }
24
+ function validArgument(value) {
25
+ return typeof value === "string" && value.length > 0 && !value.includes("\0");
26
+ }
27
+ function argumentList(value) {
28
+ return (Array.isArray(value) &&
29
+ value.length > 0 &&
30
+ value.length <= 16 &&
31
+ value.every(validArgument));
32
+ }
33
+ function setupCommand(value) {
34
+ if (!argumentList(value))
35
+ throw new Error("invalid fixture setup command");
36
+ const executable = value[0];
37
+ if (!absoluteExecutable(executable) ||
38
+ Buffer.byteLength(value.join("\0"), "utf8") > MAX_COMMAND_BYTES)
39
+ throw new Error("invalid fixture setup command");
40
+ return value;
41
+ }
42
+ function absoluteExecutable(value) {
43
+ return value !== undefined && isAbsolute(value);
44
+ }
45
+ function validEnvironmentEntry([name, value]) {
46
+ return (/^[A-Z][A-Z0-9_]*$/.test(name) &&
47
+ !RESERVED_ENV.has(name) &&
48
+ validEnvironmentValue(value));
49
+ }
50
+ function validEnvironmentValue(value) {
51
+ return (typeof value === "string" &&
52
+ !value.includes("\0") &&
53
+ !/\{\{(?!sevro\.(?:project|workspace)\}\})/.test(value));
54
+ }
55
+ function environmentRecord(value) {
56
+ if (!isRecord(value))
57
+ throw new Error("invalid fixture setup environment");
58
+ return value;
59
+ }
60
+ function setupEnvironment(value) {
61
+ const environment = environmentRecord(value);
62
+ const entries = Object.entries(environment);
63
+ if (entries.length > 32 ||
64
+ entries.some((entry) => !validEnvironmentEntry(entry)) ||
65
+ Buffer.byteLength(JSON.stringify(environment), "utf8") > MAX_ENV_BYTES)
66
+ throw new Error("invalid fixture setup environment");
67
+ return environment;
68
+ }
69
+ /** Validate a trusted extension's bounded fixture setup command. */
70
+ export function prepareFixtureSetup(value) {
71
+ if (value === undefined)
72
+ return null;
73
+ const input = setupRecord(value);
74
+ if (Object.keys(input).some((key) => key !== "command" && key !== "environment"))
75
+ throw new Error("unsupported fixture setup field");
76
+ const command = setupCommand(input.command);
77
+ const environment = setupEnvironment(input.environment ?? {});
78
+ return { command, environment };
79
+ }
80
+ function setupPath(directory) {
81
+ const inherited = process.env.PATH ?? "/usr/bin:/bin";
82
+ return directory ? `${directory}${delimiter}${inherited}` : inherited;
83
+ }
84
+ function executionEnvironment(setup, options, home) {
85
+ const environment = Object.fromEntries(Object.entries(setup.environment).map(([name, value]) => [
86
+ name,
87
+ value
88
+ .replaceAll("{{sevro.project}}", options.projectRoot)
89
+ .replaceAll("{{sevro.workspace}}", options.workspace),
90
+ ]));
91
+ return {
92
+ PATH: setupPath(options.fixtureBinDir),
93
+ HOME: options.candidateRuntimeRoot
94
+ ? join(options.candidateRuntimeRoot, "home")
95
+ : home,
96
+ TMPDIR: home,
97
+ LANG: process.env.LANG ?? "C",
98
+ GIT_CONFIG_GLOBAL: process.platform === "win32" ? "NUL" : "/dev/null",
99
+ GIT_CONFIG_NOSYSTEM: "1",
100
+ GIT_TERMINAL_PROMPT: "0",
101
+ GIT_AUTHOR_NAME: "Sevro Fixture",
102
+ GIT_AUTHOR_EMAIL: "fixture@sevro.invalid",
103
+ GIT_COMMITTER_NAME: "Sevro Fixture",
104
+ GIT_COMMITTER_EMAIL: "fixture@sevro.invalid",
105
+ GIT_AUTHOR_DATE: "2000-01-01T00:00:00+00:00",
106
+ GIT_COMMITTER_DATE: "2000-01-01T00:00:00+00:00",
107
+ ...environment,
108
+ };
109
+ }
110
+ class FixtureSetupExecution {
111
+ setup;
112
+ options;
113
+ home;
114
+ proc;
115
+ deadline;
116
+ constructor(setup, options, home) {
117
+ this.setup = setup;
118
+ this.options = options;
119
+ this.home = home;
120
+ this.deadline = new ProcessDeadline({
121
+ timeoutMs: TIMEOUT_MS,
122
+ signal: options.signal,
123
+ interruption: (reason) => new Error(reason === "timeout"
124
+ ? "fixture setup timed out"
125
+ : "fixture setup cancelled"),
126
+ });
127
+ }
128
+ async run() {
129
+ this.proc = spawn(this.setup.command, {
130
+ cwd: this.options.workspace,
131
+ env: executionEnvironment(this.setup, this.options, this.home),
132
+ detached: process.platform !== "win32",
133
+ stdin: "ignore",
134
+ stdout: "ignore",
135
+ stderr: "ignore",
136
+ });
137
+ const code = await this.deadline.wait(this.proc.exited);
138
+ if (code !== 0)
139
+ throw new Error(`fixture setup failed (${code})`);
140
+ }
141
+ async stop() {
142
+ if (!this.proc)
143
+ return;
144
+ terminateOwnedProcessGroup(this.proc);
145
+ await this.proc.exited;
146
+ }
147
+ async dispose() {
148
+ this.deadline.dispose();
149
+ await rm(this.home, { recursive: true, force: true });
150
+ }
151
+ }
152
+ /** Run setup after fixture history and before evaluator artifacts are mounted. */
153
+ export async function runFixtureSetup(setup, options) {
154
+ if (options.signal?.aborted)
155
+ throw new Error("fixture setup cancelled");
156
+ await requireCandidateRuntime(options.candidateRuntimeRoot);
157
+ if (options.candidateRuntimeRoot)
158
+ await mkdir(join(options.candidateRuntimeRoot, "home"), {
159
+ recursive: true,
160
+ mode: 0o700,
161
+ });
162
+ const home = await mkdtemp(join(tmpdir(), "sevro-setup-"));
163
+ const execution = new FixtureSetupExecution(setup, options, home);
164
+ try {
165
+ await execution.run();
166
+ }
167
+ catch (error) {
168
+ await execution.stop();
169
+ throw error;
170
+ }
171
+ finally {
172
+ await execution.dispose();
173
+ }
174
+ }
175
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["setup.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, type Subprocess } from \"../runtime-process\";\nimport { mkdir, mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { delimiter, isAbsolute, join } from \"node:path\";\nimport { isRecord } from \"../value-guards\";\nimport { requireCandidateRuntime } from \"../candidate-artifacts\";\nimport {\n ProcessDeadline,\n terminateOwnedProcessGroup,\n} from \"../process-lifecycle\";\n\nconst MAX_COMMAND_BYTES = 64 * 1024;\nconst MAX_ENV_BYTES = 16 * 1024;\nconst TIMEOUT_MS = 120_000;\nconst RESERVED_ENV = new Set([\n \"PATH\",\n \"HOME\",\n \"TMPDIR\",\n \"GIT_CONFIG_GLOBAL\",\n \"GIT_CONFIG_NOSYSTEM\",\n \"GIT_TERMINAL_PROMPT\",\n]);\n\nexport interface FixtureSetupDeclaration {\n command: string[];\n environment?: Record<string, string>;\n}\n\nexport interface FixtureSetup extends FixtureSetupDeclaration {\n environment: Record<string, string>;\n}\n\nfunction setupRecord(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new Error(\"invalid fixture setup\");\n return value;\n}\n\nfunction validArgument(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0 && !value.includes(\"\\0\");\n}\n\nfunction argumentList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.length <= 16 &&\n value.every(validArgument)\n );\n}\n\nfunction setupCommand(value: unknown): string[] {\n if (!argumentList(value)) throw new Error(\"invalid fixture setup command\");\n const executable = value[0];\n if (\n !absoluteExecutable(executable) ||\n Buffer.byteLength(value.join(\"\\0\"), \"utf8\") > MAX_COMMAND_BYTES\n )\n throw new Error(\"invalid fixture setup command\");\n return value;\n}\n\nfunction absoluteExecutable(value: string | undefined): boolean {\n return value !== undefined && isAbsolute(value);\n}\n\nfunction validEnvironmentEntry([name, value]: [string, unknown]): boolean {\n return (\n /^[A-Z][A-Z0-9_]*$/.test(name) &&\n !RESERVED_ENV.has(name) &&\n validEnvironmentValue(value)\n );\n}\n\nfunction validEnvironmentValue(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n !value.includes(\"\\0\") &&\n !/\\{\\{(?!sevro\\.(?:project|workspace)\\}\\})/.test(value)\n );\n}\n\nfunction environmentRecord(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new Error(\"invalid fixture setup environment\");\n return value;\n}\n\nfunction setupEnvironment(value: unknown): Record<string, string> {\n const environment = environmentRecord(value);\n const entries = Object.entries(environment);\n if (\n entries.length > 32 ||\n entries.some((entry) => !validEnvironmentEntry(entry)) ||\n Buffer.byteLength(JSON.stringify(environment), \"utf8\") > MAX_ENV_BYTES\n )\n throw new Error(\"invalid fixture setup environment\");\n return environment as Record<string, string>;\n}\n\n/** Validate a trusted extension's bounded fixture setup command. */\nexport function prepareFixtureSetup(value: unknown): FixtureSetup | null {\n if (value === undefined) return null;\n const input = setupRecord(value);\n if (\n Object.keys(input).some((key) => key !== \"command\" && key !== \"environment\")\n )\n throw new Error(\"unsupported fixture setup field\");\n const command = setupCommand(input.command);\n const environment = setupEnvironment(input.environment ?? {});\n return { command, environment };\n}\n\ninterface FixtureSetupOptions {\n workspace: string;\n projectRoot: string;\n fixtureBinDir?: string;\n signal?: AbortSignal;\n candidateRuntimeRoot?: string;\n}\n\nfunction setupPath(directory: string | undefined): string {\n const inherited = process.env.PATH ?? \"/usr/bin:/bin\";\n return directory ? `${directory}${delimiter}${inherited}` : inherited;\n}\n\nfunction executionEnvironment(\n setup: FixtureSetup,\n options: FixtureSetupOptions,\n home: string,\n) {\n const environment = Object.fromEntries(\n Object.entries(setup.environment).map(([name, value]) => [\n name,\n value\n .replaceAll(\"{{sevro.project}}\", options.projectRoot)\n .replaceAll(\"{{sevro.workspace}}\", options.workspace),\n ]),\n );\n return {\n PATH: setupPath(options.fixtureBinDir),\n HOME: options.candidateRuntimeRoot\n ? join(options.candidateRuntimeRoot, \"home\")\n : home,\n TMPDIR: home,\n LANG: process.env.LANG ?? \"C\",\n GIT_CONFIG_GLOBAL: process.platform === \"win32\" ? \"NUL\" : \"/dev/null\",\n GIT_CONFIG_NOSYSTEM: \"1\",\n GIT_TERMINAL_PROMPT: \"0\",\n GIT_AUTHOR_NAME: \"Sevro Fixture\",\n GIT_AUTHOR_EMAIL: \"fixture@sevro.invalid\",\n GIT_COMMITTER_NAME: \"Sevro Fixture\",\n GIT_COMMITTER_EMAIL: \"fixture@sevro.invalid\",\n GIT_AUTHOR_DATE: \"2000-01-01T00:00:00+00:00\",\n GIT_COMMITTER_DATE: \"2000-01-01T00:00:00+00:00\",\n ...environment,\n };\n}\n\nclass FixtureSetupExecution {\n private proc: Subprocess | undefined;\n private readonly deadline: ProcessDeadline;\n constructor(\n private readonly setup: FixtureSetup,\n private readonly options: FixtureSetupOptions,\n private readonly home: string,\n ) {\n this.deadline = new ProcessDeadline({\n timeoutMs: TIMEOUT_MS,\n signal: options.signal,\n interruption: (reason) =>\n new Error(\n reason === \"timeout\"\n ? \"fixture setup timed out\"\n : \"fixture setup cancelled\",\n ),\n });\n }\n\n async run(): Promise<void> {\n this.proc = spawn(this.setup.command, {\n cwd: this.options.workspace,\n env: executionEnvironment(this.setup, this.options, this.home),\n detached: process.platform !== \"win32\",\n stdin: \"ignore\",\n stdout: \"ignore\",\n stderr: \"ignore\",\n });\n const code = await this.deadline.wait(this.proc.exited);\n if (code !== 0) throw new Error(`fixture setup failed (${code})`);\n }\n\n async stop(): Promise<void> {\n if (!this.proc) return;\n terminateOwnedProcessGroup(this.proc);\n await this.proc.exited;\n }\n\n async dispose(): Promise<void> {\n this.deadline.dispose();\n await rm(this.home, { recursive: true, force: true });\n }\n}\n\n/** Run setup after fixture history and before evaluator artifacts are mounted. */\nexport async function runFixtureSetup(\n setup: FixtureSetup,\n options: FixtureSetupOptions,\n): Promise<void> {\n if (options.signal?.aborted) throw new Error(\"fixture setup cancelled\");\n await requireCandidateRuntime(options.candidateRuntimeRoot);\n if (options.candidateRuntimeRoot)\n await mkdir(join(options.candidateRuntimeRoot, \"home\"), {\n recursive: true,\n mode: 0o700,\n });\n const home = await mkdtemp(join(tmpdir(), \"sevro-setup-\"));\n const execution = new FixtureSetupExecution(setup, options, home);\n try {\n await execution.run();\n } catch (error) {\n await execution.stop();\n throw error;\n } finally {\n await execution.dispose();\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { chmod, lstat, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { isRecord } from "../value-guards.js";
4
+ export function isMissingFile(error) {
5
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
6
+ }
7
+ function validContent(content) {
8
+ return (typeof content === "string" && content.length > 0 && !content.includes("\0"));
9
+ }
10
+ function validEntry([name, content], pattern) {
11
+ return pattern.test(name) && name.length <= 64 && validContent(content);
12
+ }
13
+ function declarationBytes(entries) {
14
+ return entries.reduce((size, [name, content]) => size +
15
+ Buffer.byteLength(name, "utf8") +
16
+ Buffer.byteLength(content, "utf8"), 0);
17
+ }
18
+ /** Shared bounds for trusted Git hook and fixture executable declarations. */
19
+ export function prepareFixtureTools(value, pattern, maxTools, message) {
20
+ if (value === undefined)
21
+ return null;
22
+ if (!isRecord(value))
23
+ throw new Error(message);
24
+ const entries = Object.entries(value);
25
+ validateToolBounds(entries, pattern, maxTools, message);
26
+ return value;
27
+ }
28
+ function validateToolBounds(entries, pattern, maxTools, message) {
29
+ if (entries.length > maxTools ||
30
+ entries.some((entry) => !validEntry(entry, pattern)) ||
31
+ declarationBytes(entries) > 1024 * 1024)
32
+ throw new Error(message);
33
+ }
34
+ async function validateToolTarget(path, message) {
35
+ try {
36
+ const existing = await lstat(path);
37
+ if (!existing.isFile() || existing.isSymbolicLink())
38
+ throw new Error(message);
39
+ }
40
+ catch (error) {
41
+ if (!isMissingFile(error))
42
+ throw error;
43
+ }
44
+ }
45
+ /** Preserve target-type checks and executable modes for both declaration kinds. */
46
+ export async function installFixtureTools(tools, directory, messages) {
47
+ const root = await lstat(directory);
48
+ if (!root.isDirectory() || root.isSymbolicLink())
49
+ throw new Error(messages.directory);
50
+ for (const [name, content] of Object.entries(tools)) {
51
+ const target = join(directory, name);
52
+ await validateToolTarget(target, messages.target);
53
+ await writeFile(target, content, { mode: 0o755 });
54
+ await chmod(target, 0o755);
55
+ }
56
+ }
57
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,yBAAyB;AAC3D,OAAO,EAAE,IAAI,EAAE,kBAAkB;AACjC,OAAO,EAAE,QAAQ,EAAE,2BAAwB;AAE3C,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC9E,CAAC;AAED,SAAS,YAAY,CAAC,OAAgB;IACpC,OAAO,CACL,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC7E,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CACjB,CAAC,IAAI,EAAE,OAAO,CAAoB,EAClC,OAAe;IAEf,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,gBAAgB,CAAC,OAA4B;IACpD,OAAO,OAAO,CAAC,MAAM,CACnB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CACxB,IAAI;QACJ,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;QAC/B,MAAM,CAAC,UAAU,CAAC,OAAiB,EAAE,MAAM,CAAC,EAC9C,CAAC,CACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CACjC,KAAc,EACd,OAAe,EACf,QAAgB,EAChB,OAAe;IAEf,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxD,OAAO,KAA+B,CAAC;AACzC,CAAC;AAED,SAAS,kBAAkB,CACzB,OAA4B,EAC5B,OAAe,EACf,QAAgB,EAChB,OAAe;IAEf,IACE,OAAO,CAAC,MAAM,GAAG,QAAQ;QACzB,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACpD,gBAAgB,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI;QAEvC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,IAAY,EACZ,OAAe;IAEf,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,QAAQ,CAAC,cAAc,EAAE;YACjD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IACzC,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAA6B,EAC7B,SAAiB,EACjB,QAA+C;IAE/C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,cAAc,EAAE;QAC9C,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC","sourcesContent":["import { chmod, lstat, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { isRecord } from \"../value-guards\";\n\nexport function isMissingFile(error: unknown): boolean {\n return error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\nfunction validContent(content: unknown): content is string {\n return (\n typeof content === \"string\" && content.length > 0 && !content.includes(\"\\0\")\n );\n}\n\nfunction validEntry(\n [name, content]: [string, unknown],\n pattern: RegExp,\n): boolean {\n return pattern.test(name) && name.length <= 64 && validContent(content);\n}\n\nfunction declarationBytes(entries: [string, unknown][]): number {\n return entries.reduce(\n (size, [name, content]) =>\n size +\n Buffer.byteLength(name, \"utf8\") +\n Buffer.byteLength(content as string, \"utf8\"),\n 0,\n );\n}\n\n/** Shared bounds for trusted Git hook and fixture executable declarations. */\nexport function prepareFixtureTools(\n value: unknown,\n pattern: RegExp,\n maxTools: number,\n message: string,\n): Record<string, string> | null {\n if (value === undefined) return null;\n if (!isRecord(value)) throw new Error(message);\n const entries = Object.entries(value);\n validateToolBounds(entries, pattern, maxTools, message);\n return value as Record<string, string>;\n}\n\nfunction validateToolBounds(\n entries: [string, unknown][],\n pattern: RegExp,\n maxTools: number,\n message: string,\n): void {\n if (\n entries.length > maxTools ||\n entries.some((entry) => !validEntry(entry, pattern)) ||\n declarationBytes(entries) > 1024 * 1024\n )\n throw new Error(message);\n}\n\nasync function validateToolTarget(\n path: string,\n message: string,\n): Promise<void> {\n try {\n const existing = await lstat(path);\n if (!existing.isFile() || existing.isSymbolicLink())\n throw new Error(message);\n } catch (error) {\n if (!isMissingFile(error)) throw error;\n }\n}\n\n/** Preserve target-type checks and executable modes for both declaration kinds. */\nexport async function installFixtureTools(\n tools: Record<string, string>,\n directory: string,\n messages: { directory: string; target: string },\n): Promise<void> {\n const root = await lstat(directory);\n if (!root.isDirectory() || root.isSymbolicLink())\n throw new Error(messages.directory);\n for (const [name, content] of Object.entries(tools)) {\n const target = join(directory, name);\n await validateToolTarget(target, messages.target);\n await writeFile(target, content, { mode: 0o755 });\n await chmod(target, 0o755);\n }\n}\n"]}
@@ -0,0 +1,155 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { lstat, mkdir, mkdtemp, realpath, readFile, readdir, rm, writeFile, } from "node:fs/promises";
3
+ import { tmpdir, userInfo } from "node:os";
4
+ import { basename, dirname, join } from "node:path";
5
+ import { ownerStatus, processIdentity, validProcessIdentity, } from "../run-owner.js";
6
+ import { isRecord } from "../value-guards.js";
7
+ import { isMissingFile } from "./tools.js";
8
+ import { atomicWriteJson } from "../storage.js";
9
+ import { clearFixtureContents } from "./cleanup.js";
10
+ const RETENTION_MS = 24 * 60 * 60_000;
11
+ /** One stable boundary protects both existing and subsequently allocated peers. */
12
+ export function workspaceStatePath() {
13
+ const base = process.platform === "linux" ? "/var/tmp" : tmpdir();
14
+ return join(realpathSync(base), "sevro-workspaces-private");
15
+ }
16
+ export async function workspaceStateParent() {
17
+ const root = workspaceStatePath();
18
+ await mkdir(root, { recursive: true, mode: 0o700 });
19
+ const info = await lstat(root);
20
+ if (!info.isDirectory() ||
21
+ info.uid !== userInfo().uid ||
22
+ (info.mode & 0o077) !== 0)
23
+ throw new Error("workspace namespace must be an owned private directory");
24
+ return realpath(root);
25
+ }
26
+ export async function allocateWorkspace() {
27
+ const owner = processIdentity(process.pid);
28
+ if (!owner)
29
+ throw new Error("cannot verify workspace owner identity");
30
+ const parent = await workspaceStateParent();
31
+ await workspaceMetadataDirectory(parent);
32
+ const workspace = await realpath(await mkdtemp(join(parent, "case-")));
33
+ try {
34
+ await writeFile(workspaceOwnerPath(workspace), JSON.stringify({
35
+ format: "sevro.workspace-owner.v1",
36
+ owner,
37
+ state: "active",
38
+ timestamp: Date.now(),
39
+ }), { mode: 0o600, flag: "wx" });
40
+ return workspace;
41
+ }
42
+ catch (cause) {
43
+ await rm(workspace, { recursive: true, force: true });
44
+ throw cause;
45
+ }
46
+ }
47
+ async function workspaceMetadataDirectory(parent) {
48
+ const root = join(parent, ".owners");
49
+ await mkdir(root, { mode: 0o700, recursive: true });
50
+ const metadata = await lstat(root);
51
+ if (!ownedDirectory(metadata) || (metadata.mode & 0o077) !== 0)
52
+ throw new Error("workspace metadata must be an owned private directory");
53
+ return root;
54
+ }
55
+ function workspaceOwnerPath(workspace) {
56
+ return join(dirname(workspace), ".owners", `${basename(workspace)}.json`);
57
+ }
58
+ function validWorkspaceLifecycle(value) {
59
+ return ((value.state === "active" || value.state === "retained") &&
60
+ typeof value.timestamp === "number" &&
61
+ Number.isFinite(value.timestamp));
62
+ }
63
+ function validWorkspaceOwner(value) {
64
+ return (isRecord(value) &&
65
+ value.format === "sevro.workspace-owner.v1" &&
66
+ validWorkspaceLifecycle(value) &&
67
+ validProcessIdentity(value.owner));
68
+ }
69
+ function privateOwnerFile(info) {
70
+ return (info.isFile() &&
71
+ info.uid === userInfo().uid &&
72
+ info.size <= 4096 &&
73
+ (info.mode & 0o077) === 0);
74
+ }
75
+ async function readWorkspaceOwner(workspace) {
76
+ const path = workspaceOwnerPath(workspace);
77
+ if (!privateOwnerFile(await lstat(path)))
78
+ return null;
79
+ return JSON.parse(await readFile(path, "utf8"));
80
+ }
81
+ async function workspaceOwner(workspace) {
82
+ try {
83
+ const value = await readWorkspaceOwner(workspace);
84
+ return validWorkspaceOwner(value) ? value : null;
85
+ }
86
+ catch {
87
+ // Unreadable or malformed ownership cannot authorize temporary-data deletion.
88
+ return null;
89
+ }
90
+ }
91
+ export async function retainWorkspace(workspace) {
92
+ if (!workspaceBoundaryException(workspaceStatePath(), workspace).length)
93
+ return;
94
+ const record = await workspaceOwner(workspace);
95
+ if (!record || record.state === "retained")
96
+ return;
97
+ await atomicWriteJson(workspaceOwnerPath(workspace), {
98
+ ...record,
99
+ state: "retained",
100
+ timestamp: Date.now(),
101
+ });
102
+ }
103
+ export async function removeWorkspace(workspace) {
104
+ await rm(workspace, { recursive: true, force: true });
105
+ if (workspaceBoundaryException(workspaceStatePath(), workspace).length)
106
+ await rm(workspaceOwnerPath(workspace), { force: true });
107
+ }
108
+ async function expireWorkspace(workspace, cutoff) {
109
+ const record = await workspaceOwner(workspace);
110
+ if (!record || !expiredOwner(record, cutoff))
111
+ return;
112
+ const info = await lstat(workspace).catch((cause) => {
113
+ if (isMissingFile(cause))
114
+ return null;
115
+ throw cause;
116
+ });
117
+ if (info && !ownedDirectory(info))
118
+ return;
119
+ await clearExpiredWorkspace(workspace, info);
120
+ }
121
+ async function clearExpiredWorkspace(workspace, info) {
122
+ if (info)
123
+ await clearFixtureContents(workspace);
124
+ await removeWorkspace(workspace);
125
+ }
126
+ function ownedDirectory(info) {
127
+ return info.isDirectory() && info.uid === userInfo().uid;
128
+ }
129
+ function expiredOwner(record, cutoff) {
130
+ return (record.timestamp < cutoff &&
131
+ (record.state === "retained" || ownerStatus(record.owner) === "dead"));
132
+ }
133
+ /** Reclaim only owned inactive temporary data; evidence directories are never scanned. */
134
+ export async function cleanupExpiredWorkspaces() {
135
+ const parent = await workspaceStateParent();
136
+ const metadata = await workspaceMetadataDirectory(parent);
137
+ const cutoff = Date.now() - RETENTION_MS;
138
+ for (const entry of await readdir(metadata, { withFileTypes: true }))
139
+ if (entry.isFile() && /^case-[a-zA-Z0-9]+\.json$/.test(entry.name)) {
140
+ const workspace = join(parent, entry.name.slice(0, -5));
141
+ await expireWorkspace(workspace, cutoff).catch((cause) => {
142
+ if (!isMissingFile(cause))
143
+ console.warn(`temporary workspace cleanup did not complete: ${workspace}`);
144
+ });
145
+ }
146
+ }
147
+ /** Only a runner-owned direct child can be an exception to this deny boundary. */
148
+ export function workspaceBoundaryException(root, workspace) {
149
+ return root === workspaceStatePath() &&
150
+ workspace !== undefined &&
151
+ dirname(workspace) === root
152
+ ? [workspace]
153
+ : [];
154
+ }
155
+ //# sourceMappingURL=workspace-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-state.js","sourceRoot":"","sources":["workspace-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpathSync } from \"node:fs\";\nimport type { Stats } from \"node:fs\";\nimport {\n lstat,\n mkdir,\n mkdtemp,\n realpath,\n readFile,\n readdir,\n rm,\n writeFile,\n} from \"node:fs/promises\";\nimport { tmpdir, userInfo } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport {\n ownerStatus,\n processIdentity,\n validProcessIdentity,\n type ProcessIdentity,\n} from \"../run-owner\";\nimport { isRecord } from \"../value-guards\";\nimport { isMissingFile } from \"./tools\";\nimport { atomicWriteJson } from \"../storage\";\nimport { clearFixtureContents } from \"./cleanup\";\n\nconst RETENTION_MS = 24 * 60 * 60_000;\ninterface WorkspaceOwner {\n format: \"sevro.workspace-owner.v1\";\n owner: ProcessIdentity;\n state: \"active\" | \"retained\";\n timestamp: number;\n}\n\n/** One stable boundary protects both existing and subsequently allocated peers. */\nexport function workspaceStatePath(): string {\n const base = process.platform === \"linux\" ? \"/var/tmp\" : tmpdir();\n return join(realpathSync(base), \"sevro-workspaces-private\");\n}\n\nexport async function workspaceStateParent(): Promise<string> {\n const root = workspaceStatePath();\n await mkdir(root, { recursive: true, mode: 0o700 });\n const info = await lstat(root);\n if (\n !info.isDirectory() ||\n info.uid !== userInfo().uid ||\n (info.mode & 0o077) !== 0\n )\n throw new Error(\"workspace namespace must be an owned private directory\");\n return realpath(root);\n}\n\nexport async function allocateWorkspace(): Promise<string> {\n const owner = processIdentity(process.pid);\n if (!owner) throw new Error(\"cannot verify workspace owner identity\");\n const parent = await workspaceStateParent();\n await workspaceMetadataDirectory(parent);\n const workspace = await realpath(await mkdtemp(join(parent, \"case-\")));\n try {\n await writeFile(\n workspaceOwnerPath(workspace),\n JSON.stringify({\n format: \"sevro.workspace-owner.v1\",\n owner,\n state: \"active\",\n timestamp: Date.now(),\n }),\n { mode: 0o600, flag: \"wx\" },\n );\n return workspace;\n } catch (cause) {\n await rm(workspace, { recursive: true, force: true });\n throw cause;\n }\n}\n\nasync function workspaceMetadataDirectory(parent: string): Promise<string> {\n const root = join(parent, \".owners\");\n await mkdir(root, { mode: 0o700, recursive: true });\n const metadata = await lstat(root);\n if (!ownedDirectory(metadata) || (metadata.mode & 0o077) !== 0)\n throw new Error(\"workspace metadata must be an owned private directory\");\n return root;\n}\n\nfunction workspaceOwnerPath(workspace: string): string {\n return join(dirname(workspace), \".owners\", `${basename(workspace)}.json`);\n}\n\nfunction validWorkspaceLifecycle(value: Record<string, unknown>): boolean {\n return (\n (value.state === \"active\" || value.state === \"retained\") &&\n typeof value.timestamp === \"number\" &&\n Number.isFinite(value.timestamp)\n );\n}\n\nfunction validWorkspaceOwner(value: unknown): value is WorkspaceOwner {\n return (\n isRecord(value) &&\n value.format === \"sevro.workspace-owner.v1\" &&\n validWorkspaceLifecycle(value) &&\n validProcessIdentity(value.owner)\n );\n}\n\nfunction privateOwnerFile(info: Stats): boolean {\n return (\n info.isFile() &&\n info.uid === userInfo().uid &&\n info.size <= 4096 &&\n (info.mode & 0o077) === 0\n );\n}\n\nasync function readWorkspaceOwner(workspace: string): Promise<unknown> {\n const path = workspaceOwnerPath(workspace);\n if (!privateOwnerFile(await lstat(path))) return null;\n return JSON.parse(await readFile(path, \"utf8\")) as unknown;\n}\n\nasync function workspaceOwner(\n workspace: string,\n): Promise<WorkspaceOwner | null> {\n try {\n const value = await readWorkspaceOwner(workspace);\n return validWorkspaceOwner(value) ? value : null;\n } catch {\n // Unreadable or malformed ownership cannot authorize temporary-data deletion.\n return null;\n }\n}\n\nexport async function retainWorkspace(workspace: string): Promise<void> {\n if (!workspaceBoundaryException(workspaceStatePath(), workspace).length)\n return;\n const record = await workspaceOwner(workspace);\n if (!record || record.state === \"retained\") return;\n await atomicWriteJson(workspaceOwnerPath(workspace), {\n ...record,\n state: \"retained\",\n timestamp: Date.now(),\n });\n}\n\nexport async function removeWorkspace(workspace: string): Promise<void> {\n await rm(workspace, { recursive: true, force: true });\n if (workspaceBoundaryException(workspaceStatePath(), workspace).length)\n await rm(workspaceOwnerPath(workspace), { force: true });\n}\n\nasync function expireWorkspace(\n workspace: string,\n cutoff: number,\n): Promise<void> {\n const record = await workspaceOwner(workspace);\n if (!record || !expiredOwner(record, cutoff)) return;\n const info = await lstat(workspace).catch((cause: unknown) => {\n if (isMissingFile(cause)) return null;\n throw cause;\n });\n if (info && !ownedDirectory(info)) return;\n await clearExpiredWorkspace(workspace, info);\n}\n\nasync function clearExpiredWorkspace(\n workspace: string,\n info: Stats | null,\n): Promise<void> {\n if (info) await clearFixtureContents(workspace);\n await removeWorkspace(workspace);\n}\n\nfunction ownedDirectory(info: Stats): boolean {\n return info.isDirectory() && info.uid === userInfo().uid;\n}\n\nfunction expiredOwner(record: WorkspaceOwner, cutoff: number): boolean {\n return (\n record.timestamp < cutoff &&\n (record.state === \"retained\" || ownerStatus(record.owner) === \"dead\")\n );\n}\n\n/** Reclaim only owned inactive temporary data; evidence directories are never scanned. */\nexport async function cleanupExpiredWorkspaces(): Promise<void> {\n const parent = await workspaceStateParent();\n const metadata = await workspaceMetadataDirectory(parent);\n const cutoff = Date.now() - RETENTION_MS;\n for (const entry of await readdir(metadata, { withFileTypes: true }))\n if (entry.isFile() && /^case-[a-zA-Z0-9]+\\.json$/.test(entry.name)) {\n const workspace = join(parent, entry.name.slice(0, -5));\n await expireWorkspace(workspace, cutoff).catch((cause: unknown) => {\n if (!isMissingFile(cause))\n console.warn(\n `temporary workspace cleanup did not complete: ${workspace}`,\n );\n });\n }\n}\n\n/** Only a runner-owned direct child can be an exception to this deny boundary. */\nexport function workspaceBoundaryException(\n root: string,\n workspace?: string,\n): string[] {\n return root === workspaceStatePath() &&\n workspace !== undefined &&\n dirname(workspace) === root\n ? [workspace]\n : [];\n}\n"]}