|
18 | 18 | import sys |
19 | 19 | import tempfile |
20 | 20 | import time |
| 21 | +import tomllib |
21 | 22 | import urllib.error |
22 | 23 | import urllib.request |
23 | 24 | from collections.abc import Iterator |
|
42 | 43 | OWNER_DIRECTORY_MODE = 0o700 |
43 | 44 | METADATA_MAX_BYTES = 256 * 1024 |
44 | 45 | MANAGED_PAYLOAD_MAX_BYTES = 8 * 1024 * 1024 |
45 | | -TESTED_CODEX_VERSION = "0.144.4" |
| 46 | +TESTED_CODEX_VERSION = "0.144.5" |
46 | 47 | INSTALLER_RELEASE_TAG = f"rust-v{TESTED_CODEX_VERSION}" |
47 | 48 | INSTALLER_NAME = "install.sh" |
48 | 49 | INSTALLER_URL = ( |
|
54 | 55 | f"https://api.github.com/repos/openai/codex/releases/tags/{INSTALLER_RELEASE_TAG}" |
55 | 56 | ) |
56 | 57 | PACKAGE_CHECKSUM_ASSET = "codex-package_SHA256SUMS" |
57 | | -PACKAGE_CHECKSUM_SHA256 = "09e949a00cbcbd95d5a3d3bd6c7647e38965e944a3f7d2eb148781d3a488414a" |
| 58 | +PACKAGE_CHECKSUM_SHA256 = "406f99fddeb6cdbce180f35dfdcf17014a0bde06f7c99d0e34be88660f22ab92" |
58 | 59 | PACKAGE_ASSETS = { |
59 | 60 | "aarch64-apple-darwin": ( |
60 | 61 | "codex-package-aarch64-apple-darwin.tar.gz", |
61 | | - "312e6fa2826596fb23cc1193c30d51902b6522c36ccc43b36417590e0ebd533d", |
| 62 | + "8d1cd2d53b2070919d12c054b57485b6e08347e2666cb20932e9e95eb2aa2901", |
62 | 63 | ), |
63 | 64 | "x86_64-apple-darwin": ( |
64 | 65 | "codex-package-x86_64-apple-darwin.tar.gz", |
65 | | - "666671fbe761fe1fd57d74e725c5a306729f81f4fbefc16a01a0595e69864d41", |
| 66 | + "8322b8cf3747014b435318d2f34ee6d8f566b61360e5a770e30311cc0d90205b", |
66 | 67 | ), |
67 | 68 | "aarch64-unknown-linux-musl": ( |
68 | 69 | "codex-package-aarch64-unknown-linux-musl.tar.gz", |
69 | | - "b1bc561a5bf74f9c5767c698275ae8043cee2ce45341098048b0b0b8b4e2cfc5", |
| 70 | + "7703bbb6cbd4ba3df60c32d200bca2987691047353d3a6c825af2b8bc99f1808", |
70 | 71 | ), |
71 | 72 | "x86_64-unknown-linux-musl": ( |
72 | 73 | "codex-package-x86_64-unknown-linux-musl.tar.gz", |
73 | | - "d0a7bdb2ca821c9bb5f4cc2fb11a4ed96025db63b20d1bddf1e632361a108220", |
| 74 | + "23a7022a493c5404c50c62a4ad5655836adbee019d93c73114954d8daff20053", |
74 | 75 | ), |
75 | 76 | } |
76 | 77 | INSTALLER_MAX_BYTES = 64 * 1024 |
@@ -892,6 +893,46 @@ def load_stamp(target: Path) -> dict[str, Any] | None: |
892 | 893 | return stamp |
893 | 894 |
|
894 | 895 |
|
| 896 | +def config_base_intact(current: bytes, base: bytes) -> bool: |
| 897 | + """Return True when every top-level key the base config.toml declares is |
| 898 | + present in the current config.toml with an equal value. |
| 899 | +
|
| 900 | + config.toml is co-owned. The manager writes the setup base, but the Codex |
| 901 | + runtime persists project-trust decisions into it at launch as new |
| 902 | + ``[projects."<workspace>"]`` tables (and comparable runtime state). Those |
| 903 | + additions must not read as drift, so the managed guarantee is scoped to the |
| 904 | + base keys the manager owns rather than to an exact byte image. Any change to |
| 905 | + an owned key -- or a config.toml that no longer parses -- is still drift. |
| 906 | + AGENTS.md stays byte-exact because the runtime never writes it. |
| 907 | + """ |
| 908 | + try: |
| 909 | + current_doc = tomllib.loads(current.decode("utf-8")) |
| 910 | + base_doc = tomllib.loads(base.decode("utf-8")) |
| 911 | + except (tomllib.TOMLDecodeError, UnicodeDecodeError, ValueError): |
| 912 | + return False |
| 913 | + return all(key in current_doc and current_doc[key] == value for key, value in base_doc.items()) |
| 914 | + |
| 915 | + |
| 916 | +def _config_base_intact_on_disk(target: Path, setup_id: object) -> bool: |
| 917 | + """Read the target's config.toml and confirm the managed base is intact, |
| 918 | + tolerating Codex's runtime additions. Any read/render failure is treated as |
| 919 | + drift (conservative).""" |
| 920 | + if not isinstance(setup_id, str): |
| 921 | + return False |
| 922 | + try: |
| 923 | + current, _ = read_target_file( |
| 924 | + target, |
| 925 | + "config.toml", |
| 926 | + f"managed path {target / 'config.toml'}", |
| 927 | + owner_only=False, |
| 928 | + max_bytes=MANAGED_PAYLOAD_MAX_BYTES, |
| 929 | + ) |
| 930 | + _, rendered = render_setup(setup_id) |
| 931 | + except (SystemExit, OSError, ValueError): |
| 932 | + return False |
| 933 | + return config_base_intact(current, rendered["config.toml"]) |
| 934 | + |
| 935 | + |
895 | 936 | def inspect_target(target: Path) -> dict[str, Any]: |
896 | 937 | if not ensure_target_directory(target, create=False): |
897 | 938 | return { |
@@ -928,12 +969,17 @@ def inspect_target(target: Path) -> dict[str, Any]: |
928 | 969 | drift.append(name) |
929 | 970 | continue |
930 | 971 | owner_matches = not hasattr(os, "geteuid") or snapshot.owner == os.geteuid() |
931 | | - if ( |
932 | | - snapshot.digest != expected[name] |
933 | | - or snapshot.mode != OWNER_FILE_MODE |
934 | | - or not owner_matches |
935 | | - ): |
| 972 | + if snapshot.mode != OWNER_FILE_MODE or not owner_matches: |
936 | 973 | drift.append(name) |
| 974 | + continue |
| 975 | + if snapshot.digest == expected[name]: |
| 976 | + continue |
| 977 | + # config.toml is co-owned: the Codex runtime persists [projects.*] trust |
| 978 | + # into it at launch. Tolerate additions that leave the managed base |
| 979 | + # intact; a damaged base, or any change to AGENTS.md, is still drift. |
| 980 | + if name == "config.toml" and _config_base_intact_on_disk(target, stamp["setup_id"]): |
| 981 | + continue |
| 982 | + drift.append(name) |
937 | 983 | stamp_snapshot = snapshot_target_file(target, STAMP_NAME, owner_only=False) |
938 | 984 | if stamp_snapshot is None: |
939 | 985 | drift.append(STAMP_NAME) |
@@ -979,7 +1025,14 @@ def require_effective_clean_managed(target: Path) -> dict[str, Any]: |
979 | 1025 | owner_only=True, |
980 | 1026 | max_bytes=(METADATA_MAX_BYTES if name == STAMP_NAME else MANAGED_PAYLOAD_MAX_BYTES), |
981 | 1027 | ) |
982 | | - if actual_content != expected_content: |
| 1028 | + # config.toml is co-owned: tolerate the Codex runtime's [projects.*] |
| 1029 | + # trust additions as long as the managed base is intact. AGENTS.md and |
| 1030 | + # the stamp stay byte-exact. |
| 1031 | + if name == "config.toml": |
| 1032 | + intact = config_base_intact(actual_content, expected_content) |
| 1033 | + else: |
| 1034 | + intact = actual_content == expected_content |
| 1035 | + if not intact: |
983 | 1036 | fail( |
984 | 1037 | "managed target is not the current canonical catalog setup; " |
985 | 1038 | f"run apply --setup {setup_id} before launch" |
|
0 commit comments