@bjoernrochel/sevro 0.1.0-rc.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CONTRIBUTING.md +22 -6
  2. package/README.md +5 -1
  3. package/docs/README.md +19 -15
  4. package/docs/architecture.md +138 -17
  5. package/docs/assets/README.md +5 -4
  6. package/docs/builtin-graders-v1.md +9 -2
  7. package/docs/codex-host.md +155 -57
  8. package/docs/contracts.md +2 -1
  9. package/docs/creating-cases.md +9 -2
  10. package/docs/development-cli.md +187 -19
  11. package/docs/documentation-quality.md +12 -2
  12. package/docs/evaluation-workflow.md +110 -0
  13. package/docs/extension-protocol-v1.md +26 -3
  14. package/docs/getting-started.md +23 -2
  15. package/docs/guide-evaluation.md +33 -1
  16. package/docs/identity-v1.md +25 -2
  17. package/docs/installing.md +11 -11
  18. package/docs/native-controls.md +76 -5
  19. package/docs/native-hosts.md +196 -7
  20. package/docs/reading-results.md +102 -0
  21. package/docs/recipes.md +489 -0
  22. package/docs/releases.md +31 -12
  23. package/docs/report-v1.md +29 -1
  24. package/docs/results-v1.md +122 -4
  25. package/docs/run-settings-v1.md +147 -0
  26. package/docs/runtime-v1.md +255 -22
  27. package/docs/troubleshooting.md +12 -12
  28. package/docs/typescript-quality.md +89 -26
  29. package/examples/recipes/candidate.ts +26 -0
  30. package/examples/recipes/cases/coding-change.json +21 -0
  31. package/examples/recipes/cases/host-tools.json +21 -0
  32. package/examples/recipes/cases/mise.json +21 -0
  33. package/examples/recipes/cases/node.json +21 -0
  34. package/examples/recipes/cases/python-uv.json +21 -0
  35. package/examples/recipes/cases/semantic.json +17 -0
  36. package/examples/recipes/coding-change/check.ts +23 -0
  37. package/examples/recipes/extension-artifacts.ts +65 -0
  38. package/examples/recipes/extension-methods.ts +174 -0
  39. package/examples/recipes/extension.ts +37 -0
  40. package/examples/recipes/repository-skill/.agents/skills/startup-guide/SKILL.md +7 -0
  41. package/examples/recipes/repository-skill/.claude/skills/startup-guide/SKILL.md +7 -0
  42. package/examples/recipes/repository-skill/README.md +3 -0
  43. package/examples/recipes/run-inputs.ts +188 -0
  44. package/examples/recipes/run-options.ts +131 -0
  45. package/examples/recipes/run.ts +63 -0
  46. package/examples/recipes/runtime/empty.json +1 -0
  47. package/examples/recipes/runtime/goals.json +1 -0
  48. package/examples/recipes/runtime/mise.json +6 -0
  49. package/examples/recipes/runtime/path.json +1 -0
  50. package/examples/recipes/runtime/plugins.json +4 -0
  51. package/examples/recipes/runtime/python-uv.json +10 -0
  52. package/examples/recipes/semantic-judge.ts +29 -0
  53. package/examples/templates/coding/adapter.ts +17 -0
  54. package/examples/templates/coding/case.json +15 -0
  55. package/examples/templates/repository-skill/.agents/skills/startup-guide/SKILL.md +6 -0
  56. package/examples/templates/repository-skill/extension-data.ts +64 -0
  57. package/examples/templates/repository-skill/extension.ts +31 -0
  58. package/examples/templates/response/adapter.ts +12 -0
  59. package/examples/templates/response/case.json +13 -0
  60. package/package.json +11 -5
  61. package/schemas/report-v1.schema.json +9 -0
  62. package/schemas/run-evidence-v1.schema.json +2 -1
  63. package/schemas/runtime-v1.schema.json +43 -7
  64. package/src/advisory.ts +2 -2
  65. package/src/candidate-artifacts.ts +42 -11
  66. package/src/candidate-codex-state.ts +185 -0
  67. package/src/case-validation.ts +73 -50
  68. package/src/cli/cancellation.ts +20 -0
  69. package/src/cli/command.ts +87 -0
  70. package/src/cli/completion.ts +15 -0
  71. package/src/cli/demo.ts +60 -0
  72. package/src/cli/evaluation.ts +55 -0
  73. package/src/cli/extension.ts +79 -0
  74. package/src/cli/help.ts +198 -0
  75. package/src/cli/hosts.ts +55 -0
  76. package/src/cli/init-settings.ts +30 -0
  77. package/src/cli/init.ts +111 -0
  78. package/src/{cli-invocation.ts → cli/invocation.ts} +214 -218
  79. package/src/cli/loading.ts +151 -0
  80. package/src/cli/preparation.ts +154 -0
  81. package/src/cli/presentation.ts +93 -0
  82. package/src/cli/progress.ts +6 -0
  83. package/src/cli/run-manifest.ts +128 -0
  84. package/src/cli/run-options.ts +403 -0
  85. package/src/cli/run-overrides.ts +69 -0
  86. package/src/cli/settings.ts +154 -0
  87. package/src/cli/summary.ts +43 -0
  88. package/src/cli.ts +13 -591
  89. package/src/contract-types.ts +94 -0
  90. package/src/engine.ts +9 -5
  91. package/src/evaluation/advisory.ts +150 -0
  92. package/src/{evaluation-check-plan.ts → evaluation/check-plan.ts} +103 -67
  93. package/src/evaluation/collection.ts +88 -0
  94. package/src/evaluation/context.ts +10 -0
  95. package/src/{evaluation-grading-host.ts → evaluation/grading-host.ts} +5 -5
  96. package/src/evaluation/grading.ts +235 -0
  97. package/src/{evaluation-invocation-plan.ts → evaluation/invocation-plan.ts} +147 -58
  98. package/src/{evaluation-plan.ts → evaluation/plan.ts} +208 -141
  99. package/src/evaluation/progress.ts +19 -0
  100. package/src/evaluation/resources.ts +241 -0
  101. package/src/evaluation/run.ts +206 -0
  102. package/src/{evaluation-semantic.ts → evaluation/semantic.ts} +77 -87
  103. package/src/evaluation/trial.ts +800 -0
  104. package/src/{evaluation-types.ts → evaluation/types.ts} +79 -29
  105. package/src/evidence/host-results.ts +85 -0
  106. package/src/evidence/retained-artifacts.ts +18 -0
  107. package/src/evidence/trial-writer.ts +203 -0
  108. package/src/evidence/usage.ts +17 -0
  109. package/src/extension-client.ts +34 -96
  110. package/src/extension-session.ts +24 -48
  111. package/src/{advisory-fixture.ts → fixtures/advisory.ts} +12 -72
  112. package/src/{fixture-bin.ts → fixtures/bin.ts} +1 -1
  113. package/src/{fixture-cleanup.ts → fixtures/cleanup.ts} +13 -7
  114. package/src/fixtures/create.ts +183 -0
  115. package/src/{generated-fixture.ts → fixtures/generated.ts} +10 -53
  116. package/src/{git-hooks.ts → fixtures/git-hooks.ts} +1 -1
  117. package/src/{preparation.ts → fixtures/preparation.ts} +6 -16
  118. package/src/{repository-fixture.ts → fixtures/repository.ts} +30 -64
  119. package/src/{fixture-setup.ts → fixtures/setup.ts} +33 -51
  120. package/src/{fixture-tools.ts → fixtures/tools.ts} +1 -1
  121. package/src/{workspace-state.ts → fixtures/workspace-state.ts} +5 -5
  122. package/src/generated/cli-result.d.cts +2 -1
  123. package/src/generated/extension.d.cts +2 -1
  124. package/src/generated/report.cjs +1 -1
  125. package/src/generated/report.d.cts +2 -1
  126. package/src/generated/run-evidence.cjs +1 -1
  127. package/src/generated/run-evidence.d.cts +2 -1
  128. package/src/generated/runtime.cjs +1 -1
  129. package/src/generated/runtime.d.cts +2 -1
  130. package/src/git.ts +93 -0
  131. package/src/graders/git-head.ts +29 -84
  132. package/src/graders/shell.ts +50 -68
  133. package/src/host-diagnostic-redaction.ts +52 -0
  134. package/src/host-diagnostics.ts +331 -0
  135. package/src/hosts/claude-continuation.ts +57 -40
  136. package/src/hosts/claude-credential.ts +160 -63
  137. package/src/hosts/claude-events.ts +34 -18
  138. package/src/hosts/claude-keychain.ts +78 -21
  139. package/src/hosts/claude-login-recovery.ts +85 -0
  140. package/src/hosts/claude-login-refresh.ts +178 -0
  141. package/src/hosts/claude-login-store.ts +162 -0
  142. package/src/hosts/claude-login.ts +107 -0
  143. package/src/hosts/claude-native-goal.ts +15 -12
  144. package/src/hosts/claude-process.ts +10 -50
  145. package/src/hosts/claude-refresh-lock.ts +81 -0
  146. package/src/hosts/claude-repository-invocation.ts +9 -34
  147. package/src/hosts/claude-settings.ts +46 -9
  148. package/src/hosts/claude-skill-activations.ts +169 -0
  149. package/src/hosts/claude-state.ts +28 -15
  150. package/src/hosts/claude-tool-calls.ts +13 -10
  151. package/src/hosts/claude.ts +121 -81
  152. package/src/hosts/codex-app-server.ts +78 -52
  153. package/src/hosts/codex-configuration.ts +5 -10
  154. package/src/hosts/codex-exec-controls.ts +516 -0
  155. package/src/hosts/codex-marketplace.ts +12 -10
  156. package/src/hosts/codex-native-calls.ts +15 -122
  157. package/src/hosts/codex-process.ts +10 -183
  158. package/src/hosts/codex-profile.ts +36 -21
  159. package/src/hosts/codex-session-files.ts +1 -9
  160. package/src/hosts/codex-state.ts +134 -18
  161. package/src/hosts/codex.ts +210 -183
  162. package/src/hosts/isolation-common.ts +8 -11
  163. package/src/hosts/isolation-roots.ts +2 -2
  164. package/src/hosts/linux-sandbox.ts +15 -2
  165. package/src/hosts/mac-sandbox.ts +87 -18
  166. package/src/hosts/native-controls.ts +72 -22
  167. package/src/hosts/native-request.ts +55 -0
  168. package/src/hosts/process-lifecycle.ts +216 -48
  169. package/src/hosts/protected-worktrees.ts +1 -1
  170. package/src/hosts/skill-hook-catalog.ts +131 -0
  171. package/src/hosts/skill-hook-handler.ts +374 -0
  172. package/src/hosts/skill-hooks.ts +577 -0
  173. package/src/instrumentation.ts +7 -8
  174. package/src/native-transcript-state.ts +22 -3
  175. package/src/native-transcript-view.ts +52 -13
  176. package/src/native-transcripts.ts +26 -11
  177. package/src/paths.ts +22 -0
  178. package/src/process-lifecycle.ts +150 -0
  179. package/src/provenance.ts +27 -7
  180. package/src/prune.ts +300 -0
  181. package/src/report.ts +63 -36
  182. package/src/resolved-case.ts +1 -1
  183. package/src/results-lock.ts +42 -0
  184. package/src/results.ts +14 -18
  185. package/src/{runtime-config.ts → runtime/config.ts} +160 -46
  186. package/src/{runtime-discovery-apple.ts → runtime/discovery-apple.ts} +3 -3
  187. package/src/{runtime-discovery-providers.ts → runtime/discovery-providers.ts} +3 -9
  188. package/src/{runtime-discovery.ts → runtime/discovery.ts} +4 -4
  189. package/src/{runtime-hook-processes.ts → runtime/hook-processes.ts} +1 -1
  190. package/src/{runtime-hooks.ts → runtime/hooks.ts} +32 -22
  191. package/src/{runtime-paths.ts → runtime/paths.ts} +5 -19
  192. package/src/{runtime-query.ts → runtime/query.ts} +5 -5
  193. package/src/{runtime-seeds.ts → runtime/seeds.ts} +3 -3
  194. package/src/runtime/shared-cache.ts +130 -0
  195. package/src/{runtime-state.ts → runtime/state.ts} +28 -11
  196. package/src/schema-types.ts +73 -161
  197. package/src/shell.ts +4 -0
  198. package/src/transcript-retention.ts +146 -0
  199. package/src/value-guards.ts +22 -0
  200. package/docs/assets/sevro-logo.png +0 -0
  201. package/docs/assets/sevro-symbol.svg +0 -1
  202. package/docs/assets/sevro-wordmark.svg +0 -1
  203. package/docs/cache-ownership-validation.md +0 -49
  204. package/docs/claude-continuation-validation.md +0 -47
  205. package/docs/concurrency-validation.md +0 -86
  206. package/docs/configuration-root-validation.md +0 -70
  207. package/docs/engine-slice.md +0 -114
  208. package/docs/evidence/runtime-startup-30/README.md +0 -185
  209. package/docs/evidence/runtime-startup-30/after.json +0 -319
  210. package/docs/evidence/runtime-startup-30/baseline-timeout.jsonl +0 -2
  211. package/docs/evidence/runtime-startup-30/before.json +0 -403
  212. package/docs/evidence/runtime-startup-30/consumer-smoke.json +0 -63
  213. package/docs/evidence/runtime-startup-30/full-policy-diagnostic.jsonl +0 -3
  214. package/docs/evidence/runtime-startup-30/minimal-policy-diagnostic.jsonl +0 -3
  215. package/docs/evidence/runtime-startup-30/plugin-template-assessment.json +0 -20
  216. package/docs/evidence/runtime-startup-30/seedless-timeout.jsonl +0 -2
  217. package/docs/evidence.md +0 -17
  218. package/docs/fixture-cleanup-validation.md +0 -73
  219. package/docs/guide-verification.json +0 -239
  220. package/src/evaluation-context.ts +0 -259
  221. package/src/evaluation-fixture.ts +0 -302
  222. package/src/evaluation-run.ts +0 -200
  223. package/src/evaluation-trial.ts +0 -1016
  224. package/src/hosts/claude-nested-skills.ts +0 -272
  225. package/src/hosts/codex-command-output.ts +0 -510
  226. package/src/hosts/codex-executor-command.ts +0 -291
  227. package/src/hosts/codex-skill-reads.ts +0 -783
  228. /package/src/{trial-scheduler.ts → evaluation/scheduler.ts} +0 -0
