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') }
       : {}),