feat(headless): fail closed on missing opencode config and record config provenance Review follow-ups for the ablation seam: - probe the resolved OPENCODE_CONFIG with sha256sum before launch so a mistyped MAKA_OPENCODE_CONFIG_PATH aborts the cell instead of silently running OpenCode's built-in default (wrong arm) - record opencodeConfigPath/opencodeConfigHash in the cell execution identity so A/B arms are distinguishable and auditable from artifacts - pin the single-variable invariant with a full-object contract test: opencode-benchmark-makaprompt.json must equal the benchmark config plus only agent.build.prompt
diff --git a/packages/headless/harbor/opencode_agent.py b/packages/headless/harbor/opencode_agent.py index 5f50b0e..b71a3ce 100644 --- a/packages/headless/harbor/opencode_agent.py +++ b/packages/headless/harbor/opencode_agent.py
@@ -208,7 +208,12 @@ base_url_env, api_key_env = provider_env_names[provider] env[base_url_env] = proxy_url env[api_key_env] = proxy_token - env["OPENCODE_CONFIG"] = self._opencode_config_path() + config_path = self._opencode_config_path() + env["OPENCODE_CONFIG"] = config_path + self._resolved_opencode_config_path = config_path + self._opencode_config_hash = await self._probe_opencode_config( + environment, config_path, env + ) env["OPENCODE_FAKE_VCS"] = "git" skills_command = self._build_register_skills_command() @@ -265,6 +270,27 @@ / "opencode-benchmark.json" ) + async def _probe_opencode_config( + self, environment: BaseEnvironment, config_path: str, env: dict[str, str] + ) -> str: + # Fail closed: harbor raises RuntimeError on non-zero exit, so a + # mistyped MAKA_OPENCODE_CONFIG_PATH aborts the cell here instead of + # silently launching OpenCode with its built-in default config (the + # wrong ablation arm). The digest goes into the execution identity so + # each cell can prove which exact config bytes it ran. + result = await self.exec_as_agent( + environment, + command=f"sha256sum {shlex.quote(config_path)}", + env=env, + ) + stdout = (getattr(result, "stdout", None) or "").strip() + digest = stdout.split()[0] if stdout else "" + if not digest: + raise ValueError( + f"Could not hash the resolved OpenCode benchmark config: {config_path}" + ) + return "sha256:" + digest + def _stop_grace_ms(self) -> int: raw = self._get_env("MAKA_OPENCODE_STOP_GRACE_MS") if raw is None: @@ -433,7 +459,7 @@ ).hexdigest() pricing_profile = self._get_env("MAKA_TRIAL_PRICING_SOURCE") or "unconfigured" reasoning_effort = self._get_env("MAKA_REASONING_EFFORT") - return { + identity = { "llmConnectionSlug": self._get_env("MAKA_LLM_CONNECTION_SLUG") or provider, "model": model, **({"reasoningEffort": reasoning_effort} if reasoning_effort else {}), @@ -441,6 +467,12 @@ "pricingProfile": pricing_profile, "agentTools": False, } + resolved_config = getattr(self, "_resolved_opencode_config_path", None) + config_hash = getattr(self, "_opencode_config_hash", None) + if resolved_config and config_hash: + identity["opencodeConfigPath"] = resolved_config + identity["opencodeConfigHash"] = config_hash + return identity def _write_execution_identity(self) -> None: self.logs_dir.mkdir(parents=True, exist_ok=True)
diff --git a/packages/headless/src/__tests__/harbor-adapter.test.ts b/packages/headless/src/__tests__/harbor-adapter.test.ts index 77d3a57..51fa46c 100644 --- a/packages/headless/src/__tests__/harbor-adapter.test.ts +++ b/packages/headless/src/__tests__/harbor-adapter.test.ts
@@ -513,11 +513,20 @@ const ablation = JSON.parse( await readFile(resolve(harborDir, 'opencode-benchmark-makaprompt.json'), 'utf8'), ); - assert.deepEqual(ablation.provider, baseline.provider); - assert.equal( - ablation.agent?.build?.prompt, - DEFAULT_HEADLESS_SYSTEM_PROMPT, - 'the ablation prompt must stay byte-identical to the headless default system prompt', + const expected = { + ...baseline, + agent: { + ...baseline.agent, + build: { + ...baseline.agent?.build, + prompt: DEFAULT_HEADLESS_SYSTEM_PROMPT, + }, + }, + }; + assert.deepEqual( + ablation, + expected, + 'the ablation config must differ from the benchmark config only by agent.build.prompt, byte-identical to the headless default system prompt', ); }); @@ -3071,6 +3080,7 @@ function pythonOpenCodeAdapterSmokeScript(root: string): string { return String.raw` import asyncio +import hashlib import json import os import sys @@ -3210,6 +3220,17 @@ identity_path = Path(tmp) / "maka-cell-execution-identity.json" assert identity_path.exists(), "sampling identity must be durable before OpenCode starts" environment.agent_commands.append((command, env or {})) + if command.startswith("sha256sum "): + # Emulate the container: /opt/maka-agent is the repo bind mount. + probe_container_path = command.split(" ", 1)[1] + host_path = Path(probe_container_path.replace("/opt/maka-agent", str(root))) + if not host_path.is_file(): + raise RuntimeError(f"sha256sum: {probe_container_path}: No such file or directory") + digest = hashlib.sha256(host_path.read_bytes()).hexdigest() + return types.SimpleNamespace( + stdout=f"{digest} {probe_container_path}\n", stderr="", return_code=0 + ) + return None agent.exec_as_agent = exec_as_agent asyncio.run(agent.install(environment)) @@ -3241,6 +3262,17 @@ assert env["ZAI_API_KEY"] == "ephemeral-proxy-token", env assert env["ZAI_BASE_URL"] == "http://host.docker.internal:43210", env assert env["OPENCODE_CONFIG"] == "/opt/maka-agent/packages/headless/harbor/opencode-benchmark.json", env + baseline_identity = json.loads( + (Path(tmp) / "maka-cell-execution-identity.json").read_text(encoding="utf-8") + ) + expected_baseline_hash = "sha256:" + hashlib.sha256( + (root / "packages" / "headless" / "harbor" / "opencode-benchmark.json").read_bytes() + ).hexdigest() + assert baseline_identity["opencodeConfigPath"] == "/opt/maka-agent/packages/headless/harbor/opencode-benchmark.json", baseline_identity + assert baseline_identity["opencodeConfigHash"] == expected_baseline_hash, baseline_identity + baseline_main_index = next(i for i, item in enumerate(environment.agent_commands) if "opencode --model=" in item[0]) + probe_command, _ = environment.agent_commands[baseline_main_index - 1] + assert probe_command == "sha256sum /opt/maka-agent/packages/headless/harbor/opencode-benchmark.json", probe_command benchmark_config = json.loads( (root / "packages" / "headless" / "harbor" / "opencode-benchmark.json").read_text(encoding="utf-8") ) @@ -3321,6 +3353,34 @@ asyncio.run(override_agent.run("hi", environment, AgentContext())) _, override_env = environment.agent_commands[-1] assert override_env["OPENCODE_CONFIG"] == "/opt/maka-agent/packages/headless/harbor/opencode-benchmark-makaprompt.json", override_env + override_probe, _ = environment.agent_commands[-2] + assert override_probe == "sha256sum /opt/maka-agent/packages/headless/harbor/opencode-benchmark-makaprompt.json", override_probe + override_identity = json.loads( + (Path(tmp) / "maka-cell-execution-identity.json").read_text(encoding="utf-8") + ) + expected_override_hash = "sha256:" + hashlib.sha256( + (root / "packages" / "headless" / "harbor" / "opencode-benchmark-makaprompt.json").read_bytes() + ).hexdigest() + assert override_identity["opencodeConfigPath"] == "/opt/maka-agent/packages/headless/harbor/opencode-benchmark-makaprompt.json", override_identity + assert override_identity["opencodeConfigHash"] == expected_override_hash, override_identity + assert override_identity["opencodeConfigHash"] != expected_baseline_hash, override_identity + missing_agent = MakaOpenCodeAgent(Path(tmp), extra_env={ + "MAKA_PROVIDER_PROXY_URL": "http://host.docker.internal:43210", + "MAKA_PROVIDER_PROXY_TOKEN": "ephemeral-proxy-token", + "MAKA_LLM_CONNECTION_SLUG": "deepseek", + "MAKA_SYSTEM_PROMPT": "", + "MAKA_OPENCODE_CONFIG_PATH": "/opt/maka-agent/packages/headless/harbor/does-not-exist.json", + }, prompt_template_path=template_path, model_name="deepseek/deepseek-v4-flash") + missing_agent.exec_as_agent = exec_as_agent + commands_before = len(environment.agent_commands) + try: + asyncio.run(missing_agent.run("hi", environment, AgentContext())) + raise AssertionError("a missing MAKA_OPENCODE_CONFIG_PATH must fail closed") + except RuntimeError as error: + assert "does-not-exist.json" in str(error), error + assert not any( + "opencode --model=" in recorded for recorded, _ in environment.agent_commands[commands_before:] + ), "OpenCode must never launch when the resolved config is absent" assert environment.uploaded_files == [], environment.uploaded_files assert 'cat --' not in command, command assert "test-zai-key" not in command, command
diff --git a/packages/headless/src/cell-output.ts b/packages/headless/src/cell-output.ts index 383b1a1..4f5fe05 100644 --- a/packages/headless/src/cell-output.ts +++ b/packages/headless/src/cell-output.ts
@@ -145,6 +145,11 @@ systemPromptMode?: HeadlessSystemPromptMode; systemPromptHash: string; pricingProfile: string; + /** Resolved OpenCode benchmark config path and content hash (opencode adapter + * cells only); lets A/B arms prove which config bytes ran. Present on + * artifacts written after config provenance was introduced. */ + opencodeConfigPath?: string; + opencodeConfigHash?: string; /** Present on artifacts written after Headless Agent tool gating was introduced. */ agentTools?: boolean; /** Exact catalog-owned product surface after host binding and run policy. */ @@ -398,6 +403,22 @@ : {}), systemPromptHash: requireString(value.systemPromptHash, 'executionIdentity.systemPromptHash'), pricingProfile: requireString(value.pricingProfile, 'executionIdentity.pricingProfile'), + ...('opencodeConfigPath' in value + ? { + opencodeConfigPath: requireString( + value.opencodeConfigPath, + 'executionIdentity.opencodeConfigPath', + ), + } + : {}), + ...('opencodeConfigHash' in value + ? { + opencodeConfigHash: requireString( + value.opencodeConfigHash, + 'executionIdentity.opencodeConfigHash', + ), + } + : {}), ...('agentTools' in value ? { agentTools: requireBoolean(value.agentTools, 'executionIdentity.agentTools') } : {}),