Skip to content

Commit b4278d4

Browse files
committed
fix: make Codex bootstrap release CI deterministic
1 parent 12a9ca4 commit b4278d4

8 files changed

Lines changed: 83 additions & 22 deletions

File tree

.github/workflows/codex-bootstrap-release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ jobs:
2525
with:
2626
python-version: '3.9'
2727

28+
- name: Install ACL support required by Linux permission-contract tests
29+
run: |
30+
set -Eeuo pipefail
31+
if ! command -v getfacl >/dev/null 2>&1; then
32+
sudo apt-get update -qq
33+
sudo apt-get install --no-install-recommends --yes acl
34+
fi
35+
2836
- name: Use pinned application Node line
2937
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
3038
with:
@@ -157,6 +165,14 @@ jobs:
157165
with:
158166
python-version: ${{ matrix.python-version }}
159167

168+
- name: Install ACL support required by Linux permission-contract tests
169+
run: |
170+
set -Eeuo pipefail
171+
if ! command -v getfacl >/dev/null 2>&1; then
172+
sudo apt-get update -qq
173+
sudo apt-get install --no-install-recommends --yes acl
174+
fi
175+
160176
- name: Validate router and complete bootstrap suite
161177
run: |
162178
set -Eeuo pipefail
@@ -178,6 +194,14 @@ jobs:
178194
with:
179195
python-version: '3.9'
180196

197+
- name: Install ACL support required by Linux permission-contract tests
198+
run: |
199+
set -Eeuo pipefail
200+
if ! command -v getfacl >/dev/null 2>&1; then
201+
sudo apt-get update -qq
202+
sudo apt-get install --no-install-recommends --yes acl
203+
fi
204+
181205
- name: Verify native arm64 release identity
182206
env:
183207
RELEASE_TAG: ${{ github.ref_name }}

bootstrap/codex/README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969

7070
必须以最终实际运行 Codex 的非 root Unix 用户执行本方案;管理员应先 `sudo -iu <USER>`,再 clone 和安装。`--home` 只用于同一用户的隔离测试,不是跨用户 provision 开关;安装器会拒绝 root、owner 不匹配、受保护系统目录后代和隐式符号链接写穿。`$CODEX_HOME` 必须是 `$HOME` 内的专用目录;新建权限为 `0700`,已有目录及其自 HOME 起的现存祖先不得由其他主体替换或写入。唯一例外是上一表所述、经过 POSIX ACL 完整验证的 root-owned Delta HOME;例外不会向 HOME 以下受管目录传播。
7171

72-
生产部署必须由维护者批准一个 immutable Git tag 或完整 commit SHA。当前 manifest 固定到 `codex-bootstrap-v0.2.2``audited_base_commit` 只是编写本方案时检查的起始树,不是可部署 revision。实际安装还必须把该 annotated tag 与 release provenance 中的完整 commit SHA 绑定;不要部署 moving branch,也不要移动既有 tag。
72+
生产部署必须由维护者批准一个 immutable Git tag 或完整 commit SHA。当前 manifest 固定到 `codex-bootstrap-v0.2.3``audited_base_commit` 只是编写本方案时检查的起始树,不是可部署 revision。实际安装还必须把该 annotated tag 与 release provenance 中的完整 commit SHA 绑定;不要部署 moving branch,也不要移动既有 tag。
7373

7474
## 四、NCSA Delta:先建立交互连接
7575

@@ -150,7 +150,7 @@ bash bootstrap/codex/remote-install.sh --help
150150
在 release tag 所在的 clean checkout 上生成一个新的、不可覆盖的目录:
151151