@@ -1,12 +1,19 @@
1
- import { runtimeSeedSources } from "../runtime-config";
1
+ import { runtimeLocalHttpEnabled, runtimeSeedSources } from "../runtime/config";
2
+ import { shellQuote } from "../shell";
2
3
  import {
3
4
  candidateArtifactEnvironment,
4
5
  candidateArtifactReadRoots,
5
6
  } from "../candidate-artifacts";
7
+ import {
8
+ codexStateEnabled,
9
+ retainCandidateCodexState,
10
+ } from "../candidate-codex-state";
6
11
  import { randomUUID } from "node:crypto";
7
12
  import { existsSync } from "node:fs";
13
+ import { isMissingFile } from "../fixtures/tools";
8
14
  import {
9
15
  cp,
16
+ lstat,
10
17
  mkdir,
11
18
  readFile,
12
19
  realpath,
@@ -21,12 +28,17 @@ import {
21
28
  candidateTranscriptEnvironment,
22
29
  candidateTranscriptRoots,
23
30
  } from "../native-transcripts";
24
- import { prepareHookMounts } from "../runtime-hooks";
31
+ import { prepareHookMounts } from "../runtime/hooks";
32
+ import { requireNativeHookRoots } from "../native-transcript-state";
25
33
  import {
26
34
  requireRuntimeReadRoots,
27
35
  runtimeExecutableProtection,
28
- } from "../runtime-paths";
29
- import { prepareRuntimeState } from "../runtime-state";
36
+ } from "../runtime/paths";
37
+ import { prepareRuntimeState } from "../runtime/state";
38
+ import {
39
+ sharedCacheDenyRoots,
40
+ sharedCacheRoots,
41
+ } from "../runtime/shared-cache";
30
42
  import { codexPermissionProfile } from "./codex-profile";
