From 723003321e5f206c8db72d12747428bd5cd492b4 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:31:18 +0200 Subject: [PATCH 1/6] Use persisted project trust for Codex 0.152 qualification --- tools/live_codex_qualification_codex0152.py | 94 ++++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/tools/live_codex_qualification_codex0152.py b/tools/live_codex_qualification_codex0152.py index d16a1df..1d5084f 100644 --- a/tools/live_codex_qualification_codex0152.py +++ b/tools/live_codex_qualification_codex0152.py @@ -2,6 +2,7 @@ import contextlib import json +import os import re from pathlib import Path from typing import Any, Callable, Iterator @@ -32,6 +33,81 @@ def _set_feature(text: str, key: str, value: str) -> str: return text.rstrip() + f"\n\n[features]\n{key} = {value}\n" +def _write_persisted_project_trust(home: Path, repo: Path) -> None: + """Persist the Codex 0.152 project trust decision in an isolated user config. + + Codex 0.152 project trust is a user-config setting. Passing + `projects..trust_level` through `-c` is not a valid strict CLI override + and, without strict config, can be ignored. Qualification therefore mirrors + the supported product runtime by storing trust in the disposable CODEX_HOME. + """ + + config_path = home / "config.toml" + text = config_path.read_text(encoding="utf-8") if config_path.exists() else "" + text = _set_feature(text, "hooks", "true") + project_header = f"[projects.{base.toml_quote(str(repo.resolve()))}]" + if project_header not in text: + text = text.rstrip() + f"\n\n{project_header}\ntrust_level = \"trusted\"\n" + config_path.parent.mkdir(parents=True, exist_ok=True) + config_path.write_text(text.rstrip() + "\n", encoding="utf-8") + + +def _persisted_trust_args() -> Iterator[None]: + """Route targeted 0.152 probes through persisted trust, never CLI trust.""" + + old_common = base.common_codex_args + + def common_codex_args(**kwargs: Any) -> list[str]: + kwargs["trust_project"] = False + args = old_common(**kwargs) + return [item for item in args if item != "--ignore-user-config"] + + base.common_codex_args = common_codex_args + try: + yield + finally: + base.common_codex_args = old_common + + +_persisted_trust_args = contextlib.contextmanager(_persisted_trust_args) + + +@contextlib.contextmanager +def _isolated_persisted_trust_runtime(cap_runtime: Path) -> Iterator[Path]: + """Use one disposable CODEX_HOME and trust every probed cwd explicitly.""" + + home, auth_path, auth_before = v5._prepare_isolated_codex_home( + cap_runtime / "codex0152-persisted-trust" + ) + old_common = base.common_codex_args + previous_home = os.environ.get("CODEX_HOME") + + def common_codex_args(**kwargs: Any) -> list[str]: + cwd = Path(kwargs["cwd"]).resolve() + _write_persisted_project_trust(home, cwd) + kwargs["trust_project"] = False + args = old_common(**kwargs) + return [item for item in args if item != "--ignore-user-config"] + + base.common_codex_args = common_codex_args + os.environ["CODEX_HOME"] = str(home) + try: + yield home + finally: + base.common_codex_args = old_common + if previous_home is None: + os.environ.pop("CODEX_HOME", None) + else: + os.environ["CODEX_HOME"] = previous_home + cleanup, auth_unchanged = v5._cleanup_isolated_codex_home( + home, auth_path, auth_before + ) + if not cleanup or not auth_unchanged: + raise base.QualificationError( + "Codex 0.152 persisted-trust runtime did not clean up safely" + ) + + @contextlib.contextmanager def _codex0152_compaction(cap_runtime: Path, capability_id: str) -> Iterator[None]: """Exercise real auto-compaction without the 0.152 TokenBudget fallback buffer. @@ -60,13 +136,19 @@ def set_compact_config(repo: Path, *, limit: int, scope: str) -> None: def run_c08(**kwargs: Any): cap_runtime = Path(kwargs["runtime_root"]) / "C08" - with _codex0152_compaction(cap_runtime, "C08"): + with ( + _codex0152_compaction(cap_runtime, "C08"), + _isolated_persisted_trust_runtime(cap_runtime), + ): return v4._c08_runtime(**kwargs) def run_c09(**kwargs: Any): cap_runtime = Path(kwargs["runtime_root"]) / "C09" - with _codex0152_compaction(cap_runtime, "C09"): + with ( + _codex0152_compaction(cap_runtime, "C09"), + _isolated_persisted_trust_runtime(cap_runtime), + ): return v4._c09_runtime(**kwargs) @@ -106,7 +188,7 @@ def _c13_contract(cap_runtime: Path) -> Iterator[None]: def run_c13(current_runtime: Callable[..., tuple[str, bool]], **kwargs: Any): cap_runtime = Path(kwargs["runtime_root"]) / "C13" - with _c13_contract(cap_runtime): + with _persisted_trust_args(), _c13_contract(cap_runtime): return current_runtime(**kwargs) @@ -144,7 +226,11 @@ def run_c06( _cap, cap_runtime, _spec, repo, _worktrees, results, _eval = regression._runtime_paths( root=root, runtime_root=runtime_root, capability_id=cid ) - with v2._python_bytecode_disabled(), regression._patched_v4(cap_runtime, cid): + with ( + v2._python_bytecode_disabled(), + regression._patched_v4(cap_runtime, cid), + _isolated_persisted_trust_runtime(cap_runtime), + ): base.ensure_git_repo(repo) regression.v1._install_plananvil_release(root, repo) v4._instrument_hooks(repo, event_to_script={"PreToolUse": "plan-anvil-guard.py"}) From 593e9ef0dc6979b1d6e8519d0043bc7246376adb Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:32:13 +0200 Subject: [PATCH 2/6] Persist C13 trust in isolated Codex homes --- tools/live_codex_qualification_harness_v6.py | 86 +++++++++++++++++--- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/tools/live_codex_qualification_harness_v6.py b/tools/live_codex_qualification_harness_v6.py index 23dab3e..7babd56 100644 --- a/tools/live_codex_qualification_harness_v6.py +++ b/tools/live_codex_qualification_harness_v6.py @@ -103,12 +103,43 @@ def _seed_project_fixture(repo: Path, context_proof: str, *, include_project_age handle.write(prior.C13_HOOK_LOG_RELATIVE + "\n") +def _declare_home_agent(home: Path) -> None: + config_path = home / "config.toml" + text = config_path.read_text(encoding="utf-8") if config_path.exists() else "" + header = f"[agents.{HOME_AGENT_NAME}]" + if header in text: + return + text = text.rstrip() + ( + "\n\n[agents]\n" + "enabled = true\n" + "max_concurrent_threads_per_session = 2\n" + f"\n{header}\n" + 'description = "C13 qualification child for real SubagentStart context semantics."\n' + f'config_file = "./agents/{HOME_AGENT_FILENAME}"\n' + ) + _write(config_path, text.rstrip() + "\n") + + +def _prepare_trusted_ephemeral_home( + cap_runtime: Path, + repo: Path, +) -> tuple[Path, Path | None, tuple[int, int, int] | None]: + home, auth_path, auth_before = prior._prepare_isolated_codex_home( + cap_runtime / "ephemeral" + ) + compat._write_persisted_project_trust(home, repo) + return home, auth_path, auth_before + + def _prepare_home_scoped_fallback_agent( cap_runtime: Path, + repo: Path, ) -> tuple[Path, Path | None, tuple[int, int, int] | None, bool]: home, auth_path, auth_before = prior._prepare_isolated_codex_home(cap_runtime) + compat._write_persisted_project_trust(home, repo) agent_path = home / "agents" / HOME_AGENT_FILENAME _write(agent_path, _agent_toml()) + _declare_home_agent(home) return home, auth_path, auth_before, agent_path.is_file() @@ -141,18 +172,34 @@ def _c13_runtime( _seed_project_fixture(project_repo, proof, include_project_agent=True) project_fixture_commit = base.commit_fixture_baseline(project_repo) + ephemeral_home, ephemeral_auth_path, ephemeral_auth_before = ( + _prepare_trusted_ephemeral_home(cap_runtime, project_repo) + ) + ephemeral_cleanup_verified = True + ephemeral_auth_unchanged = True prior._clear_hook_log(project_repo) before_ephemeral = base.git_snapshot(project_repo) - payload_e, events_e, error_e, known_e = prior._run_c13_codex( - repo=project_repo, - schemas=schemas, - results_dir=results_dir, - position=1, - ephemeral=True, - timeout=600, - ) - after_ephemeral = base.git_snapshot(project_repo) - records_e = prior._read_hook_records(project_repo) + try: + payload_e, events_e, error_e, known_e = prior._run_c13_codex( + repo=project_repo, + schemas=schemas, + results_dir=results_dir, + position=1, + ephemeral=True, + isolated_codex_home=ephemeral_home, + timeout=600, + ) + after_ephemeral = base.git_snapshot(project_repo) + records_e = prior._read_hook_records(project_repo) + finally: + ephemeral_cleanup_verified, ephemeral_auth_unchanged = ( + prior._cleanup_isolated_codex_home( + ephemeral_home, + ephemeral_auth_path, + ephemeral_auth_before, + ) + ) + outcome_e, trial_e = prior._evaluate_transport( transport="ephemeral", payload=payload_e, @@ -164,9 +211,21 @@ def _c13_runtime( git_before=before_ephemeral, git_after=after_ephemeral, ) + if known_e: + outcome_e = "BLOCKED" + trial_e["outcome"] = "BLOCKED" + trial_e["blocker"] = "recognized ephemeral parent-thread registration failure" + elif not (ephemeral_cleanup_verified and ephemeral_auth_unchanged): + outcome_e = "BLOCKED" + trial_e["outcome"] = "BLOCKED" + trial_e["blocker"] = "isolated ephemeral CODEX_HOME cleanup/auth invariants failed" trial_e["agent_fixture_scope"] = "project" trial_e["agent_name_matches_filename"] = True trial_e["required_spawn_agent_type"] = HOME_AGENT_NAME + trial_e["persisted_project_trust"] = True + trial_e["isolated_codex_home"] = True + trial_e["isolated_home_cleanup_verified"] = ephemeral_cleanup_verified + trial_e["auth_metadata_unchanged"] = ephemeral_auth_unchanged trials: list[dict[str, Any]] = [base.sanitize(trial_e)] fallback_used = False @@ -196,7 +255,7 @@ def _c13_runtime( fallback_fixture_commit = base.commit_fixture_baseline(fallback_repo) isolated_home, auth_path, auth_before, home_agent_materialized = ( - _prepare_home_scoped_fallback_agent(cap_runtime) + _prepare_home_scoped_fallback_agent(cap_runtime, fallback_repo) ) fallback_available = auth_path is not None and home_agent_materialized @@ -218,6 +277,7 @@ def _c13_runtime( f"{str(home_agent_materialized).lower()}", "file_backed_auth_bridge_available=" f"{str(auth_path is not None).lower()}", + "persisted_project_trust=true", f"session_cleanup_verified={str(cleanup_verified).lower()}", ], "blocker": "isolated non-ephemeral home-agent fallback preflight unavailable", @@ -274,6 +334,7 @@ def _c13_runtime( trial_n["agent_name_matches_filename"] = True trial_n["required_spawn_agent_type"] = HOME_AGENT_NAME trial_n["fallback_fixture_commit"] = fallback_fixture_commit + trial_n["persisted_project_trust"] = True trials.append(base.sanitize(trial_n)) final_outcome = outcome_n transport_resolution = "non_ephemeral_home_agent_fallback" @@ -325,6 +386,9 @@ def _c13_runtime( observations=[ f"ephemeral_outcome={outcome_e}", f"ephemeral_known_parent_thread_failure={str(known_e).lower()}", + "ephemeral_persisted_project_trust=true", + f"ephemeral_cleanup_verified={str(ephemeral_cleanup_verified).lower()}", + f"ephemeral_auth_metadata_unchanged={str(ephemeral_auth_unchanged).lower()}", f"non_ephemeral_fallback_enabled={str(ALLOW_NON_EPHEMERAL_FALLBACK).lower()}", f"non_ephemeral_fallback_used={str(fallback_used).lower()}", f"non_ephemeral_fallback_available={str(fallback_available).lower()}", From 381380fa961c96624119a91ba1d10f2990b2a4b3 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:32:49 +0200 Subject: [PATCH 3/6] Assert persisted Codex project trust in qualification --- tests/test_codex0152_product_alignment.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_codex0152_product_alignment.py b/tests/test_codex0152_product_alignment.py index 2444b3c..3ed92d1 100644 --- a/tests/test_codex0152_product_alignment.py +++ b/tests/test_codex0152_product_alignment.py @@ -31,6 +31,9 @@ def test_product_requires_exact_subagent_roles(self) -> None: self.assertIn("`SubagentStart` matcher input is the spawned `agent_type`", self.contract) self.assertIn("`plan_anvil_profiler`", self.contract) self.assertIn("`plan_anvil_reviewer`", self.contract) + product_config = (ROOT / ".codex" / "config.toml").read_text(encoding="utf-8") + self.assertIn("[agents.plan_anvil_profiler]", product_config) + self.assertIn("[agents.plan_anvil_reviewer]", product_config) def test_product_has_deterministic_mutation_postcondition(self) -> None: self.assertIn("hook enforcement is an early guard only", self.contract) @@ -50,6 +53,13 @@ def test_qualification_disables_token_budget_only_in_isolated_compaction_fixture self.assertIn('_set_feature(text, "token_budget", "false")', self.compat) self.assertIn("finally:\n v4._set_compact_config = old_set", self.compat) + def test_qualification_persists_project_trust_in_isolated_codex_home(self) -> None: + self.assertIn("_write_persisted_project_trust", self.compat) + self.assertIn('kwargs["trust_project"] = False', self.compat) + self.assertIn('item != "--ignore-user-config"', self.compat) + self.assertIn("_isolated_persisted_trust_runtime(cap_runtime)", self.compat) + self.assertIn('os.environ["CODEX_HOME"] = str(home)', self.compat) + def test_c06_qualifies_codex0152_guaranteed_hook_and_product_postcondition(self) -> None: self.assertIn("C06_SUPPORTED_HOOK", self.compat) self.assertIn('item.get("tool_name") == "Bash"', self.compat) From 146d8ef0f1cc472066482af78d44b83cd109a133 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:33:12 +0200 Subject: [PATCH 4/6] Test C13 persisted trust and fallback gating --- tests/test_live_codex_qualification_harness_v6.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/test_live_codex_qualification_harness_v6.py b/tests/test_live_codex_qualification_harness_v6.py index 15bfcc5..4b3e6dc 100644 --- a/tests/test_live_codex_qualification_harness_v6.py +++ b/tests/test_live_codex_qualification_harness_v6.py @@ -41,20 +41,31 @@ def test_agent_identity_is_aligned(self) -> None: self.assertIn("required_spawn_agent_type", self.source) self.assertIn("agent_type` exactly `fixture_agent`", self.compat) - def test_ephemeral_attempt_remains_project_scoped(self) -> None: + def test_ephemeral_attempt_remains_project_scoped_and_persistently_trusted(self) -> None: self.assertIn("_seed_project_fixture(project_repo, proof, include_project_agent=True)", self.source) + self.assertIn("_prepare_trusted_ephemeral_home(cap_runtime, project_repo)", self.source) + self.assertIn("isolated_codex_home=ephemeral_home", self.source) self.assertIn("ephemeral=True", self.source) self.assertIn('trial_e["agent_fixture_scope"] = "project"', self.source) + self.assertIn('trial_e["persisted_project_trust"] = True', self.source) + self.assertIn("ephemeral_cleanup_verified", self.source) + self.assertIn("ephemeral_auth_metadata_unchanged", self.source) def test_fallback_separates_agent_discovery_from_project_hook(self) -> None: self.assertIn("_seed_project_fixture(fallback_repo, proof, include_project_agent=False)", self.source) self.assertIn('home / "agents" / HOME_AGENT_FILENAME', self.source) + self.assertIn("compat._write_persisted_project_trust(home, repo)", self.source) + self.assertIn("_declare_home_agent(home)", self.source) + self.assertIn("_prepare_home_scoped_fallback_agent(cap_runtime, fallback_repo)", self.source) self.assertIn('trial_n["agent_fixture_scope"] = "disposable_CODEX_HOME"', self.source) self.assertIn('trial_n["project_agent_present"] = False', self.source) self.assertIn('trial_n["project_scoped_subagent_start_hook"] = True', self.source) + self.assertIn('trial_n["persisted_project_trust"] = True', self.source) self.assertIn("compat.run_c13(_c13_runtime", self.source) - def test_fallback_is_still_known_error_gated(self) -> None: + def test_known_ephemeral_parent_failure_always_uses_the_gated_fallback(self) -> None: + self.assertIn('if known_e:\n outcome_e = "BLOCKED"', self.source) + self.assertIn("recognized ephemeral parent-thread registration failure", self.source) self.assertIn("known_e and ALLOW_NON_EPHEMERAL_FALLBACK", self.source) self.assertIn("ephemeral_known_transport_blocker_fallback_not_enabled", self.source) self.assertIn("non_ephemeral_home_agent_fallback", self.source) From 143d4cf29e1e8a1e9f05d920b3a3d04c6ebab3e6 Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:33:31 +0200 Subject: [PATCH 5/6] Add persisted trust regression tests --- tests/test_codex0152_persisted_trust.py | 129 ++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 tests/test_codex0152_persisted_trust.py diff --git a/tests/test_codex0152_persisted_trust.py b/tests/test_codex0152_persisted_trust.py new file mode 100644 index 0000000..59dd6fa --- /dev/null +++ b/tests/test_codex0152_persisted_trust.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import os +from pathlib import Path +import sys +import tempfile +import unittest +from unittest import mock + + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "tools")) + +import live_codex_qualification_codex0152 as compat + + +class Codex0152PersistedTrustTests(unittest.TestCase): + def test_persisted_trust_file_is_idempotent_and_enables_hooks(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + home = root / "home" + repo = root / "repo" + repo.mkdir() + + compat._write_persisted_project_trust(home, repo) + compat._write_persisted_project_trust(home, repo) + + text = (home / "config.toml").read_text(encoding="utf-8") + header = f"[projects.{compat.base.toml_quote(str(repo.resolve()))}]" + self.assertIn("[features]", text) + self.assertIn("hooks = true", text) + self.assertEqual(text.count(header), 1) + self.assertIn('trust_level = "trusted"', text) + + def test_persisted_trust_args_remove_invalid_cli_trust_and_load_user_config(self) -> None: + original = compat.base.common_codex_args + calls: list[dict[str, object]] = [] + + def fake_common(**kwargs: object) -> list[str]: + calls.append(dict(kwargs)) + args = ["codex", "exec", "--ignore-user-config"] + if kwargs.get("trust_project"): + args += ["-c", 'projects."/tmp/repo".trust_level="trusted"'] + return args + + compat.base.common_codex_args = fake_common + try: + with compat._persisted_trust_args(): + args = compat.base.common_codex_args( + cwd=Path("/tmp/repo"), + sandbox="read-only", + schema=Path("schema.json"), + output=Path("output.json"), + trust_project=True, + ) + finally: + compat.base.common_codex_args = original + + self.assertFalse(bool(calls[-1]["trust_project"])) + self.assertNotIn("--ignore-user-config", args) + self.assertFalse(any("projects." in item for item in args)) + + def test_isolated_runtime_persists_each_probed_cwd_and_restores_environment(self) -> None: + original_common = compat.base.common_codex_args + previous_home = os.environ.get("CODEX_HOME") + + def fake_common(**kwargs: object) -> list[str]: + args = ["codex", "exec", "--ignore-user-config"] + if kwargs.get("trust_project"): + args += ["-c", 'projects."bad".trust_level="trusted"'] + return args + + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + cap_runtime = root / "runtime" + home = root / "isolated-home" + repo_a = root / "repo-a" + repo_b = root / "repo-b" + home.mkdir() + repo_a.mkdir() + repo_b.mkdir() + + compat.base.common_codex_args = fake_common + try: + with ( + mock.patch.object( + compat.v5, + "_prepare_isolated_codex_home", + return_value=(home, None, None), + ), + mock.patch.object( + compat.v5, + "_cleanup_isolated_codex_home", + return_value=(True, True), + ) as cleanup, + compat._isolated_persisted_trust_runtime(cap_runtime), + ): + self.assertEqual(os.environ.get("CODEX_HOME"), str(home)) + args_a = compat.base.common_codex_args( + cwd=repo_a, + sandbox="read-only", + schema=Path("schema.json"), + output=Path("output.json"), + trust_project=True, + ) + args_b = compat.base.common_codex_args( + cwd=repo_b, + sandbox="read-only", + schema=Path("schema.json"), + output=Path("output.json"), + trust_project=True, + ) + + cleanup.assert_called_once_with(home, None, None) + finally: + compat.base.common_codex_args = original_common + + text = (home / "config.toml").read_text(encoding="utf-8") + self.assertIn(str(repo_a.resolve()), text) + self.assertIn(str(repo_b.resolve()), text) + self.assertNotIn("--ignore-user-config", args_a) + self.assertNotIn("--ignore-user-config", args_b) + self.assertFalse(any("projects." in item for item in args_a + args_b)) + + self.assertEqual(os.environ.get("CODEX_HOME"), previous_home) + + +if __name__ == "__main__": + unittest.main() From 11b2638a94a6db5298b0dacfee14c75e84bdc09e Mon Sep 17 00:00:00 2001 From: KeyffMS <124252104+KeyffMS@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:34:04 +0200 Subject: [PATCH 6/6] Document Codex 0.152 qualification trust contract --- docs/CODEX_0152_QUALIFICATION_TRUST.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/CODEX_0152_QUALIFICATION_TRUST.md diff --git a/docs/CODEX_0152_QUALIFICATION_TRUST.md b/docs/CODEX_0152_QUALIFICATION_TRUST.md new file mode 100644 index 0000000..5c131cc --- /dev/null +++ b/docs/CODEX_0152_QUALIFICATION_TRUST.md @@ -0,0 +1,16 @@ +# Codex 0.152 qualification trust contract + +Live PlanAnvil qualification must model Codex CLI 0.152 project trust using the supported user-config path. + +For C06, C08, C09, and C13 qualification probes: + +- use a disposable, isolated `CODEX_HOME`; +- bridge authentication read-only through the existing qualification helper; +- persist `[projects.""] trust_level = "trusted"` in that isolated home's `config.toml`; +- do not pass `projects..trust_level` through `-c/--config`; +- do not use `--ignore-user-config`, because that would discard the persisted trust decision; +- clean the isolated home after the probe and verify authentication metadata was unchanged. + +This is qualification infrastructure, not a product configuration requirement. The PlanAnvil repository must not persist machine-specific trust paths. + +C13 remains ephemeral-first. A recognized Codex 0.152 parent-thread registration failure (`collab spawn failed: no thread with id`) is a transport blocker even if `SubagentStart` fired before the failure. Only that recognized blocker permits the controlled non-ephemeral isolated-home fallback.