152152
```bash
153-
release_tag=codex-bootstrap-v0.2.2
153+
release_tag=codex-bootstrap-v0.2.3
154154
release_commit=$(git rev-parse "${release_tag}^{commit}")
155155
kit_parent="$PWD/../drclaw-release-output-private"
156156
(umask 077; mkdir "$kit_parent")
@@ -172,7 +172,7 @@ builder 只接受与 manifest、HEAD 和完整 SHA 一致的 annotated tag;拒
172172
完整搬运该目录到新服务器后,只需一条离线命令,不必先从 bundle 手动提取脚本:
173173

174174
```bash
175-
bash /path/to/drclaw-codex-bootstrap-v0.2.2-offline/install.sh --full
175+
bash /path/to/drclaw-codex-bootstrap-v0.2.3-offline/install.sh --full
176176
```
177177

178178
wrapper 会先拒绝目录中的任何额外 entry、symlink、缺失文件、owner/mode 异常或 checksum inventory 漂移,并验证每个 payload,再把同目录 bundle 交给现有远程安装器;tag object 与 peeled commit 两个身份也同时固定。Git bundle 为了保留发布身份与 commit 原始 SHA 必须携带其可达 Git 历史,因此 builder 会扫描**全部可达历史路径,以及所有可达 blob、commit 和 tag payload**,而不只检查当前 tree;允许的 community gitlink 只记录路径与 object ID,bundle/archive 都不携带其仓库内容。内部 checksum 可证明搬运完整性;抵抗“payload 与 checksum 同时被替换”仍需通过独立可信渠道保存并核对 provenance sidecar 的 SHA256。

bootstrap/codex/STATUS-2026-08-19.zh-CN.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
# Dr. Claw Codex 可移植部署 v0.2:暂停状态与完整交接
22

33
初始记录:2026-08-19(America/Chicago)
4-
最近续作:2026-08-20(America/Chicago
5-
状态:**非 GitHub 范围内的工作已经完成并完成 final-tag 验收。最终本地 release 是 `codex-bootstrap-v0.2.2`GitHub push、Release、Actions、ARM runner 和保护规则按用户要求不处理**
4+
最近续作:2026-08-25(America/New_York
5+
状态:**v0.2.2 的本地验收和 tag 保持不变。为完成 GitHub 发布闭环,当前候选是新的、不可变的 `codex-bootstrap-v0.2.3`;尚未创建或推送该 tag,也尚未宣称 GitHub Actions/Release 已完成**
66

7-
这份文件记录本轮工作的真实边界:已经做了什么、验证到了哪里、哪些方案被拒绝、现在还有哪些问题、恢复时应从哪里继续。第八节以前保留 2026-08-19 的历史暂停证据;本节以下的 2026-08-20 更新优先于其中已经解决的旧问题。它不替代正式使用文档 [`README.zh-CN.md`](README.zh-CN.md) 和应用安装文档 [`APP_INSTALL.zh-CN.md`](APP_INSTALL.zh-CN.md)
7+
这份文件记录本轮工作的真实边界:已经做了什么、验证到了哪里、哪些方案被拒绝、现在还有哪些问题、恢复时应从哪里继续。第八节以前保留 2026-08-19 的历史暂停证据;本节以下的 2026-08-20 与 2026-08-25 更新优先于其中已经解决的旧问题。它不替代正式使用文档 [`README.zh-CN.md`](README.zh-CN.md) 和应用安装文档 [`APP_INSTALL.zh-CN.md`](APP_INSTALL.zh-CN.md)
8+
9+
## 0. 2026-08-25 GitHub 发布续作(以本节为准)
10+
11+
恢复工作后先完整下载了 Delta 中的版本化项目,并在本地重新核验了以下事实:
12+
13+
- `codex-bootstrap-v0.2.2`、其 tag object 和所有历史 tag 均保持原样;本次绝不移动、删除或重用已有 tag;
14+
- 172 个 filesystem skill、catalog 和 frontmatter 重新验证为一致;v0.2.2 release-kit builder 在干净的原生 Linux 临时 clone 中再次生成并通过 bundle/provenance/checksum 验证;
15+
- 项目中没有可迁移的 Codex conversation/session/auth archive。唯一有效的工作记录是本状态文件、版本化文档和 Git 提交历史;所有 auth、session、token、cache 与本地 Codex turn-diff refs 均不属于发布输入。
16+
17+
在将 tag 交给 GitHub Actions 前,发现并修复了一项**测试环境**闭环:`verify_live_delta_identity()` 正确拒绝一个不存在的 `/usr/bin/scontrol`,但其单测曾在没有 Slurm 的 generic CI host 上模拟该不存在路径,因而会被正确的 executable trust gate 拒绝。测试现在以真实 root-owned executable 模拟已验证的 command path;生产端的 Delta 身份检查没有放宽。release workflow 同时在三个 Linux validation job 中显式安装 `acl`,确保 ACL 安全合同的测试不依赖 runner 的偶然预装状态。Python 由 `actions/setup-python` 提供,避免使用系统 Python 缺少 `ensurepip` 的情况。
18+
19+
因此发布候选将 manifest/app bundle version 与 immutable release ref 前移到 `0.2.3`。在创建 annotated `codex-bootstrap-v0.2.3` 前,仍必须完成:干净源码测试、最终 release kit、隔离 fresh/latest-Codex 合同和 source 的 GitHub push;tag 推送后还必须等待 x86_64、Python matrix 与 native ARM64 workflow 绿灯,才允许 workflow publish job 创建 GitHub Release。若任一项失败,保留 v0.2.3 tag 不动,修复后创建更高的新 tag。
20+
21+
### 0.1 2026-08-25 最新官方 Codex 无人值守合同
22+
23+
- 重新读取当前官方 `https://chatgpt.com/codex/install.sh`,确认其文档化的 `CODEX_NON_INTERACTIVE` 会映射到内部 `NON_INTERACTIVE`,并跳过包括 `Start Codex now?` 在内的 `/dev/tty` 提示。
24+
- `bootstrap.py` 现在只为官方安装器这个短生命周期子进程固定设置 `CODEX_NON_INTERACTIVE=1`。它仍从白名单构造 secret-free 环境;不继承操作员的 `CODEX_NON_INTERACTIVE``NON_INTERACTIVE` 值,也不把该值写入 config、receipt 或日志。回归测试用操作员环境中的冲突值验证此行为,同时继续验证 API key、GitHub token、SSH agent 等不会传给子进程。
25+
- 在全新、mode `700` 的 disposable `/tmp` HOME/CODEX_HOME 中,`CODEX_RELEASE=latest` 的真实安装已完成,官方当前版本为 `Codex CLI 0.149.1`。安装器没有停在启动确认;随后 `bootstrap.py doctor --require-clean-native-skills --json` 返回 `ok: true`,并通过 safe config、全局 AGENTS、managed skill discovery、plugin JSON 和 Codex compatibility 合同。该实验没有登录、没有 auth/token/session,也没有读取真实 HOME 或 Delta project。
26+
- 本机原生 WSL 的完整 Python suite 运行了 184 项,结果为 `3 failures, 1 error`,且均是本机系统前置条件而非新改动:系统 Python 缺 `ensurepip`(两个 Dr. Claw CLI preflight 断言受此影响),并且没有 `/usr/bin/getfacl`(两个 root-owned Delta ACL 合同受此影响)。release workflow 使用 `actions/setup-python` 并在三个 Linux validation job 显式安装 `acl`;仍须以 GitHub matrix/ARM 绿灯作为最终发布证据,不能把本机这个受限结果写成全绿。
827

