intentdna 1.7.4 → 1.8.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 (152) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/LICENSE +201 -0
  4. package/README.md +143 -194
  5. package/dist/assets/candidates.d.ts +124 -0
  6. package/dist/assets/candidates.js +1249 -0
  7. package/dist/assets/catalog.d.ts +101 -0
  8. package/dist/assets/catalog.js +311 -0
  9. package/dist/assets/evidence.d.ts +93 -0
  10. package/dist/assets/evidence.js +391 -0
  11. package/dist/assets/index.d.ts +4 -0
  12. package/dist/assets/index.js +4 -0
  13. package/dist/assets/metadata.d.ts +19 -0
  14. package/dist/assets/metadata.js +227 -0
  15. package/dist/cli/commands/assets.d.ts +65 -0
  16. package/dist/cli/commands/assets.js +2871 -0
  17. package/dist/cli/commands/auth.d.ts +322 -0
  18. package/dist/cli/commands/auth.js +1592 -0
  19. package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
  20. package/dist/cli/commands/cloud-auth-guidance.js +4 -0
  21. package/dist/cli/commands/cloud.d.ts +1359 -0
  22. package/dist/cli/commands/cloud.js +8364 -0
  23. package/dist/cli/commands/evolve.js +18 -2
  24. package/dist/cli/commands/generate.js +25 -2
  25. package/dist/cli/commands/guard.d.ts +1 -1
  26. package/dist/cli/commands/guard.js +1 -1
  27. package/dist/cli/commands/guide.d.ts +15 -0
  28. package/dist/cli/commands/guide.js +1747 -0
  29. package/dist/cli/commands/init.d.ts +12 -0
  30. package/dist/cli/commands/init.js +95 -60
  31. package/dist/cli/commands/org.d.ts +16 -0
  32. package/dist/cli/commands/org.js +2393 -0
  33. package/dist/cli/commands/release.d.ts +59 -0
  34. package/dist/cli/commands/release.js +716 -0
  35. package/dist/cli/commands/report.d.ts +82 -0
  36. package/dist/cli/commands/report.js +1516 -0
  37. package/dist/cli/commands/run.d.ts +6 -1
  38. package/dist/cli/commands/run.js +95 -26
  39. package/dist/cli/commands/sync.d.ts +8 -1
  40. package/dist/cli/commands/sync.js +436 -141
  41. package/dist/cli/commands/templates.d.ts +64 -0
  42. package/dist/cli/commands/templates.js +2151 -1
  43. package/dist/cli/commands/workflow.d.ts +26 -0
  44. package/dist/cli/commands/workflow.js +3053 -0
  45. package/dist/cli/index.js +719 -42
  46. package/dist/compiler/cascade.js +4 -9
  47. package/dist/compiler/compile.js +1 -0
  48. package/dist/governance/evidence-capture-attribution.d.ts +12 -0
  49. package/dist/governance/evidence-capture-attribution.js +46 -0
  50. package/dist/governance/index.d.ts +2 -0
  51. package/dist/governance/index.js +1 -0
  52. package/dist/hooks/cli.d.ts +6 -0
  53. package/dist/hooks/cli.js +168 -12
  54. package/dist/hooks/enforce.d.ts +1 -0
  55. package/dist/hooks/protocol.d.ts +1 -1
  56. package/dist/hooks/protocol.js +1 -1
  57. package/dist/hooks/state.d.ts +41 -0
  58. package/dist/hooks/state.js +126 -2
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.js +3 -0
  61. package/dist/mcp/tools-state.js +2 -0
  62. package/dist/organization/index.d.ts +121 -0
  63. package/dist/organization/index.js +413 -0
  64. package/dist/runtime/claude-sync-target.d.ts +49 -0
  65. package/dist/runtime/claude-sync-target.js +280 -0
  66. package/dist/runtime/codex-adapter.d.ts +254 -0
  67. package/dist/runtime/codex-adapter.js +1204 -0
  68. package/dist/runtime/codex-sync-target.d.ts +33 -0
  69. package/dist/runtime/codex-sync-target.js +241 -0
  70. package/dist/runtime/index.d.ts +8 -0
  71. package/dist/runtime/index.js +4 -0
  72. package/dist/runtime/output-artifact-registry.d.ts +1 -1
  73. package/dist/runtime/output-artifact-registry.js +2 -0
  74. package/dist/runtime/settings-adapter.d.ts +1 -1
  75. package/dist/runtime/settings-adapter.js +1 -1
  76. package/dist/runtime/skill-adapter.d.ts +5 -1
  77. package/dist/runtime/skill-adapter.js +120 -34
  78. package/dist/runtime/sync-artifact-registry.d.ts +99 -0
  79. package/dist/runtime/sync-artifact-registry.js +194 -0
  80. package/dist/runtime/sync-target-plan.d.ts +45 -0
  81. package/dist/runtime/sync-target-plan.js +19 -0
  82. package/dist/schema/controller-registry.d.ts +8 -1
  83. package/dist/schema/controller-registry.js +8 -0
  84. package/dist/schema/types.d.ts +35 -23
  85. package/dist/schema/types.js +10 -1
  86. package/dist/schema/validate.js +22 -22
  87. package/dist/schema/validators/controllers.js +38 -20
  88. package/dist/schema/workflow-authoring-contract.d.ts +29 -0
  89. package/dist/schema/workflow-authoring-contract.js +109 -0
  90. package/dist/schema/yaml-parser.js +18 -0
  91. package/dist/templates/api-backend.dna.yaml +19 -0
  92. package/dist/templates/brainstorming-first.dna.yaml +19 -0
  93. package/dist/templates/code-cleanup.dna.yaml +19 -0
  94. package/dist/templates/code-review-pipeline.dna.yaml +18 -0
  95. package/dist/templates/documentation-writer.dna.yaml +19 -0
  96. package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
  97. package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
  98. package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
  99. package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
  100. package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
  101. package/dist/templates/flutter-rewrite.dna.yaml +20 -0
  102. package/dist/templates/frontend-quality.dna.yaml +19 -0
  103. package/dist/templates/full-pipeline.dna.yaml +20 -0
  104. package/dist/templates/marketplace.d.ts +92 -0
  105. package/dist/templates/marketplace.js +502 -0
  106. package/dist/templates/metadata.d.ts +25 -0
  107. package/dist/templates/metadata.js +136 -3
  108. package/dist/templates/mobile-dev.dna.yaml +19 -0
  109. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
  110. package/dist/templates/multi-perspective-review.dna.yaml +19 -0
  111. package/dist/templates/persistent-executor.dna.yaml +19 -0
  112. package/dist/templates/qa-loop.dna.yaml +18 -0
  113. package/dist/templates/release-evidence-gate.dna.yaml +131 -0
  114. package/dist/templates/requirements-gate.dna.yaml +19 -0
  115. package/dist/templates/research-orchestration.dna.yaml +19 -0
  116. package/dist/templates/root-cause-analysis.dna.yaml +19 -0
  117. package/dist/templates/safe-refactoring.dna.yaml +321 -0
  118. package/dist/templates/secure-dev.dna.yaml +19 -0
  119. package/dist/templates/subagent-parallel.dna.yaml +19 -0
  120. package/dist/templates/systematic-debugging.dna.yaml +19 -0
  121. package/dist/templates/tdd-strict.dna.yaml +19 -0
  122. package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
  123. package/dist/templates/workflow-author.dna.yaml +302 -0
  124. package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
  125. package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
  126. package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
  127. package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
  128. package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
  129. package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
  130. package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
  131. package/dist/workflow/authoring-packet.d.ts +67 -0
  132. package/dist/workflow/authoring-packet.js +218 -0
  133. package/dist/workflow/draft.d.ts +159 -0
  134. package/dist/workflow/draft.js +800 -0
  135. package/dist/workflow/index.d.ts +3 -0
  136. package/dist/workflow/index.js +3 -0
  137. package/dist/workflow/suggestion-review.d.ts +65 -0
  138. package/dist/workflow/suggestion-review.js +245 -0
  139. package/package.json +1 -1
  140. package/spec/README.md +15 -48
  141. package/spec/codex-adapter-contract.md +176 -0
  142. package/spec/sync-target-plan.md +158 -0
  143. package/spec/workflow-capability-ir.md +153 -0
  144. package/spec/agent-dispatch-fix.md +0 -116
  145. package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
  146. package/spec/flutter-rewrite-template-optimization.md +0 -301
  147. package/spec/foundation-hardening.md +0 -178
  148. package/spec/hooks-infra-harness-hardening.md +0 -741
  149. package/spec/multi-config.md +0 -172
  150. package/spec/parallel-isolation.md +0 -268
  151. package/spec/template-version-namespace-fix.md +0 -653
  152. package/spec/workflow-pipeline.md +0 -101