31
43
  import { evaluationProtectedRoots } from "./isolation-roots";
32
44
  type Request = Parameters<HostAdapter["run"]>[0];
@@ -44,6 +56,7 @@ export interface CodexHostOptions {
44
56
  agentConcurrencyLimit?: number | null;
45
57
  timeoutMs?: number;
46
58
  entrypoint?: "exec" | "app-server";
59
+ retainHostDiagnostics?: boolean;
47
60
  /** Integration-test seam; production uses the same CLI for execution and preflight. */
48
61
  sandboxBinary?: string;
49
62
  }
@@ -64,23 +77,22 @@ async function copyAuth(source: string, target: string): Promise<void> {
64
77
  await writeFile(target, bytes, { flag: "wx", mode: 0o600 });
65
78
  }
66
79
 
67
- function shellQuote(value: string): string {
68
- return `'${value.replaceAll("'", `'"'"'`)}'`;
69
- }
70
-
71
80
  export async function codexHookMounts(
72
81
  request: Request,
73
82
  state: CodexState,
74
83
  installedPluginRoots: string[],
75
84
  ) {
76
- if (!request.runtimePolicy) return undefined;
77
85
  const hooks = await prepareHookMounts({
78
86
  workspace: request.workspace,
79
- stateRoot: join(request.workspace, ".git", "sevro-runtime"),
87
+ stateRoot: state.stateRoot,
80
88
  privateRoot: state.codexHome,
81
89
  pluginRoots: installedPluginRoots,
82
90
  protectedRoots: state.protectedRoots,
83
- policy: request.runtimePolicy,
91
+ policy: request.runtimePolicy ?? {
92
+ format: "sevro.runtime.v1",
93
+ environment: {},
94
+ readOnlyRoots: [],
95
+ },
84
96
  });
85
97
  for (const [index, root] of installedPluginRoots.entries()) {
86
98
  const curated = hooks.directories[index];
@@ -88,9 +100,28 @@ export async function codexHookMounts(
88
100
  await rm(root, { recursive: true });
89
101
  await cp(curated, root, { recursive: true });
90
102
  }
103
+ await grantCodexHookReads(state, hooks.directories);
91
104
  return hooks;
92
105
  }
93
106
 
107
+ /** Hooks advertise paths relative to these curated copies, not the install cache. */
108
+ async function grantCodexHookReads(
109
+ state: CodexState,
110
+ roots: string[],
111
+ ): Promise<void> {
112
+ if (roots.length === 0) return;
113
+ await requireNativeHookRoots(roots, state.codexHome);
114
+ const path = join(state.codexHome, "config.toml");
115
+ const config = await readFile(path, "utf8");
116
+ const header = `[permissions.${state.profileId}.filesystem]\n`;
117
+ const grants = roots
118
+ .map((root) => `${JSON.stringify(root)} = "read"\n`)
119
+ .join("");
120
+ await writeFile(path, config.replace(header, header + grants), {
121
+ mode: 0o600,
122
+ });
123
+ }
124
+
94
125
  async function privateCodexDirectories(stateRoot: string) {
95
126
  const codexHome = join(stateRoot, "codex-home"),
96
127
  parentHome = join(stateRoot, "home"),
@@ -145,9 +176,14 @@ async function writeCodexProfile(
145
176
  protectedRoots: string[],
146
177
  limit: number | null,
147
178
  commandEnvironment = request.runtimePolicy?.environment,
148
- runtimeWriteRoot?: string,
179
+ runtimeWriteRoot: string | undefined,
180
+ writableCacheRoots: string[],
149
181
  ): Promise<void> {
150
182
  const profile = codexPermissionProfile({
183
+ localHttp: runtimeLocalHttpEnabled(
184
+ request.runtimePolicy,
185
+ request.runtimeRole ?? "candidate",
186
+ ),
151
187
  commandEnvironment: codexProfileEnvironment(
152
188
  request,
153
189
  paths,
@@ -156,6 +192,10 @@ async function writeCodexProfile(
156
192
  runtimeWriteRoot,
157
193
  runtimeReadRoots: request.runtimePolicy?.readOnlyRoots,
158
194
  nativeReadRoots: codexNativeReadRoots(request, paths.codexHome),
195
+ nativeWriteRoots: [
196
+ ...codexStateWriteRoots(request, paths),
197
+ ...writableCacheRoots,
198
+ ],
159
199
  id: profileId,
160
200
  workspace: request.workspace,
161
201
  commandHome: paths.commandHome,
@@ -166,11 +206,17 @@ async function writeCodexProfile(
166
206
  });
167
207
  await writeFile(
168
208
  join(paths.codexHome, "config.toml"),
169
- profile + concurrencyConfiguration(limit),
209
+ profile +
210
+ concurrencyConfiguration(limit) +
211
+ fixtureTrustConfiguration(request),
170
212
  { flag: "wx", mode: 0o600 },
171
213
  );
172
214
  }
173
215
 
216
+ function fixtureTrustConfiguration(request: Request): string {
217
+ return `\n[projects.${JSON.stringify(request.workspace)}]\ntrust_level = "untrusted"\n`;
218
+ }
219
+
174
220
  function codexProfileEnvironment(
175
221
  request: Request,
176
222
  paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
@@ -181,6 +227,11 @@ function codexProfileEnvironment(
181
227
  ...codexTranscriptEnvironment(request, paths.codexHome),
182
228
  ...candidateTranscriptEnvironment(request.candidateTranscriptRoot),
183
229
  ...candidateArtifactEnvironment(request.candidateArtifactRoots),
230
+ ...(request.runtimePolicy?.codexState === true
231
+ ? {
232
+ SEVRO_CODEX_AGENT_SKILLS: join(paths.parentHome, ".agents", "skills"),
233
+ }
234
+ : {}),
184
235
  };
185
236
  }
186
237
 
@@ -188,14 +239,23 @@ function codexTranscriptEnvironment(
188
239
  request: Request,
189
240
  home: string,
190
241
  ): Record<string, string> {
191
- return request.runtimePolicy?.nativeTranscripts === true
192
- ? { CODEX_HOME: home, SEVRO_NATIVE_TRANSCRIPT_ROOT: join(home, "sessions") }
193
- : {};
242
+ return {
243
+ ...(request.runtimePolicy?.codexState === true ? { CODEX_HOME: home } : {}),
244
+ ...(request.runtimePolicy?.nativeTranscripts === true
245
+ ? {
246
+ CODEX_HOME: home,
247
+ SEVRO_NATIVE_TRANSCRIPT_ROOT: join(home, "sessions"),
248
+ }
249
+ : {}),
250
+ };
194
251
  }
195
252
 
196
253
  function codexNativeReadRoots(request: Request, home: string) {
197
254
  return [
198
255
  ...linuxArg0ReadRoots(home),
256
+ ...(request.runtimePolicy?.codexState === true
257
+ ? [join(home, "config.toml"), join(home, "skills", ".system")]
258
+ : []),
199
259
  ...codexTranscriptRoots(request, home),
200
260
  ...candidateTranscriptRoots(request.candidateTranscriptRoot),
201
261
  ...candidateArtifactReadRoots(request.candidateArtifactRoots),
@@ -321,7 +381,11 @@ function applyCodexRuntime(
321
381
  function codexRuntimeProfile(
322
382
  runtime: Awaited<ReturnType<typeof codexRuntime>>,
323
383
  ) {
324
- return { environment: runtime?.environment, root: runtime?.root };
384
+ return {
385
+ environment: runtime?.environment,
386
+ root: runtime?.root,
387
+ writableCacheRoots: runtime?.writableCacheRoots ?? [],
388
+ };
325
389
  }
326
390
 
327
391
  export async function prepareCodexState(
@@ -331,6 +395,7 @@ export async function prepareCodexState(
331
395
  request: Request,
332
396
  helperRoot?: string,
333
397
  ) {
398
+ await requireIsolatedGitMetadata(request.workspace);
334
399
  const paths = await privateCodexDirectories(stateRoot);
335
400
  const runtime = await codexRuntime(request, paths, stateRoot);
336
401
  await copyAuth(options.authFile, join(paths.codexHome, "auth.json"));
@@ -356,6 +421,14 @@ export async function prepareCodexState(
356
421
  );
357
422
  if (helperRoot) readRoots.push(helperRoot);
358
423
  requireCodexRuntimeReadRoots(request, protectedRoots);
424
+ await sharedCacheRoots(
425
+ request.runtimePolicy,
426
+ request.runtimeRole,
427
+ protectedRoots,
428
+ );
429
+ protectedRoots.push(
430
+ ...sharedCacheDenyRoots(request.runtimePolicy, request.runtimeRole),
431
+ );
359
432
  requireUnprotectedExecutables(
360
433
  readRoots,
361
434
  runtimeExecutableProtection(
@@ -375,11 +448,54 @@ export async function prepareCodexState(
375
448
  limit,
376
449
  runtimeProfile.environment,
377
450
  runtimeProfile.root,
451
+ runtimeProfile.writableCacheRoots,
378
452
  );
379
453
  const shellRoot = await fixtureShellRoot(request, paths.parentHome);
380
454
  const env = codexEnvironment(paths, request, shellRoot);
381
455
  applyCodexRuntime(runtime, env, paths);
382
- return { ...paths, sandboxBinary, profileId, env, protectedRoots };
456
+ await prepareCodexStateEvidence(request, stateRoot, paths);
457
+ return { ...paths, stateRoot, sandboxBinary, profileId, env, protectedRoots };
458
+ }
459
+
460
+ function codexStateWriteRoots(
461
+ request: Request,
462
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
463
+ ): string[] {
464
+ return codexStateEnabled(request.runtimePolicy)
465
+ ? [
466
+ join(paths.codexHome, "skills"),
467
+ join(paths.parentHome, ".agents", "skills"),
468
+ join(paths.commandHome, ".agents", "skills"),
469
+ join(paths.commandHome, ".codex", "skills"),
470
+ ]
471
+ : [];
472
+ }
473
+
474
+ async function prepareCodexStateEvidence(
475
+ request: Request,
476
+ stateRoot: string,
477
+ paths: Awaited<ReturnType<typeof privateCodexDirectories>>,
478
+ ): Promise<void> {
479
+ if (!codexStateEnabled(request.runtimePolicy)) return;
480
+ const skillRoots = codexStateWriteRoots(request, paths);
481
+ await Promise.all(
482
+ skillRoots.map((root) => mkdir(root, { recursive: true, mode: 0o700 })),
483
+ );
484
+ await mkdir(join(paths.codexHome, "skills", ".system"), { mode: 0o700 });
485
+ await retainCandidateCodexState(
486
+ request.candidateRuntimeRoot,
487
+ stateRoot,
488
+ paths,
489
+ );
490
+ }
491
+
492
+ async function requireIsolatedGitMetadata(workspace: string): Promise<void> {
493
+ try {
494
+ if (!(await lstat(join(workspace, ".git"))).isDirectory())
495
+ throw new Error("Codex Git metadata must be an isolated directory");
496
+ } catch (cause) {
497
+ if (!isMissingFile(cause)) throw cause;
498
+ }
383
499
  }
384
500
 
385
501
  function requireCodexRuntimeReadRoots(