928
## 0. 2026-08-20 续作更新(以本节为准)
1029

@@ -59,7 +78,7 @@
5978
- skill runtime inventory:24 个 skill 仍含 Claude-specific 提示、33 个提到 MCP,source installed/discoverable 不等于外部 provider/credential 已激活;
6079
- isolated app service 使用 `service=none`,因此 doctor 正确提示需要在真实 login HOME 下由批准的 supervisor 启动。
6180

62-
当前分支在 final tag 之后只会保留本验收记录的文档提交;它不改变 v0.2.2 tag、offline kit、manifest 或任何可部署代码。未来恢复 GitHub 时应只推送 final source commit/tag 和这个审计文档,绝不移动 v0.2.0v0.2.1 或 v0.2.2 tags。
81+
`v0.2.2` 是完整的历史验收基线;为修复 GitHub CI 的模拟与确定性前置条件,本次会从干净源码创建新的 `v0.2.3` release。绝不移动、删除或复用 `v0.1.0``v0.2.0``v0.2.1``v0.2.2` tags;未来只可继续创建更高的新 tag
6382

6483
## 一、暂停时的准确结论
6584

bootstrap/codex/app-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"schema_version": 1,
3-
"bundle_version": "0.2.2",
3+
"bundle_version": "0.2.3",
44
"application": {
55
"name": "Dr. Claw Web",
66
"package_name": "dr-claw",

bootstrap/codex/bootstrap.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,16 +2675,21 @@ def install_codex(self) -> None:
26752675
if not payload.startswith(b"#!"):
26762676
raise BootstrapError("Downloaded Codex installer did not look like a shell script.")
26772677
installer_path.write_bytes(payload)
2678+
installer_environment = portable_codex_env(
2679+
self.user_home,
2680+
self.codex_home,
2681+
self.target_env,
2682+
include_release=True,
2683+
)
2684+
# The official installer prompts through /dev/tty. Force its documented
2685+
# non-interactive mode only for this child process so a one-command
2686+
# bootstrap cannot hang or persist an operator's prompt preference.
2687+
installer_environment["CODEX_NON_INTERACTIVE"] = "1"
26782688
try:
26792689
subprocess.run(
26802690
["bash", str(installer_path)],
26812691
check=True,
2682-
env=portable_codex_env(
2683-
self.user_home,
2684-
self.codex_home,
2685-
self.target_env,
2686-
include_release=True,
2687-
),
2692+
env=installer_environment,
26882693
cwd=str(self.repo_root),
26892694
timeout=CODEX_INSTALL_TIMEOUT_SECONDS,
26902695
)

bootstrap/codex/manifest.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"schema_version": 1,
3-
"bundle_version": "0.2.2",
3+
"bundle_version": "0.2.3",
44
"audited_at": "2026-08-19",
55
"baseline": {
66
"repository": "https://github.com/OpenLAIR/dr-claw.git",
77
"audited_base_commit": "4b3575641b63f8e09a9539b23c2be4f45f5d0317",
8-
"bundle_release_ref": "codex-bootstrap-v0.2.2",
9-
"production_revision_policy": "Deploy the exact codex-bootstrap-v0.2.2 tag together with its full commit SHA from the signed-off release provenance. The audited_base_commit records the starting tree inspected while this bundle was authored; it is not the deployable bundle revision. Never deploy a moving branch as a reproducible baseline."
8+
"bundle_release_ref": "codex-bootstrap-v0.2.3",
9+
"production_revision_policy": "Deploy the exact codex-bootstrap-v0.2.3 tag together with its full commit SHA from the signed-off release provenance. The audited_base_commit records the starting tree inspected while this bundle was authored; it is not the deployable bundle revision. Never deploy a moving branch as a reproducible baseline."
1010
},
1111
"requirements": {
1212
"python": ">=3.9",

bootstrap/codex/tests/test_bootstrap.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,9 +1279,11 @@ def test_v01_peer_metadata_lock_normalization_permits_only_managed_skill_migrati
12791279
"status_sha256": hashlib.sha256(b"").hexdigest(),
12801280
},
12811281
}
1282-
(self.codex_home / "drclaw-bootstrap-state.json").write_text(
1283-
json.dumps(state), encoding="utf-8"
1284-
)
1282+
state_path = self.codex_home / "drclaw-bootstrap-state.json"
1283+
state_path.write_text(json.dumps(state), encoding="utf-8")
1284+
# The production receipt is deliberately private. Do not rely on the
1285+
# caller's umask when constructing this security-sensitive fixture.
1286+
state_path.chmod(0o600)
12851287
legacy_bin = self.home / ".local" / "bin"
12861288
legacy_bin.mkdir(parents=True, exist_ok=True)
12871289
for name in module.DRCLAW_CLI_LAUNCHERS:
@@ -1985,6 +1987,13 @@ def test_skill_runtime_inventory_is_machine_readable_and_explicitly_non_activate
19851987