@@ -0,0 +1,1204 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { constants } from "node:fs";
4
+ import { access, mkdir, readFile, unlink } from "node:fs/promises";
5
+ import { delimiter, dirname, join, relative, resolve, sep } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { RUNTIME_DECISION_EVENT_SCHEMA_VERSION, trustedEvidenceCaptureAttribution, validateRuntimeDecisionEvent } from "../governance/index.js";
8
+ import { appendEvidenceCaptureEvent, appendRuntimeDecisionEvent, atomicWrite, EVIDENCE_CAPTURE_SCHEMA_VERSION } from "../hooks/state.js";
9
+ export const CODEX_ADAPTER_MINIMUM_VERSION = "0.134.0";
10
+ export const CODEX_NATIVE_HOOK_EVENTS = [
11
+ "SessionStart",
12
+ "PreToolUse",
13
+ "PostToolUse",
14
+ "UserPromptSubmit",
15
+ "PreCompact",
16
+ "PostCompact",
17
+ "Stop",
18
+ ];
19
+ const EVENT_LABELS = {
20
+ SessionStart: "session_start",
21
+ PreToolUse: "pre_tool_use",
22
+ PostToolUse: "post_tool_use",
23
+ UserPromptSubmit: "user_prompt_submit",
24
+ PreCompact: "pre_compact",
25
+ PostCompact: "post_compact",
26
+ Stop: "stop",
27
+ };
28
+ const EVENT_MATCHERS = {
29
+ SessionStart: "startup|resume|clear",
30
+ PreToolUse: "Bash",
31
+ };
32
+ const EVENT_TIMEOUTS = {
33
+ Stop: 30,
34
+ };
35
+ const OWNED_HOOK_STATUS_MESSAGE = "intentdna:codex-adapter";
36
+ const OWNED_PLUGIN_NAME = "intentdna-codex";
37
+ const TRUST_STATE_START = "# Intent DNA-owned Codex hook trust state";
38
+ const TRUST_STATE_END = "# End Intent DNA-owned Codex hook trust state";
39
+ const execFileAsync = promisify(execFile);
40
+ function compareSemver(a, b) {
41
+ const parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
42
+ const left = parse(a);
43
+ const right = parse(b);
44
+ const length = Math.max(left.length, right.length);
45
+ for (let index = 0; index < length; index++) {
46
+ const leftPart = left[index] ?? 0;
47
+ const rightPart = right[index] ?? 0;
48
+ if (leftPart > rightPart)
49
+ return 1;
50
+ if (leftPart < rightPart)
51
+ return -1;
52
+ }
53
+ return 0;
54
+ }
55
+ function parseCodexVersion(output) {
56
+ return output.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
57
+ }
58
+ async function executableFromPath(name, envPath = process.env.PATH ?? "") {
59
+ for (const dir of envPath.split(delimiter)) {
60
+ if (!dir)
61
+ continue;
62
+ const candidate = join(dir, name);
63
+ try {
64
+ await access(candidate, constants.X_OK);
65
+ return candidate;
66
+ }
67
+ catch { /* keep searching */ }
68
+ }
69
+ return null;
70
+ }
71
+ async function runCodexProbeCommand(executablePath, args) {
72
+ try {
73
+ await execFileAsync(executablePath, args, { timeout: 5000 });
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ export async function probeCodexRuntime(options = {}) {
81
+ const executablePath = options.executablePath ?? await executableFromPath("codex", options.envPath);
82
+ if (!executablePath) {
83
+ return {
84
+ executable_path: null,
85
+ version: null,
86
+ minimum_version: CODEX_ADAPTER_MINIMUM_VERSION,
87
+ satisfies_minimum_version: false,
88
+ exec_json_available: false,
89
+ execpolicy_check_available: false,
90
+ output_schema_available: false,
91
+ classification: "diagnostic_only",
92
+ reason: "Codex executable was not found on PATH",
93
+ };
94
+ }
95
+ try {
96
+ const versionOutput = await execFileAsync(executablePath, ["--version"], { timeout: 5000 });
97
+ const version = parseCodexVersion(`${versionOutput.stdout}\n${versionOutput.stderr}`);
98
+ const satisfiesMinimum = version ? compareSemver(version, CODEX_ADAPTER_MINIMUM_VERSION) >= 0 : false;
99
+ const execJsonAvailable = await runCodexProbeCommand(executablePath, ["exec", "--help"]);
100
+ const execpolicyCheckAvailable = await runCodexProbeCommand(executablePath, ["execpolicy", "check", "--help"]);
101
+ return {
102
+ executable_path: executablePath,
103
+ version,
104
+ minimum_version: CODEX_ADAPTER_MINIMUM_VERSION,
105
+ satisfies_minimum_version: satisfiesMinimum,
106
+ exec_json_available: execJsonAvailable,
107
+ execpolicy_check_available: execpolicyCheckAvailable,
108
+ output_schema_available: execJsonAvailable,
109
+ classification: satisfiesMinimum ? "diagnostic_only" : "diagnostic_only",
110
+ reason: satisfiesMinimum ? "Codex runtime probe succeeded; runtime enforcement still requires trusted hook execution evidence" : "Codex runtime is missing or below the minimum supported version",
111
+ };
112
+ }
113
+ catch {
114
+ return {
115
+ executable_path: executablePath,
116
+ version: null,
117
+ minimum_version: CODEX_ADAPTER_MINIMUM_VERSION,
118
+ satisfies_minimum_version: false,
119
+ exec_json_available: false,
120
+ execpolicy_check_available: false,
121
+ output_schema_available: false,
122
+ classification: "diagnostic_only",
123
+ reason: "Codex version probe failed",
124
+ };
125
+ }
126
+ }
127
+ function parseJsonlEvents(stdout) {
128
+ return stdout.split(/\r?\n/)
129
+ .map((line) => line.trim())
130
+ .filter(Boolean)
131
+ .flatMap((line) => {
132
+ try {
133
+ const parsed = JSON.parse(line);
134
+ return isRecord(parsed) ? [{ raw: parsed }] : [];
135
+ }
136
+ catch {
137
+ return [];
138
+ }
139
+ });
140
+ }
141
+ function execFileWithTimeout(executablePath, args, options) {
142
+ return new Promise((resolve) => {
143
+ const child = spawn(executablePath, args, {
144
+ cwd: options.cwd,
145
+ env: { ...process.env, ...options.env },
146
+ stdio: ["ignore", "pipe", "pipe"],
147
+ });
148
+ const stdoutChunks = [];
149
+ const stderrChunks = [];
150
+ let settled = false;
151
+ const finish = (status, exitCode) => {
152
+ if (settled)
153
+ return;
154
+ settled = true;
155
+ clearTimeout(timer);
156
+ resolve({
157
+ status,
158
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
159
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
160
+ exitCode,
161
+ });
162
+ };
163
+ const timer = setTimeout(() => {
164
+ child.kill("SIGTERM");
165
+ finish("timeout", null);
166
+ }, options.timeoutMs);
167
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
168
+ child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
169
+ child.on("error", () => finish("failed", null));
170
+ child.on("close", (code) => finish(code === 0 ? "completed" : "failed", code));
171
+ });
172
+ }
173
+ export async function runCodexExecDogfood(options) {
174
+ const probe = await probeCodexRuntime({ executablePath: options.executablePath, envPath: options.env?.PATH });
175
+ const executablePath = probe.executable_path;
176
+ const prompt = options.prompt ?? "Reply exactly INTENTDNA-CODEX-DOGFOOD-OK and do not run shell commands.";
177
+ const args = [
178
+ "exec",
179
+ "--ephemeral",
180
+ "--json",
181
+ "--skip-git-repo-check",
182
+ "-C",
183
+ options.projectDir,
184
+ "--sandbox",
185
+ "read-only",
186
+ ...(options.extraArgs ?? []),
187
+ prompt,
188
+ ];
189
+ if (!executablePath || !probe.satisfies_minimum_version || !probe.exec_json_available) {
190
+ return {
191
+ probe,
192
+ status: "skipped",
193
+ classification: "diagnostic_only",
194
+ command: [executablePath ?? "codex", ...args],
195
+ events: [],
196
+ stdout: "",
197
+ stderr: "",
198
+ reason: probe.reason,
199
+ };
200
+ }
201
+ const result = await execFileWithTimeout(executablePath, args, {
202
+ cwd: options.projectDir,
203
+ timeoutMs: options.timeoutMs ?? 30000,
204
+ env: options.env,
205
+ });
206
+ const events = parseJsonlEvents(result.stdout);
207
+ return {
208
+ probe,
209
+ status: result.status,
210
+ classification: result.status === "completed" && events.length > 0 ? "diagnostic_only" : "diagnostic_only",
211
+ command: [executablePath, ...args],
212
+ events,
213
+ stdout: result.stdout,
214
+ stderr: result.stderr,
215
+ reason: result.status === "completed" ? "Codex exec --json dogfood completed; hook trust and RuntimeDecisionEvent provenance still determine enforcement classification" : `Codex dogfood ${result.status}`,
216
+ };
217
+ }
218
+ export function withCodexDogfoodEvidence(evidence, dogfood) {
219
+ return {
220
+ ...evidence,
221
+ runtime_probe: dogfood.probe,
222
+ notes: [
223
+ ...evidence.notes,
224
+ `Codex exec dogfood status: ${dogfood.status}`,
225
+ dogfood.reason,
226
+ ],
227
+ };
228
+ }
229
+ export function codexInstallModeSemantics(mode) {
230
+ if (mode === "plugin_scoped") {
231
+ return {
232
+ mode,
233
+ writes_plugin_artifacts: true,
234
+ writes_project_fallback: false,
235
+ writes_trust_state: false,
236
+ support_status: "partial",
237
+ classification: "diagnostic_only",
238
+ reason: "Plugin-scoped hooks are a distribution surface until Codex trust is verified by runtime evidence",
239
+ };
240
+ }
241
+ if (mode === "project_fallback") {
242
+ return {
243
+ mode,
244
+ writes_plugin_artifacts: true,
245
+ writes_project_fallback: true,
246
+ writes_trust_state: true,
247
+ support_status: "partial",
248
+ classification: "diagnostic_only",
249
+ reason: "Project fallback hooks need matching trust state and hook execution evidence before enterprise classification",
250
+ };
251
+ }
252
+ if (mode === "managed") {
253
+ return {
254
+ mode,
255
+ writes_plugin_artifacts: true,
256
+ writes_project_fallback: true,
257
+ writes_trust_state: true,
258
+ support_status: "partial",
259
+ classification: "diagnostic_only",
260
+ reason: "Managed hooks require managed root and runtime execution evidence before enterprise classification",
261
+ };
262
+ }
263
+ return {
264
+ mode,
265
+ writes_plugin_artifacts: false,
266
+ writes_project_fallback: false,
267
+ writes_trust_state: false,
268
+ support_status: "unsupported",
269
+ classification: "unsupported",
270
+ reason: "No Codex runtime files are installed in none mode",
271
+ };
272
+ }
273
+ function quoteCommandPart(value) {
274
+ if (/[\0\r\n]/.test(value))
275
+ throw new Error("Codex adapter command paths must not contain control characters");
276
+ return `'${value.replace(/'/g, `'\\''`)}'`;
277
+ }
278
+ function resolveDispatcherScript(options) {
279
+ const adapterRoot = resolve(options.adapterRoot);
280
+ const dispatcherScript = resolve(adapterRoot, options.dispatcherScript);
281
+ const relativePath = relative(adapterRoot, dispatcherScript);
282
+ if (relativePath === "" || relativePath.startsWith("..") || relativePath.includes("..")) {
283
+ throw new Error("Codex adapter dispatcherScript must stay inside adapterRoot");
284
+ }
285
+ return dispatcherScript;
286
+ }
287
+ export function buildCodexDispatcherCommand(options) {
288
+ if (options.dispatcherCommand !== undefined) {
289
+ if (options.dispatcherCommand.trim().length === 0)
290
+ throw new Error("Codex adapter dispatcherCommand is required");
291
+ if (/[\0\r\n]/.test(options.dispatcherCommand))
292
+ throw new Error("Codex adapter command paths must not contain control characters");
293
+ return options.dispatcherCommand;
294
+ }
295
+ const nodePath = options.nodePath ?? process.execPath;
296
+ if (nodePath.trim().length === 0)
297
+ throw new Error("Codex adapter nodePath is required");
298
+ return `${quoteCommandPart(nodePath)} ${quoteCommandPart(resolveDispatcherScript(options))}`;
299
+ }
300
+ function canonicalJson(value) {
301
+ if (Array.isArray(value))
302
+ return value.map((item) => canonicalJson(item));
303
+ if (value && typeof value === "object") {
304
+ const record = value;
305
+ return Object.fromEntries(Object.keys(record).sort().map((key) => [key, canonicalJson(record[key])]));
306
+ }
307
+ return value;
308
+ }
309
+ export function codexHookTrustedHash(identity) {
310
+ return `sha256:${createHash("sha256").update(JSON.stringify(canonicalJson(identity)), "utf-8").digest("hex")}`;
311
+ }
312
+ function hookGroup(event, command, hookInstallMode) {
313
+ const hook = {
314
+ type: "command",
315
+ command: `${command} CodexHook --codex-event ${event} --codex-install-mode ${hookInstallMode}`,
316
+ statusMessage: OWNED_HOOK_STATUS_MESSAGE,
317
+ ...(EVENT_TIMEOUTS[event] ? { timeout: EVENT_TIMEOUTS[event] } : {}),
318
+ };
319
+ return {
320
+ ...(EVENT_MATCHERS[event] ? { matcher: EVENT_MATCHERS[event] } : {}),
321
+ hooks: [hook],
322
+ };
323
+ }
324
+ export function compileCodexPluginManifest(options) {
325
+ return {
326
+ name: options.pluginName ?? OWNED_PLUGIN_NAME,
327
+ version: options.pluginVersion ?? "0.1.0",
328
+ description: options.pluginDescription ?? "Intent DNA Codex CLI adapter hooks",
329
+ hooks: options.pluginHooksPath ?? "./hooks/hooks.json",
330
+ };
331
+ }
332
+ function compileCodexHooksConfigForCommand(dispatcherCommand, hookInstallMode) {
333
+ if (dispatcherCommand.trim().length === 0)
334
+ throw new Error("Codex adapter dispatcherCommand is required");
335
+ return {
336
+ hooks: Object.fromEntries(CODEX_NATIVE_HOOK_EVENTS.map((event) => [event, [hookGroup(event, dispatcherCommand, hookInstallMode)]])),
337
+ };
338
+ }
339
+ export function compileCodexHooksConfig(options) {
340
+ return compileCodexHooksConfigForCommand(buildCodexDispatcherCommand(options), options.hookInstallMode ?? "project_fallback");
341
+ }
342
+ function sourceList(values) {
343
+ return [...new Set(values.map((value) => value.source_role ?? value.source_gene).filter(Boolean))].join(", ") || "unknown";
344
+ }
345
+ export function compileCodexInstructionArtifact(ir, options) {
346
+ const path = options.instructionPath ?? "AGENTS.intentdna.md";
347
+ const lines = [
348
+ "# Intent DNA Codex Instructions",
349
+ "",
350
+ "<!-- intentdna:codex-instructions:start -->",
351
+ "These instructions are generated from Intent DNA Constraint IR for Codex CLI. They are soft guidance; hooks, permissions, and RuntimeDecisionEvent evidence remain the stronger enforcement surfaces.",
352
+ "",
353
+ ];
354
+ if (ir.active_role)
355
+ lines.push(`Active role: ${ir.active_role}`, "");
356
+ if (ir.role_scope)
357
+ lines.push(`Role scope: ${JSON.stringify(ir.role_scope)}`, "");
358
+ if (ir.prompt_directives.length > 0) {
359
+ lines.push("## Prompt directives");
360
+ for (const directive of ir.prompt_directives)
361
+ lines.push(`- [${directive.priority}] ${directive.text} (source: ${directive.source_gene})`);
362
+ lines.push("");
363
+ }
364
+ if (ir.context_injections.length > 0) {
365
+ lines.push("## Context injections");
366
+ for (const injection of ir.context_injections)
367
+ lines.push(`- [${injection.type}] ${injection.text} (tags: ${injection.relevance_tags.join(", ")})`);
368
+ lines.push("");
369
+ }
370
+ if (ir.pre_execution_gates.length > 0) {
371
+ lines.push("## Pre-execution gates summarized for Codex");
372
+ for (const gate of ir.pre_execution_gates)
373
+ lines.push(`- ${gate.action.toUpperCase()} when ${gate.condition}: ${gate.message} (source: ${gate.source_gene})`);
374
+ lines.push("");
375
+ }
376
+ if (ir.post_execution_validators.length > 0 || (ir.verifier_specs?.length ?? 0) > 0) {
377
+ lines.push("## Validation expectations");
378
+ for (const validator of ir.post_execution_validators)
379
+ lines.push(`- Check ${validator.check} (source: ${validator.source_gene})`);
380
+ for (const spec of ir.verifier_specs ?? [])
381
+ lines.push(`- ${spec.kind} verifier ${spec.id} at ${spec.when} (${spec.severity})`);
382
+ lines.push("");
383
+ }
384
+ lines.push("<!-- intentdna:codex-instructions:end -->", "");
385
+ return { path, content: lines.join("\n") };
386
+ }
387
+ function policyMapEntry(source, codexSurface, supportStatus, evidenceRef, notes) {
388
+ return { source, codex_surface: codexSurface, support_status: supportStatus, evidence_ref: evidenceRef, notes };
389
+ }
390
+ export function compileCodexPolicyMapArtifact(ir, options) {
391
+ const path = options.policyMapPath ?? ".dna/compiled/codex-policy-map.json";
392
+ const entries = [];
393
+ if (ir.prompt_directives.length > 0)
394
+ entries.push(policyMapEntry(sourceList(ir.prompt_directives), options.instructionPath ?? "AGENTS.intentdna.md", "partial", "codex-instruction-artifact", ["Prompt directives are soft Codex instructions, not deterministic enforcement."]));
395
+ if (ir.role_scope || ir.role_tool_permissions || ir.roles_scope_map?.length)
396
+ entries.push(policyMapEntry(sourceList(ir.roles_scope_map?.map((entry) => ({ source_role: entry.role_name })) ?? []), "Codex permissions/rules plus hook diagnostics", "partial", "codex-role-scope-map", ["Role scope and tool permissions require Codex permissions/rules or hooks for each concrete runtime path."]));
397
+ if (ir.pre_execution_gates.length > 0)
398
+ entries.push(policyMapEntry(sourceList(ir.pre_execution_gates), "PreToolUse project_fallback hook", "partial", "codex-pre-tool-use-hook", ["Only supported Codex PreToolUse paths can be intercepted; non-Bash/non-MCP gaps stay explicit."]));
399
+ if (ir.tool_filters.length > 0)
400
+ entries.push(policyMapEntry(sourceList(ir.tool_filters), "Codex execpolicy/rules and PreToolUse hook subset", "partial", "codex-tool-filter-map", ["Codex execpolicy syntax is runtime-specific; adapter records the mapping and does not claim all tool filters are enforceable."]));
401
+ if (ir.post_execution_validators.length > 0 || (ir.verifier_specs?.length ?? 0) > 0)
402
+ entries.push(policyMapEntry(sourceList(ir.post_execution_validators), "PostToolUse/Stop validation evidence", "partial", "codex-validation-hook", ["Post execution validation can record evidence but cannot roll back side effects."]));
403
+ if (ir.context_injections.length > 0)
404
+ entries.push(policyMapEntry(sourceList(ir.context_injections.map((entry) => ({ source_gene: entry.type }))), options.instructionPath ?? "AGENTS.intentdna.md", "partial", "codex-context-instruction-artifact", ["Static context can be rendered into instructions; dynamic context remains event-limited."]));
405
+ return {
406
+ path,
407
+ content: {
408
+ schema_version: "intentdna.codex_policy_map.v1",
409
+ generated_at: options.generatedAt ?? new Date().toISOString(),
410
+ adapter: "codex_cli",
411
+ entries,
412
+ unsupported: unsupportedCodexCapabilities(ir),
413
+ },
414
+ };
415
+ }
416
+ function normalizedCommandHookIdentity(event, group, hook) {
417
+ return {
418
+ event_name: EVENT_LABELS[event],
419
+ ...(group.matcher ? { matcher: group.matcher } : {}),
420
+ hooks: [{
421
+ type: "command",
422
+ command: hook.command,
423
+ timeout: Math.max(1, hook.timeout ?? 600),
424
+ async: false,
425
+ ...(hook.statusMessage ? { statusMessage: hook.statusMessage } : {}),
426
+ }],
427
+ };
428
+ }
429
+ function trustStateKey(hooksPath, event, groupIndex, handlerIndex) {
430
+ return `${hooksPath}:${EVENT_LABELS[event]}:${groupIndex}:${handlerIndex}`;
431
+ }
432
+ export function buildCodexHookTrustState(hooksPath, hooksConfig) {
433
+ if (hooksPath.trim().length === 0)
434
+ return {};
435
+ const state = {};
436
+ for (const event of CODEX_NATIVE_HOOK_EVENTS) {
437
+ const groups = hooksConfig.hooks[event] ?? [];
438
+ groups.forEach((group, groupIndex) => {
439
+ group.hooks.forEach((hook, handlerIndex) => {
440
+ if (hook.type !== "command" || !isIntentDnaOwnedHook(hook))
441
+ return;
442
+ state[trustStateKey(hooksPath, event, groupIndex, handlerIndex)] = {
443
+ trusted_hash: codexHookTrustedHash(normalizedCommandHookIdentity(event, group, hook)),
444
+ };
445
+ });
446
+ });
447
+ }
448
+ return state;
449
+ }
450
+ function trustStateMatches(expected, actual) {
451
+ const expectedEntries = Object.entries(expected);
452
+ return expectedEntries.length > 0 && expectedEntries.every(([key, value]) => actual[key]?.trusted_hash === value.trusted_hash);
453
+ }
454
+ export function verifyCodexHookTrust(input) {
455
+ const reasons = [];
456
+ const evidenceRefs = [
457
+ { type: "artifact", ref: "codex-runtime-probe" },
458
+ { type: "artifact", ref: "codex-hook-trust-state" },
459
+ ];
460
+ const semantics = codexInstallModeSemantics(input.hookInstallMode);
461
+ if (input.hookInstallMode === "plugin_scoped" || semantics.classification === "unsupported")
462
+ reasons.push(semantics.reason);
463
+ if (!input.probe.satisfies_minimum_version)
464
+ reasons.push(input.probe.reason);
465
+ if (!input.actualTrustState || !trustStateMatches(input.expectedTrustState, input.actualTrustState))
466
+ reasons.push("Codex hook trust state does not match generated Intent DNA hook identity");
467
+ if (input.hookInstallMode === "managed")
468
+ reasons.push("Managed Codex hook policy proof is not implemented in this source-level slice");
469
+ if (!input.runtimeExecutionVerified)
470
+ reasons.push("Codex hook execution has not been verified by runtime evidence");
471
+ if (!input.provenanceComplete)
472
+ reasons.push("RuntimeDecisionEvent provenance is incomplete");
473
+ const trusted = reasons.length === 0 && input.hookInstallMode === "project_fallback";
474
+ return {
475
+ trusted,
476
+ support_status: trusted ? "supported" : semantics.support_status,
477
+ classification: trusted ? "enterprise_evidence" : semantics.classification,
478
+ evidence_refs: evidenceRefs,
479
+ reasons: reasons.length > 0 ? reasons : ["Codex hook trust verified with runtime execution evidence"],
480
+ };
481
+ }
482
+ export function compileCodexAdapterArtifacts(ir, options) {
483
+ const dispatcherScript = options.dispatcherCommand ? options.dispatcherCommand : resolveDispatcherScript(options);
484
+ const dispatcherCommand = buildCodexDispatcherCommand(options);
485
+ const pluginHooksPath = options.pluginHooksPath ?? "./hooks/hooks.json";
486
+ const instructionPath = options.instructionPath ?? "AGENTS.intentdna.md";
487
+ const policyMapPath = options.policyMapPath ?? ".dna/compiled/codex-policy-map.json";
488
+ const fallbackHooksPath = options.fallbackHooksPath ?? ".codex/hooks.json";
489
+ const fallbackConfigPath = options.fallbackConfigPath ?? ".codex/config.toml";
490
+ const hookInstallMode = options.hookInstallMode ?? "project_fallback";
491
+ const pluginHooks = compileCodexHooksConfigForCommand(dispatcherCommand, hookInstallMode);
492
+ const fallbackHooks = compileCodexHooksConfigForCommand(dispatcherCommand, hookInstallMode);
493
+ const instruction = compileCodexInstructionArtifact(ir, { ...options, instructionPath });
494
+ const policyMap = compileCodexPolicyMapArtifact(ir, { ...options, instructionPath, policyMapPath });
495
+ const trustState = buildCodexHookTrustState(fallbackHooksPath, fallbackHooks);
496
+ const unsupported = unsupportedCodexCapabilities(ir);
497
+ const generatedAt = options.generatedAt ?? new Date().toISOString();
498
+ return {
499
+ pluginManifest: compileCodexPluginManifest({ ...options, pluginHooksPath }),
500
+ pluginHooks,
501
+ fallbackHooks,
502
+ instruction,
503
+ policyMap,
504
+ trustState,
505
+ evidence: {
506
+ schema_version: "intentdna.codex_adapter_evidence.v1",
507
+ generated_at: generatedAt,
508
+ adapter: "codex_cli",
509
+ minimum_codex_version: CODEX_ADAPTER_MINIMUM_VERSION,
510
+ hook_install_mode: hookInstallMode,
511
+ install_mode_semantics: codexInstallModeSemantics(hookInstallMode),
512
+ dispatcher_command: dispatcherCommand,
513
+ dispatcher_script: dispatcherScript,
514
+ plugin_hooks_path: pluginHooksPath,
515
+ instruction_path: instructionPath,
516
+ policy_map_path: policyMapPath,
517
+ fallback_hooks_path: fallbackHooksPath,
518
+ fallback_config_path: fallbackConfigPath,
519
+ generated_events: [...CODEX_NATIVE_HOOK_EVENTS],
520
+ trust_state: trustState,
521
+ support_status: codexSupportStatus(ir),
522
+ unsupported,
523
+ notes: [
524
+ "Plugin-scoped Codex hooks are a distribution surface and require Codex trust review before enforcement.",
525
+ "Fallback hook trust hashes are generated for .codex/config.toml hooks.state evidence, not embedded in hooks.json.",
526
+ "RuntimeDecisionEvent is primary runtime evidence and must be written through appendRuntimeDecisionEvent; this file is adapter/config evidence only.",
527
+ ],
528
+ },
529
+ };
530
+ }
531
+ function isRecord(value) {
532
+ return typeof value === "object" && value !== null && !Array.isArray(value);
533
+ }
534
+ function isIntentDnaOwnedHook(hook, dispatcherCommand) {
535
+ const commandMatches = dispatcherCommand === undefined ||
536
+ hookCommandStartsWith(hook, `${dispatcherCommand} CodexHook --codex-event`) ||
537
+ hookCommandIncludesCodexWrapper(hook);
538
+ return isRecord(hook) &&
539
+ hook.type === "command" &&
540
+ hook.statusMessage === OWNED_HOOK_STATUS_MESSAGE &&
541
+ commandMatches;
542
+ }
543
+ function hookCommandStartsWith(hook, prefix) {
544
+ return isRecord(hook) && typeof hook.command === "string" && hook.command.startsWith(prefix);
545
+ }
546
+ function hookCommandIncludesCodexWrapper(hook) {
547
+ return isRecord(hook) && typeof hook.command === "string" && hook.command.includes(" CodexHook --codex-event ");
548
+ }
549
+ function isIntentDnaOwnedGroup(group, dispatcherCommand) {
550
+ return isRecord(group) && Array.isArray(group.hooks) && group.hooks.some((hook) => isIntentDnaOwnedHook(hook, dispatcherCommand));
551
+ }
552
+ function isIntentDnaOwnedHooksConfig(value) {
553
+ if (!isRecord(value) || !isRecord(value.hooks))
554
+ return false;
555
+ const groupsByEvent = Object.values(value.hooks);
556
+ if (groupsByEvent.length === 0)
557
+ return false;
558
+ return groupsByEvent.every((groups) => Array.isArray(groups) && groups.every((group) => isRecord(group) && Array.isArray(group.hooks) && group.hooks.length > 0 && group.hooks.every((hook) => isIntentDnaOwnedHook(hook))));
559
+ }
560
+ export function mergeCodexFallbackHooks(existingContent, generated, dispatcherCommand) {
561
+ const existingRoot = parseCodexHooksConfig(existingContent);
562
+ const nextHooks = { ...existingRoot.hooks };
563
+ for (const event of CODEX_NATIVE_HOOK_EVENTS) {
564
+ const existingGroups = Array.isArray(nextHooks[event]) ? nextHooks[event] : [];
565
+ const preserved = existingGroups.map((group) => {
566
+ if (!isRecord(group) || !Array.isArray(group.hooks))
567
+ return group;
568
+ const hooks = group.hooks.filter((hook) => !isIntentDnaOwnedHook(hook, dispatcherCommand));
569
+ if (hooks.length === 0)
570
+ return null;
571
+ return { ...group, hooks };
572
+ }).filter((group) => group !== null && !isIntentDnaOwnedGroup(group, dispatcherCommand));
573
+ nextHooks[event] = [...preserved, ...(generated.hooks[event] ?? [])];
574
+ }
575
+ return { hooks: nextHooks };
576
+ }
577
+ export function removeCodexFallbackHooks(existingContent, dispatcherCommand) {
578
+ const existingRoot = parseCodexHooksConfig(existingContent);
579
+ const nextHooks = {};
580
+ for (const [event, groups] of Object.entries(existingRoot.hooks)) {
581
+ const preserved = Array.isArray(groups)
582
+ ? groups.map((group) => {
583
+ if (!isRecord(group) || !Array.isArray(group.hooks))
584
+ return group;
585
+ const hooks = group.hooks.filter((hook) => !isIntentDnaOwnedHook(hook, dispatcherCommand));
586
+ return hooks.length > 0 ? { ...group, hooks } : null;
587
+ }).filter((group) => group !== null && !isIntentDnaOwnedGroup(group, dispatcherCommand))
588
+ : [];
589
+ if (preserved.length > 0)
590
+ nextHooks[event] = preserved;
591
+ }
592
+ if (Object.keys(nextHooks).length === 0)
593
+ return null;
594
+ return { hooks: nextHooks };
595
+ }
596
+ function parseCodexHooksConfig(content) {
597
+ if (!content)
598
+ return { hooks: {} };
599
+ const parsed = JSON.parse(content);
600
+ if (!isRecord(parsed))
601
+ throw new Error("Codex hooks config must be a JSON object");
602
+ if ("hooks" in parsed && !isRecord(parsed.hooks))
603
+ throw new Error("Codex hooks config hooks field must be an object");
604
+ const hooks = isRecord(parsed.hooks) ? parsed.hooks : {};
605
+ for (const [event, groups] of Object.entries(hooks)) {
606
+ if (!Array.isArray(groups))
607
+ throw new Error(`Codex hooks config event ${event} must be an array`);
608
+ groups.forEach((group, groupIndex) => {
609
+ if (!isRecord(group) || !Array.isArray(group.hooks))
610
+ throw new Error(`Codex hooks config event ${event}[${groupIndex}] must contain hooks array`);
611
+ group.hooks.forEach((hook, hookIndex) => {
612
+ if (!isRecord(hook) || hook.type !== "command" || typeof hook.command !== "string") {
613
+ throw new Error(`Codex hooks config event ${event}[${groupIndex}].hooks[${hookIndex}] must be a command hook`);
614
+ }
615
+ });
616
+ });
617
+ }
618
+ return { hooks: hooks };
619
+ }
620
+ export function codexTrustStateToml(trustState) {
621
+ return Object.entries(trustState)
622
+ .sort(([left], [right]) => left.localeCompare(right))
623
+ .flatMap(([key, hookState]) => [
624
+ `[hooks.state."${escapeTomlString(key)}"]`,
625
+ `trusted_hash = "${escapeTomlString(hookState.trusted_hash)}"`,
626
+ "",
627
+ ])
628
+ .join("\n")
629
+ .trimEnd();
630
+ }
631
+ function escapeTomlString(value) {
632
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
633
+ }
634
+ export function mergeCodexTrustStateConfig(existingContent, trustState) {
635
+ const generated = codexTrustStateToml(trustState);
636
+ const preserved = stripIntentDnaTrustStateBlock(existingContent ?? "").trimEnd();
637
+ if (!generated)
638
+ return preserved ? `${preserved}\n` : "";
639
+ const block = [TRUST_STATE_START, generated, TRUST_STATE_END].join("\n");
640
+ return preserved ? `${preserved}\n\n${block}\n` : `${block}\n`;
641
+ }
642
+ export function removeCodexTrustStateConfig(existingContent) {
643
+ const preserved = stripIntentDnaTrustStateBlock(existingContent ?? "").trimEnd();
644
+ return preserved ? `${preserved}\n` : "";
645
+ }
646
+ function hasIntentDnaTrustStateBlock(content) {
647
+ return content.includes(TRUST_STATE_START) && content.includes(TRUST_STATE_END);
648
+ }
649
+ function stripIntentDnaTrustStateBlock(content) {
650
+ const pattern = new RegExp(`${escapeRegExp(TRUST_STATE_START)}[\\s\\S]*?${escapeRegExp(TRUST_STATE_END)}\\n?`, "g");
651
+ return content.replace(pattern, "");
652
+ }
653
+ function escapeRegExp(value) {
654
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
655
+ }
656
+ function projectPath(projectDir, configuredPath) {
657
+ const root = resolve(projectDir);
658
+ const candidate = resolve(root, configuredPath);
659
+ const relativePath = relative(root, candidate);
660
+ if (relativePath === "" || relativePath.startsWith("..") || relativePath.includes(`..${sep}`)) {
661
+ throw new Error(`Codex adapter path escapes project directory: ${configuredPath}`);
662
+ }
663
+ return candidate;
664
+ }
665
+ function codexArtifactPaths(projectDir, artifacts) {
666
+ return {
667
+ pluginManifest: join(projectDir, "codex-plugin", ".codex-plugin", "plugin.json"),
668
+ pluginHooks: join(projectDir, "codex-plugin", "hooks", "hooks.json"),
669
+ instruction: projectPath(projectDir, artifacts?.instruction.path ?? "AGENTS.intentdna.md"),
670
+ policyMap: projectPath(projectDir, artifacts?.policyMap.path ?? ".dna/compiled/codex-policy-map.json"),
671
+ adapterEvidence: join(projectDir, ".dna", "compiled", "codex-adapter-evidence.json"),
672
+ fallbackHooks: projectPath(projectDir, artifacts?.evidence.fallback_hooks_path ?? ".codex/hooks.json"),
673
+ fallbackConfig: projectPath(projectDir, artifacts?.evidence.fallback_config_path ?? ".codex/config.toml"),
674
+ };
675
+ }
676
+ async function assertWritableCodexPluginArtifact(path, validateExisting) {
677
+ const existing = await readFile(path, "utf-8").catch(() => null);
678
+ if (existing === null)
679
+ return;
680
+ const parsed = JSON.parse(existing);
681
+ if (!validateExisting(parsed))
682
+ throw new Error(`Refusing to overwrite non-Intent DNA Codex plugin artifact: ${path}`);
683
+ }
684
+ async function assertWritableOwnedTextArtifact(path, startMarker, errorLabel) {
685
+ const existing = await readFile(path, "utf-8").catch(() => null);
686
+ if (existing === null)
687
+ return;
688
+ if (!existing.includes(startMarker))
689
+ throw new Error(`Refusing to overwrite non-Intent DNA ${errorLabel}: ${path}`);
690
+ }
691
+ async function assertWritableOwnedJsonArtifact(path, schemaVersion, errorLabel) {
692
+ const existing = await readFile(path, "utf-8").catch(() => null);
693
+ if (existing === null)
694
+ return;
695
+ const parsed = JSON.parse(existing);
696
+ if (!isRecord(parsed) || parsed.schema_version !== schemaVersion)
697
+ throw new Error(`Refusing to overwrite non-Intent DNA ${errorLabel}: ${path}`);
698
+ }
699
+ async function unlinkOwnedCodexPluginArtifact(path, validateExisting, removed) {
700
+ const existing = await readFile(path, "utf-8").catch(() => null);
701
+ if (existing === null)
702
+ return;
703
+ const parsed = JSON.parse(existing);
704
+ if (!validateExisting(parsed))
705
+ return;
706
+ await unlink(path);
707
+ removed.push(path);
708
+ }
709
+ async function unlinkOwnedTextArtifact(path, marker, removed) {
710
+ const existing = await readFile(path, "utf-8").catch(() => null);
711
+ if (existing === null || !existing.includes(marker))
712
+ return;
713
+ await unlink(path);
714
+ removed.push(path);
715
+ }
716
+ async function unlinkOwnedJsonArtifact(path, schemaVersion, removed) {
717
+ const existing = await readFile(path, "utf-8").catch(() => null);
718
+ if (existing === null)
719
+ return;
720
+ const parsed = JSON.parse(existing);
721
+ if (!isRecord(parsed) || parsed.schema_version !== schemaVersion)
722
+ return;
723
+ await unlink(path);
724
+ removed.push(path);
725
+ }
726
+ export async function writeCodexAdapterArtifacts(projectDir, artifacts) {
727
+ const paths = codexArtifactPaths(projectDir, artifacts);
728
+ let evidence = artifacts.evidence;
729
+ let mergedHooks = null;
730
+ let finalTrustState = null;
731
+ let existingConfig = null;
732
+ await assertWritableOwnedTextArtifact(paths.instruction, "intentdna:codex-instructions:start", "Codex instruction artifact");
733
+ await assertWritableOwnedJsonArtifact(paths.policyMap, "intentdna.codex_policy_map.v1", "Codex policy map artifact");
734
+ await assertWritableOwnedJsonArtifact(paths.adapterEvidence, "intentdna.codex_adapter_evidence.v1", "Codex adapter evidence artifact");
735
+ if (artifacts.evidence.install_mode_semantics.writes_plugin_artifacts) {
736
+ await assertWritableCodexPluginArtifact(paths.pluginManifest, (value) => isRecord(value) && value.name === OWNED_PLUGIN_NAME);
737
+ await assertWritableCodexPluginArtifact(paths.pluginHooks, isIntentDnaOwnedHooksConfig);
738
+ }
739
+ if (artifacts.evidence.install_mode_semantics.writes_project_fallback) {
740
+ const existingHooks = await readFile(paths.fallbackHooks, "utf-8").catch(() => null);
741
+ mergedHooks = mergeCodexFallbackHooks(existingHooks, artifacts.fallbackHooks, artifacts.evidence.dispatcher_command);
742
+ finalTrustState = buildCodexHookTrustState(artifacts.evidence.fallback_hooks_path, mergedHooks);
743
+ existingConfig = await readFile(paths.fallbackConfig, "utf-8").catch(() => null);
744
+ mergeCodexTrustStateConfig(existingConfig, finalTrustState);
745
+ evidence = { ...evidence, trust_state: finalTrustState };
746
+ }
747
+ const written = [];
748
+ await mkdir(dirname(paths.instruction), { recursive: true });
749
+ await assertWritableOwnedTextArtifact(paths.instruction, "intentdna:codex-instructions:start", "Codex instruction artifact");
750
+ await atomicWrite(paths.instruction, artifacts.instruction.content);
751
+ written.push(paths.instruction);
752
+ await mkdir(dirname(paths.policyMap), { recursive: true });
753
+ await assertWritableOwnedJsonArtifact(paths.policyMap, "intentdna.codex_policy_map.v1", "Codex policy map artifact");
754
+ await atomicWrite(paths.policyMap, JSON.stringify(artifacts.policyMap.content, null, 2) + "\n");
755
+ written.push(paths.policyMap);
756
+ if (artifacts.evidence.install_mode_semantics.writes_project_fallback) {
757
+ await mkdir(dirname(paths.fallbackHooks), { recursive: true });
758
+ await atomicWrite(paths.fallbackHooks, JSON.stringify(mergedHooks, null, 2) + "\n");
759
+ written.push(paths.fallbackHooks);
760
+ await mkdir(dirname(paths.fallbackConfig), { recursive: true });
761
+ await atomicWrite(paths.fallbackConfig, mergeCodexTrustStateConfig(existingConfig, finalTrustState ?? {}));
762
+ written.push(paths.fallbackConfig);
763
+ }
764
+ await mkdir(dirname(paths.adapterEvidence), { recursive: true });
765
+ await atomicWrite(paths.adapterEvidence, JSON.stringify(evidence, null, 2) + "\n");
766
+ written.push(paths.adapterEvidence);
767
+ if (artifacts.evidence.install_mode_semantics.writes_plugin_artifacts) {
768
+ await mkdir(dirname(paths.pluginManifest), { recursive: true });
769
+ await assertWritableCodexPluginArtifact(paths.pluginManifest, (value) => isRecord(value) && value.name === OWNED_PLUGIN_NAME);
770
+ await atomicWrite(paths.pluginManifest, JSON.stringify(artifacts.pluginManifest, null, 2) + "\n");
771
+ written.push(paths.pluginManifest);
772
+ await mkdir(dirname(paths.pluginHooks), { recursive: true });
773
+ await assertWritableCodexPluginArtifact(paths.pluginHooks, isIntentDnaOwnedHooksConfig);
774
+ await atomicWrite(paths.pluginHooks, JSON.stringify(artifacts.pluginHooks, null, 2) + "\n");
775
+ written.push(paths.pluginHooks);
776
+ }
777
+ return written;
778
+ }
779
+ export async function removeCodexAdapterArtifacts(projectDir, dispatcherCommand, artifacts) {
780
+ const paths = codexArtifactPaths(projectDir, artifacts);
781
+ const removed = [];
782
+ await unlinkOwnedCodexPluginArtifact(paths.pluginManifest, (value) => isRecord(value) && value.name === OWNED_PLUGIN_NAME, removed);
783
+ await unlinkOwnedCodexPluginArtifact(paths.pluginHooks, isIntentDnaOwnedHooksConfig, removed);
784
+ await unlinkOwnedTextArtifact(paths.instruction, "intentdna:codex-instructions:start", removed);
785
+ await unlinkOwnedJsonArtifact(paths.policyMap, "intentdna.codex_policy_map.v1", removed);
786
+ await unlinkOwnedJsonArtifact(paths.adapterEvidence, "intentdna.codex_adapter_evidence.v1", removed);
787
+ const fallbackContent = await readFile(paths.fallbackHooks, "utf-8").catch(() => null);
788
+ if (fallbackContent !== null) {
789
+ const existingRoot = parseCodexHooksConfig(fallbackContent);
790
+ const hasOwnedHooks = Object.values(existingRoot.hooks).some((groups) => Array.isArray(groups) && groups.some((group) => isIntentDnaOwnedGroup(group, dispatcherCommand)));
791
+ if (hasOwnedHooks) {
792
+ const nextHooks = removeCodexFallbackHooks(fallbackContent, dispatcherCommand);
793
+ if (nextHooks) {
794
+ await atomicWrite(paths.fallbackHooks, JSON.stringify(nextHooks, null, 2) + "\n");
795
+ }
796
+ else {
797
+ await unlink(paths.fallbackHooks).catch(() => { });
798
+ }
799
+ removed.push(paths.fallbackHooks);
800
+ }
801
+ }
802
+ const configContent = await readFile(paths.fallbackConfig, "utf-8").catch(() => null);
803
+ if (configContent !== null && hasIntentDnaTrustStateBlock(configContent)) {
804
+ const nextConfig = removeCodexTrustStateConfig(configContent);
805
+ if (nextConfig) {
806
+ await atomicWrite(paths.fallbackConfig, nextConfig);
807
+ }
808
+ else {
809
+ await unlink(paths.fallbackConfig).catch(() => { });
810
+ }
811
+ removed.push(paths.fallbackConfig);
812
+ }
813
+ return removed;
814
+ }
815
+ export async function appendCodexRuntimeDecisionEvent(projectDir, event, sessionId) {
816
+ await appendRuntimeDecisionEvent(projectDir, event, sessionId);
817
+ }
818
+ function codexSupportStatus(ir) {
819
+ return {
820
+ prompt_directives: ir.prompt_directives.length > 0 ? "partial" : "unknown",
821
+ tool_filters: ir.tool_filters.length > 0 ? "partial" : "unknown",
822
+ pre_execution_gates: ir.pre_execution_gates.length > 0 ? "partial" : "unknown",
823
+ post_execution_validators: ir.post_execution_validators.length > 0 || ir.verifier_specs?.length ? "partial" : "unknown",
824
+ context_injections: ir.context_injections.length > 0 ? "partial" : "unknown",
825
+ role_scope: ir.role_scope ? "partial" : "unknown",
826
+ role_tool_permissions: ir.role_tool_permissions ? "partial" : "unknown",
827
+ runtime_decision_event: "partial",
828
+ permission_request: "unsupported",
829
+ subagent_lifecycle: "unsupported",
830
+ };
831
+ }
832
+ function unsupportedCodexCapabilities(ir) {
833
+ const unsupported = [
834
+ "full_claude_code_hook_parity",
835
+ "native_runtime_decision_event",
836
+ "plugin_hook_trust_without_review",
837
+ "permission_request_runtime_fixture_not_frozen",
838
+ "subagent_lifecycle_runtime_fixture_not_frozen",
839
+ ];
840
+ if (ir.tool_filters.length > 0)
841
+ unsupported.push("non_bash_tool_filter_interception");
842
+ if (ir.post_execution_validators.length > 0 || ir.verifier_specs?.some((spec) => spec.when === "post_tool_use" || spec.when === "stop")) {
843
+ unsupported.push("post_execution_side_effect_rollback");
844
+ }
845
+ return [...new Set(unsupported)];
846
+ }
847
+ function cascadeLayers(ir) {
848
+ const layers = ir.provenance?.cascade_layers;
849
+ const result = {};
850
+ for (const layer of ["species", "enterprise", "project", "personal", "role", "context", "task"]) {
851
+ const entry = layers?.[layer];
852
+ result[layer] = {
853
+ layer,
854
+ source_id: entry?.source_id ?? "unknown",
855
+ source_ref: entry?.source_ref ?? "unknown",
856
+ fingerprint: entry?.fingerprint ?? "unknown",
857
+ status: entry?.status ?? "unknown",
858
+ };
859
+ }
860
+ return result;
861
+ }
862
+ function fallbackConstraint(decision, reason, ir) {
863
+ return {
864
+ constraint_id: decision === "allow" ? "allow:no_blocking_constraint" : "unknown",
865
+ source_layer: decision === "allow" && ir.provenance?.policy_bundle_id ? "enterprise" : "unknown",
866
+ source_dna_id: decision === "allow" ? ir.provenance?.policy_bundle_id ?? "unknown" : "unknown",
867
+ gene_id: decision === "allow" ? "no_blocking_constraint" : "unknown",
868
+ codon_type: "threshold",
869
+ action: decision,
870
+ provenance_reason: reason || "unknown",
871
+ };
872
+ }
873
+ function candidateMatches(candidate, decision) {
874
+ return candidate.action === decision;
875
+ }
876
+ function winningConstraint(ir, decision, reason) {
877
+ if (decision === "allow")
878
+ return fallbackConstraint(decision, reason, ir);
879
+ const matched = ir.provenance?.constraint_winners.find((candidate) => candidateMatches(candidate, decision));
880
+ return matched ? { ...matched, action: decision } : fallbackConstraint(decision, reason, ir);
881
+ }
882
+ function enforcementPointFor(input) {
883
+ if (input.hookInstallMode === "project_fallback" && input.hookTrusted === true && input.hookEvent === "PreToolUse")
884
+ return "hook";
885
+ return "unsupported";
886
+ }
887
+ function firstString(...values) {
888
+ for (const value of values) {
889
+ if (typeof value === "string" && value.trim().length > 0)
890
+ return value;
891
+ }
892
+ return undefined;
893
+ }
894
+ function firstNumber(...values) {
895
+ for (const value of values) {
896
+ if (typeof value === "number" && Number.isFinite(value))
897
+ return value;
898
+ if (typeof value === "string" && value.trim().length > 0) {
899
+ const parsed = Number(value);
900
+ if (Number.isFinite(parsed))
901
+ return parsed;
902
+ }
903
+ }
904
+ return undefined;
905
+ }
906
+ function nestedRecord(value, key) {
907
+ if (!isRecord(value))
908
+ return undefined;
909
+ const nested = value[key];
910
+ return isRecord(nested) ? nested : undefined;
911
+ }
912
+ function normalizeEvidenceResult(value) {
913
+ if (!value)
914
+ return undefined;
915
+ const normalized = value.toLowerCase().trim();
916
+ if (normalized === "success" || normalized === "succeeded" || normalized === "pass" || normalized === "passed" || normalized === "ok")
917
+ return "success";
918
+ if (normalized === "failure" || normalized === "failed" || normalized === "fail" || normalized === "error")
919
+ return "failure";
920
+ if (normalized === "blocked" || normalized === "block")
921
+ return "blocked";
922
+ if (normalized === "skipped" || normalized === "skip")
923
+ return "skipped";
924
+ if (normalized === "unsupported")
925
+ return "unsupported";
926
+ if (normalized === "unknown")
927
+ return "unknown";
928
+ return undefined;
929
+ }
930
+ function normalizeEvidenceRetry(root, payload, toolInput) {
931
+ const retry = nestedRecord(root, "retry") ?? nestedRecord(payload, "retry") ?? nestedRecord(toolInput, "retry");
932
+ const attempt = firstNumber(retry?.attempt, retry?.retry_attempt, root.retry_attempt, root.retryAttempt, payload.retry_attempt, payload.retryAttempt, toolInput?.retry_attempt, toolInput?.retryAttempt);
933
+ const maxAttempts = firstNumber(retry?.max_attempts, retry?.maxAttempts, root.max_attempts, root.maxAttempts, root.retry_max_attempts, root.retryMaxAttempts, payload.max_attempts, payload.maxAttempts, payload.retry_max_attempts, payload.retryMaxAttempts, toolInput?.max_attempts, toolInput?.maxAttempts, toolInput?.retry_max_attempts, toolInput?.retryMaxAttempts);
934
+ if (attempt === undefined || maxAttempts === undefined)
935
+ return undefined;
936
+ const reason = firstString(retry?.reason, retry?.retry_reason, root.retry_reason, root.retryReason, payload.retry_reason, payload.retryReason, toolInput?.retry_reason, toolInput?.retryReason) ?? "retry observed";
937
+ return { attempt, max_attempts: maxAttempts, reason };
938
+ }
939
+ function normalizeCodexEvent(value) {
940
+ if (!value)
941
+ return "unsupported";
942
+ if (CODEX_NATIVE_HOOK_EVENTS.includes(value) || value === "PermissionRequest")
943
+ return value;
944
+ const compact = value.replace(/[_-](\w)/g, (_match, char) => char.toUpperCase());
945
+ const normalized = compact.charAt(0).toUpperCase() + compact.slice(1);
946
+ if (CODEX_NATIVE_HOOK_EVENTS.includes(normalized) || normalized === "PermissionRequest")
947
+ return normalized;
948
+ return value;
949
+ }
950
+ export function normalizeCodexHookInput(rawInput, fallbackEvent) {
951
+ const root = isRecord(rawInput) ? rawInput : {};
952
+ const payload = isRecord(root.payload) ? root.payload : root;
953
+ const tool = nestedRecord(payload, "tool") ?? nestedRecord(root, "tool");
954
+ const toolInput = nestedRecord(payload, "tool_input") ?? nestedRecord(payload, "toolInput") ?? nestedRecord(payload, "input") ?? nestedRecord(root, "tool_input");
955
+ const event = normalizeCodexEvent(firstString(root.event, root.event_name, root.hook_event_name, root.hookEventName, payload.event, payload.event_name, payload.hook_event_name, fallbackEvent));
956
+ const command = firstString(root.command, payload.command, toolInput?.command, toolInput?.cmd, tool?.command);
957
+ const toolName = firstString(root.tool_name, root.toolName, payload.tool_name, payload.toolName, tool?.name, command ? "Bash" : undefined);
958
+ return {
959
+ event,
960
+ session_id: firstString(root.session_id, root.sessionId, payload.session_id, payload.sessionId),
961
+ run_id: firstString(root.run_id, root.runId, payload.run_id, payload.runId),
962
+ agent_id: firstString(root.agent_id, root.agentId, payload.agent_id, payload.agentId),
963
+ agent_role: firstString(root.agent_role, root.agentRole, payload.agent_role, payload.agentRole),
964
+ agent_type: firstString(root.agent_type, root.agentType, payload.agent_type, payload.agentType),
965
+ subagent_id: firstString(root.subagent_id, root.subagentId, payload.subagent_id, payload.subagentId),
966
+ tool_name: toolName,
967
+ command,
968
+ cwd: firstString(root.cwd, payload.cwd),
969
+ resource_ref: firstString(root.resource_ref, root.resourceRef, payload.resource_ref, payload.resourceRef, command),
970
+ result: normalizeEvidenceResult(firstString(root.result, root.status, root.outcome, payload.result, payload.status, payload.outcome, toolInput?.result, toolInput?.status)),
971
+ exit_code: firstNumber(root.exit_code, root.exitCode, payload.exit_code, payload.exitCode, toolInput?.exit_code, toolInput?.exitCode),
972
+ duration_ms: firstNumber(root.duration_ms, root.durationMs, payload.duration_ms, payload.durationMs, toolInput?.duration_ms, toolInput?.durationMs),
973
+ retry: normalizeEvidenceRetry(root, payload, toolInput),
974
+ payload,
975
+ };
976
+ }
977
+ function textMatchesCondition(text, condition) {
978
+ const normalizedText = text.toLowerCase();
979
+ const normalizedCondition = condition.toLowerCase().trim();
980
+ if (!normalizedCondition)
981
+ return false;
982
+ if (normalizedText.includes(normalizedCondition))
983
+ return true;
984
+ if (normalizedCondition.includes("dangerous command")) {
985
+ return /\b(rm\s+-rf|git\s+reset\s+--hard|git\s+clean\s+-f|drop\s+table|shutdown|reboot)\b/.test(normalizedText);
986
+ }
987
+ return normalizedCondition.split(/\s+/).filter(Boolean).every((token) => normalizedText.includes(token));
988
+ }
989
+ function matchingPreExecutionGate(ir, input) {
990
+ const subject = [input.tool_name, input.command, input.resource_ref, JSON.stringify(input.payload)].filter(Boolean).join(" ");
991
+ return ir.pre_execution_gates.find((gate) => textMatchesCondition(subject, gate.condition));
992
+ }
993
+ function codexHookOutputForGate(gate) {
994
+ if (gate.action === "block") {
995
+ return { output: { continue: false, decision: "block", reason: gate.message }, decision: "block", reason: gate.message };
996
+ }
997
+ if (gate.action === "escalate") {
998
+ return {
999
+ output: { continue: true, hookSpecificOutput: { permissionDecision: "ask", additionalContext: gate.message } },
1000
+ decision: "escalate",
1001
+ reason: gate.message,
1002
+ };
1003
+ }
1004
+ return {
1005
+ output: { continue: true, hookSpecificOutput: { additionalContext: `WARN: ${gate.message}` } },
1006
+ decision: "warn",
1007
+ reason: gate.message,
1008
+ };
1009
+ }
1010
+ function codexDecisionForInput(ir, input) {
1011
+ if (input.event === "PreToolUse" || input.event === "PermissionRequest") {
1012
+ const gate = matchingPreExecutionGate(ir, input);
1013
+ if (gate)
1014
+ return codexHookOutputForGate(gate);
1015
+ return { output: { continue: true, suppressOutput: true }, decision: "allow", reason: "No Codex pre-execution gate matched" };
1016
+ }
1017
+ if (input.event === "PostToolUse" || input.event === "Stop") {
1018
+ const hasValidators = ir.post_execution_validators.length > 0 || (ir.verifier_specs?.length ?? 0) > 0;
1019
+ if (!hasValidators)
1020
+ return { output: { continue: true, suppressOutput: true }, decision: "allow", reason: "No Codex post-execution validator configured" };
1021
+ return {
1022
+ output: input.event === "Stop" ? { continue: true, systemMessage: "Intent DNA Codex validation evidence recorded" } : { continue: true, suppressOutput: true },
1023
+ decision: "validate",
1024
+ reason: "Codex post-execution validation evidence recorded",
1025
+ };
1026
+ }
1027
+ return { output: { continue: true, suppressOutput: true }, decision: "allow", reason: `Codex event ${input.event} is unsupported by this runner slice` };
1028
+ }
1029
+ function runtimeEventClassification(event) {
1030
+ const validation = validateRuntimeDecisionEvent(event);
1031
+ return validation.classification === "enterprise_evidence" ? "enterprise_evidence" : "diagnostic_only";
1032
+ }
1033
+ function codexEvidenceResult(input, decision) {
1034
+ if (input.result)
1035
+ return input.result;
1036
+ if (input.exit_code !== undefined)
1037
+ return input.exit_code === 0 ? "success" : "failure";
1038
+ if (decision === "block" || decision === "escalate")
1039
+ return "blocked";
1040
+ if (input.event === "unsupported")
1041
+ return "unsupported";
1042
+ return "success";
1043
+ }
1044
+ function codexEvidenceFact(input, decision) {
1045
+ if (decision === "escalate")
1046
+ return "manual_intervention";
1047
+ if (input.retry)
1048
+ return "retry";
1049
+ if (input.event === "PostToolUse" || input.event === "Stop" || input.result || input.exit_code !== undefined)
1050
+ return "result";
1051
+ if (input.command)
1052
+ return "command";
1053
+ if (decision === "block")
1054
+ return "blocker";
1055
+ return "result";
1056
+ }
1057
+ function buildCodexEvidenceCaptureEvent(params) {
1058
+ const fact = codexEvidenceFact(params.input, params.decision);
1059
+ const command = params.input.command ?? params.input.resource_ref;
1060
+ const result = codexEvidenceResult(params.input, params.decision);
1061
+ const toolLabel = params.input.tool_name ?? "unknown tool";
1062
+ const subject = command ? `${toolLabel}: ${command}` : `${toolLabel} ${params.input.event}`;
1063
+ const summary = fact === "manual_intervention"
1064
+ ? `Codex hook requested human intervention for ${subject}: ${params.reason}`
1065
+ : fact === "retry" && params.input.retry
1066
+ ? `Codex hook observed retry ${params.input.retry.attempt}/${params.input.retry.max_attempts} for ${subject}: ${params.input.retry.reason}`
1067
+ : `Codex hook observed ${fact} for ${subject}: ${result}`;
1068
+ const attribution = trustedEvidenceCaptureAttribution(params.ir, {
1069
+ workflowAsset: params.workflowAsset,
1070
+ workflowId: params.workflowId,
1071
+ stepId: params.runtimeEvent.step_id,
1072
+ });
1073
+ return {
1074
+ schema_version: EVIDENCE_CAPTURE_SCHEMA_VERSION,
1075
+ event_id: `c5:${params.runtimeEvent.event_id}`,
1076
+ captured_at: params.runtimeEvent.timestamp,
1077
+ project_root: params.projectDir,
1078
+ source: "dna_hook",
1079
+ workflow_asset: attribution.workflowAsset,
1080
+ workflow_id: attribution.workflowId,
1081
+ run_id: params.runtimeEvent.run_id || "unknown",
1082
+ step_id: attribution.stepId,
1083
+ fact,
1084
+ summary,
1085
+ command,
1086
+ result,
1087
+ exit_code: params.input.exit_code,
1088
+ duration_ms: params.input.duration_ms,
1089
+ retry: params.input.retry,
1090
+ manual_intervention: fact === "manual_intervention"
1091
+ ? { required: true, reason: params.reason, actor: "human" }
1092
+ : undefined,
1093
+ evidence_refs: [
1094
+ { type: "runtime_decision_event", ref: params.runtimeEvent.event_id },
1095
+ ...params.runtimeEvent.evidence_refs.map((ref) => ({ type: ref.type, ref: ref.ref })),
1096
+ ],
1097
+ redaction: "intentdna.c5.codex_hook_secret_redaction.v1",
1098
+ };
1099
+ }
1100
+ function projectCodexHookReference(kind, value) {
1101
+ const material = value && value.trim().length > 0 ? value : "unknown";
1102
+ const digest = createHash("sha256").update(material).digest("hex");
1103
+ return `codex-hook-${kind}:sha256:${digest}`;
1104
+ }
1105
+ export async function runCodexHook(rawInput, options) {
1106
+ const input = normalizeCodexHookInput(rawInput);
1107
+ const decision = codexDecisionForInput(options.ir, input);
1108
+ const sessionId = options.sessionId ?? input.session_id;
1109
+ const runId = options.runId ?? input.run_id ?? sessionId;
1110
+ const rawReference = input.resource_ref ?? input.command;
1111
+ const projectedResourceRef = projectCodexHookReference("resource", rawReference);
1112
+ const projectedEvidenceRef = projectCodexHookReference("evidence", rawReference ?? `codex-hook:${input.event}`);
1113
+ const event = mapCodexDecisionEvent({
1114
+ ir: options.ir,
1115
+ decision: decision.decision,
1116
+ decisionReason: decision.reason,
1117
+ hookEvent: input.event,
1118
+ hookInstallMode: options.hookInstallMode,
1119
+ hookTrusted: options.hookTrusted,
1120
+ codexVersion: options.codexVersion,
1121
+ sessionId,
1122
+ runId,
1123
+ stepId: options.stepId,
1124
+ toolName: input.tool_name,
1125
+ actionKind: input.event,
1126
+ resourceRef: projectedResourceRef,
1127
+ agentId: input.agent_id,
1128
+ agentRole: input.agent_role,
1129
+ agentType: input.agent_type,
1130
+ sessionAgent: input.subagent_id,
1131
+ orgId: options.orgId,
1132
+ teamId: options.teamId,
1133
+ userId: options.userId,
1134
+ projectId: options.projectId ?? options.projectDir,
1135
+ evidenceRefs: [{ type: "artifact", ref: projectedEvidenceRef }],
1136
+ timestamp: options.timestamp,
1137
+ });
1138
+ const classification = runtimeEventClassification(event);
1139
+ const reasons = classification === "enterprise_evidence"
1140
+ ? [decision.reason]
1141
+ : [decision.reason, "RuntimeDecisionEvent validation kept this event diagnostic-only"];
1142
+ if (options.appendEvent !== false) {
1143
+ await appendCodexRuntimeDecisionEvent(options.projectDir, event, sessionId);
1144
+ await appendEvidenceCaptureEvent(options.projectDir, buildCodexEvidenceCaptureEvent({
1145
+ projectDir: options.projectDir,
1146
+ input,
1147
+ decision: decision.decision,
1148
+ reason: decision.reason,
1149
+ runtimeEvent: event,
1150
+ workflowAsset: options.workflowAsset,
1151
+ workflowId: options.workflowId,
1152
+ ir: options.ir,
1153
+ }));
1154
+ }
1155
+ return { output: decision.output, decision: decision.decision, classification, event, reasons };
1156
+ }
1157
+ export function mapCodexDecisionEvent(input) {
1158
+ const runtime = `codex_cli@${input.codexVersion ?? "unknown"}`;
1159
+ const agentId = input.agentId ?? "unknown";
1160
+ const agentRole = input.agentRole ?? input.ir.active_role ?? "unknown";
1161
+ const agentType = input.agentType ?? "codex_cli";
1162
+ const sessionAgent = input.sessionAgent ?? input.sessionId ?? "unknown";
1163
+ const evidenceRefs = input.evidenceRefs && input.evidenceRefs.length > 0
1164
+ ? input.evidenceRefs
1165
+ : [{ type: input.hookTrusted ? "artifact" : "unknown", ref: input.hookTrusted ? "codex-hook-execution" : "unknown" }];
1166
+ return {
1167
+ schema_version: RUNTIME_DECISION_EVENT_SCHEMA_VERSION,
1168
+ event_id: input.eventId ?? randomUUID(),
1169
+ timestamp: input.timestamp ?? new Date().toISOString(),
1170
+ org_id: input.orgId ?? "unknown",
1171
+ team_id: input.teamId ?? "unknown",
1172
+ user_id: input.userId ?? "unknown",
1173
+ project_id: input.projectId ?? "unknown",
1174
+ agent_id: agentId,
1175
+ agent_role: agentRole,
1176
+ agent_type: agentType,
1177
+ session_agent: sessionAgent,
1178
+ harness_adapter: "codex_cli",
1179
+ runtime,
1180
+ policy_bundle_id: input.ir.provenance?.policy_bundle_id ?? "unknown",
1181
+ policy_bundle_version: input.ir.provenance?.policy_bundle_version ?? "unknown",
1182
+ cascade_layers: cascadeLayers(input.ir),
1183
+ winning_constraint: winningConstraint(input.ir, input.decision, input.decisionReason),
1184
+ compiled_ir_hash: input.ir.compiled_ir_hash ?? input.ir.provenance?.compiled_ir_hash ?? "unknown",
1185
+ decision: input.decision,
1186
+ decision_reason: input.decisionReason || "unknown",
1187
+ enforcement_point: enforcementPointFor(input),
1188
+ harness_runtime_context: {
1189
+ agent_id: agentId,
1190
+ agent_role: agentRole,
1191
+ agent_type: agentType,
1192
+ session_agent: sessionAgent,
1193
+ harness_adapter: "codex_cli",
1194
+ runtime,
1195
+ },
1196
+ session_id: input.sessionId ?? "unknown",
1197
+ run_id: input.runId ?? input.sessionId ?? "unknown",
1198
+ step_id: input.stepId ?? "unknown",
1199
+ tool_name: input.toolName ?? "unknown",
1200
+ action_kind: input.actionKind ?? input.hookEvent ?? "unknown",
1201
+ resource_ref: input.resourceRef ?? "unknown",
1202
+ evidence_refs: evidenceRefs,
1203
+ };
1204
+ }