19861988
def test_live_delta_identity_gate_and_generic_host_support_matrix(self) -> None:
19871989
module = load_bootstrap_module()
1990+
# The production check deliberately verifies that ``scontrol`` resolves
1991+
# to a trusted executable before it runs it. CI does not ship Slurm,
1992+
# so model the command with a real root-owned executable instead of a
1993+
# nonexistent /usr/bin/scontrol path that the production guard must
1994+
# reject.
1995+
trusted_executable = shutil.which("true")
1996+
self.assertIsNotNone(trusted_executable)
19881997
delta_result = subprocess.CompletedProcess(
19891998
["scontrol", "show", "config"],
19901999
0,
@@ -1993,7 +2002,7 @@ def test_live_delta_identity_gate_and_generic_host_support_matrix(self) -> None:
19932002
)
19942003
with mock.patch.object(module, "bounded_fqdn", return_value="dt-login04.delta.ncsa.illinois.edu"), mock.patch.object(
19952004
module.platform, "machine", return_value="amd64"
1996-
), mock.patch.object(module.shutil, "which", return_value="/usr/bin/scontrol"), mock.patch.object(
2005+
), mock.patch.object(module.shutil, "which", return_value=trusted_executable), mock.patch.object(
19972006
module.subprocess, "run", return_value=delta_result
19982007
):
19992008
identity = module.verify_live_delta_identity(cwd=REPO_ROOT)
@@ -2196,6 +2205,8 @@ def test_codex_installer_and_plugin_commands_receive_no_operator_secrets(self) -
21962205
"GH_TOKEN": secret_marker,
21972206
"SSH_AUTH_SOCK": "/tmp/private-agent.sock",
21982207
"CODEX_RELEASE": "0.147.0",
2208+
"CODEX_NON_INTERACTIVE": "false",
2209+
"NON_INTERACTIVE": "true",
21992210
"HTTPS_PROXY": "https://proxy.example.invalid:8443",
22002211
"NO_PROXY": "127.0.0.1,localhost",
22012212
"DRCLAW_CA_BUNDLE": str(ca_bundle),
@@ -2222,6 +2233,8 @@ def installer_run(command, **kwargs):
22222233
self.assertEqual(len(installer_environments), 1)
22232234
installer_env = installer_environments[0]
22242235
self.assertEqual(installer_env["CODEX_RELEASE"], "0.147.0")
2236+
self.assertEqual(installer_env["CODEX_NON_INTERACTIVE"], "1")
2237+
self.assertNotIn("NON_INTERACTIVE", installer_env)
22252238
for key in ("REVIEW_FAKE_SECRET", "OPENAI_API_KEY", "GH_TOKEN", "SSH_AUTH_SOCK"):
22262239
self.assertNotIn(key, installer_env)
22272240
self.assertEqual(installer_env["HTTPS_PROXY"], operator_env["HTTPS_PROXY"])

bootstrap/codex/tests/test_install_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def _complete_install(self, **overrides):
361361

362362
def test_manifest_pins_official_node_artifacts(self):
363363
node = self.manifest["node"]
364-
self.assertEqual(self.manifest["bundle_version"], "0.2.2")
364+
self.assertEqual(self.manifest["bundle_version"], "0.2.3")
365365
self.assertEqual(
366366
self.manifest["runtime_receipt"],
367367
{"schema_version": 1, "filename": ".drclaw-node-runtime.json"},

0 commit comments

Comments
 (0)