Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 70 additions & 60 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ jobs:
- name: Core Codex / model dep scan
env:
SCAN_BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
run: ./scripts/check-no-model-deps.sh
run: python3 scripts/validate-upstream.py policy python --output target/upstream-reports/policy
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-format:
name: Rust / Format + upstream pin
Expand All @@ -28,16 +34,14 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Upstream pin
run: PIN_ONLY=1 ./scripts/check-upstream-pin.sh
- name: Format
run: |
cargo fmt --check
cargo fmt --check --manifest-path crates/patch/Cargo.toml
cargo fmt --check --manifest-path crates/codex-runtime/Cargo.toml
cargo fmt --check --manifest-path crates/pty/Cargo.toml
cargo fmt --check --manifest-path crates/file-system/Cargo.toml
cargo fmt --check --manifest-path crates/linux-sandbox/Cargo.toml
- name: Format and pin
run: python3 scripts/validate-upstream.py pin format --output target/upstream-reports/format
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-clippy:
name: Rust / Clippy (${{ matrix.name }})
Expand All @@ -47,27 +51,11 @@ jobs:
matrix:
include:
- name: root
command: |
set -euo pipefail
cargo clippy --locked --all-targets -- -D warnings
fail=0
for pkg in codespace-linux-sandbox codex-linux-sandbox; do
if cargo tree -p codespace-runner --locked --edges normal -i "$pkg" --prefix none >/dev/null 2>&1; then
echo "codespace-runner graph must not include $pkg" >&2
cargo tree -p codespace-runner --locked --edges normal -i "$pkg" --prefix none >&2 || true
fail=1
fi
done
test "$fail" -eq 0
stage: clippy-root dependencies
- name: adapters
command: |
cargo clippy --locked --manifest-path crates/patch/Cargo.toml --all-targets -- -D warnings
cargo clippy --locked --manifest-path crates/pty/Cargo.toml --all-targets -- -D warnings
cargo clippy --locked --manifest-path crates/file-system/Cargo.toml --all-targets -- -D warnings
stage: clippy-adapters
- name: codex-adapters
command: |
cargo clippy --locked --manifest-path crates/codex-runtime/Cargo.toml --all-targets -- -D warnings
cargo clippy --locked --manifest-path crates/linux-sandbox/Cargo.toml --all-targets -- -D warnings
stage: clippy-codex
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -83,27 +71,21 @@ jobs:
~/.cargo/git
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Clippy
run: ${{ matrix.command }}
run: python3 scripts/validate-upstream.py ${{ matrix.stage }} --output target/upstream-reports/${{ matrix.name }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-unit:
name: Rust / Unit (${{ matrix.name }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: patch
command: cargo test --locked --manifest-path crates/patch/Cargo.toml
- name: codex-runtime
command: cargo test --locked --manifest-path crates/codex-runtime/Cargo.toml
- name: pty
command: cargo test --locked --manifest-path crates/pty/Cargo.toml
- name: file-system
command: cargo test --locked --manifest-path crates/file-system/Cargo.toml
- name: linux-sandbox-protocol
command: cargo test --locked -p codespace-linux-sandbox-protocol
- name: linux-sandbox
command: cargo test --locked --manifest-path crates/linux-sandbox/Cargo.toml --bins --test cli
name: [patch, codex-runtime, pty, file-system, linux-sandbox-protocol, linux-sandbox]
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -117,7 +99,13 @@ jobs:
~/.cargo/git
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Unit tests
run: ${{ matrix.command }}
run: python3 scripts/validate-upstream.py unit-${{ matrix.name }} --output target/upstream-reports/unit-${{ matrix.name }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-linux-isolation:
name: Rust / Linux isolation
Expand Down Expand Up @@ -154,12 +142,14 @@ jobs:
echo "Disabling kernel.apparmor_restrict_unprivileged_userns for bubblewrap."
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Build Linux sandbox helper
run: cargo build --locked --manifest-path crates/linux-sandbox/Cargo.toml --bin codespace-linux-sandbox
- name: Linux isolation tests
env:
CODESPACE_REQUIRE_LINUX_SANDBOX: "1"
run: cargo test --locked --manifest-path crates/linux-sandbox/Cargo.toml --test isolation
run: python3 scripts/validate-upstream.py linux-isolation --output target/upstream-reports/isolation
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-integration:
name: Rust / Integration
Expand Down Expand Up @@ -191,17 +181,33 @@ jobs:
if [ -n "$current_apparmor" ] && [ "$current_apparmor" != "0" ]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Build helpers
run: |
cargo build --locked --manifest-path crates/patch/Cargo.toml --bin codespace-patch
cargo build --locked --manifest-path crates/codex-runtime/Cargo.toml --bin codespace-codex-runtime
cargo build --locked --manifest-path crates/linux-sandbox/Cargo.toml --bin codespace-linux-sandbox
- name: Workspace integration tests
env:
CODESPACE_PATCH_BIN: ${{ github.workspace }}/crates/patch/target/debug/codespace-patch
CODESPACE_RUNTIME_BIN: ${{ github.workspace }}/crates/codex-runtime/target/debug/codespace-codex-runtime
CODESPACE_LINUX_SANDBOX_BIN: ${{ github.workspace }}/crates/linux-sandbox/target/debug/codespace-linux-sandbox
run: cargo test --locked --workspace
run: python3 scripts/validate-upstream.py integration --output target/upstream-reports/integration
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust-macos:
name: Rust / macOS contracts
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: macOS contracts and dependencies
run: python3 scripts/validate-upstream.py macos-core dependencies --output target/upstream-reports/macos
- uses: actions/upload-artifact@v4
if: always()
with:
name: upstream-${{ github.job }}-${{ matrix.name || 'single' }}-${{ github.run_attempt }}
path: target/upstream-reports/
if-no-files-found: warn

rust:
name: rust
Expand All @@ -212,6 +218,8 @@ jobs:
- rust-unit
- rust-linux-isolation
- rust-integration
- rust-macos
- policy-scan
runs-on: ubuntu-latest
steps:
- name: Require all Rust checks
Expand All @@ -221,3 +229,5 @@ jobs:
test "${{ needs.rust-unit.result }}" = "success"
test "${{ needs.rust-linux-isolation.result }}" = "success"
test "${{ needs.rust-integration.result }}" = "success"
test "${{ needs.rust-macos.result }}" = "success"
test "${{ needs.policy-scan.result }}" = "success"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ compile-out/
.turbo/
.direnv/
.local/
__pycache__/
33 changes: 23 additions & 10 deletions crates/pty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,29 @@ mod tests {
assert_eq!(env!("CARGO_PKG_NAME"), "codespace-pty");
}

#[test]
fn default_size_is_24x80_and_matches_upstream() {
assert_eq!(DEFAULT_ROWS, 24);
assert_eq!(DEFAULT_COLS, 80);
let upstream = codex_utils_pty::TerminalSize::default();
assert_eq!(
(upstream.rows, upstream.cols),
(DEFAULT_ROWS, DEFAULT_COLS),
"upstream TerminalSize::default drifted from advertised PTY size"
);
#[tokio::test]
async fn created_terminal_has_advertised_size() {
let dir = tempfile::tempdir().unwrap();
let env = HashMap::from([("PATH".into(), "/usr/bin:/bin".into())]);
let mut session = spawn("/bin/stty", &["size".into()], dir.path(), &env)
.await
.expect("spawn stty");
let mut output = session.take_stdout().expect("stdout");
let code = tokio::time::timeout(Duration::from_secs(5), session.take_exit().unwrap())
.await
.expect("exit timeout")
.expect("exit receiver");
assert_eq!(code, 0);
let bytes = tokio::time::timeout(Duration::from_secs(5), async {
let mut bytes = Vec::new();
while let Some(chunk) = output.recv().await {
bytes.extend(chunk);
}
bytes
})
.await
.expect("output timeout");
assert_eq!(String::from_utf8(bytes).unwrap().trim(), "24 80");
}

#[tokio::test]
Expand Down
33 changes: 33 additions & 0 deletions docs/ko/upstream-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,36 @@
## 반영과 되돌리기

PR에 이전·새 SHA, 동작 변경, 테스트 근거를 기록합니다. 호환성 검사가 실패한 채 병합하거나 다른 패치 엔진으로 조용히 대체하지 않습니다. 되돌릴 때는 서브모듈, 어댑터 잠금 파일, 필요한 Cargo 패치, 문서를 함께 되돌리고 영향받는 검사를 다시 수행합니다. 고정 커밋 불일치는 경고가 아닌 오류입니다.

## 재현 가능한 검증 보고서

전체 로컬 검증은 `python3 scripts/validate-upstream.py all`로 실행합니다.
CI도 같은 이름의 단계를 병렬 실행하며 목록은 `--help`로 확인합니다.
보고서와 명령 로그는 기본적으로 `target/upstream-reports/local`에 생성됩니다.
시도마다 `--output`으로 다른 Git 무시 디렉터리를 지정하거나 이전 결과를 보관합니다.
보고서에는 소스 HEAD, Codex SHA, Rust 호스트, 소스와 lockfile 해시가 기록되며
실행 중 입력이 바뀌면 실패합니다. `passed`는 기록된 단계만의 통과를 뜻합니다.
macOS에서는 Linux 격리를 `not_run`, 전체 결과를 `incomplete`로 표시합니다.
Linux CI 근거가 별도로 필요하며 macOS CI는 PTY와 파일 시스템 계약도 검사합니다.
한 플랫폼 결과만으로 다른 플랫폼 검증을 대체하지 않습니다.

의존성 검사는 `--locked`와 대상 플랫폼 필터를 사용한 Cargo metadata에서
제품 root의 일반·빌드 의존성을 탐색하고 개발용 관계는 제외합니다.
에이전트·제품 crate 및 Runner에서 샌드박스 라이브러리로 향하는 경로는 실패합니다.
이는 패키지 도달 가능성 검사이며 모든 API가 실제 실행됨을 뜻하지 않습니다.
Cargo feature 통합으로 선택적 관계가 보수적으로 포함될 수 있습니다.

동일한 Rust target의 보관된 결과와 후보 결과를 비교할 수 있습니다.
기준 결과를 자동 갱신하지 않습니다.

```bash
python3 scripts/upstream_dependencies.py --target x86_64-unknown-linux-gnu \
--compare target/baseline/dependencies.json \
--output target/candidate/dependencies.json
```

보고서는 추가·삭제된 패키지와 관계를 나열합니다. 버전·출처 변경은 삭제와 추가로
표시됩니다. 일반 변화는 검토 대상이며 금지 의존성, 잘못된 metadata, 누락된 root,
Cargo 실패는 검증 실패입니다. 경로 식별자는 저장소 상대 경로를 사용합니다.
CI는 실패 시에도 보고서와 로그를 업로드합니다. 기존 pin 검사 스크립트는
SHA와 패치 테스트만 확인하며 전체 검증 명령을 대체하지 않습니다.
6 changes: 4 additions & 2 deletions docs/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@
"forbidden",
"local-gate",
"release-and-rollback",
"reproducible-validation-reports",
"review-the-candidate",
"updating-codex-dependencies",
"upstream-pin-update",
Expand All @@ -611,12 +612,13 @@
"로컬-게이트",
"반영과-되돌리기",
"업스트림-핀-갱신",
"재현-가능한-검증-보고서",
"점검-목록",
"제출-전-검증",
"후보-검토"
],
"source_sha256": "efe8b1d11db41148ec6dfd08a67b52fd72d2e32a480123f27668feff9e39f8da",
"translation_sha256": "fe4f0732755820b3b2db72190770359663ca6c6f2fcbc54227f714152ef7682d"
"source_sha256": "dcac8e5362953f5ade48326c69d48f4a448509853ec2eb6034fb6400fe0418b3",
"translation_sha256": "692bccd11d607df49a0802b0a37df204af071a23ec826d6e72f8bdd2973fa6ff"
},
{
"id": "documentation",
Expand Down
35 changes: 35 additions & 0 deletions docs/upstream-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,38 @@ A passing patch subset is insufficient for an update that also affects execution
## Release and rollback

Open a PR with the old/new SHA, behavioral changes, and test evidence. Do not merge a failed compatibility gate or silently fall back to another patch engine. If the update must be reverted, revert the submodule, adapter locks, required Cargo patches, and documentation together; then rerun the affected gates. A pin mismatch is an error, not a warning.

## Reproducible validation reports

Run `python3 scripts/validate-upstream.py all` for the complete local sequence.
CI calls the same named stages in parallel; use `--help` to list them.
Reports and command logs default to `target/upstream-reports/local`.
Choose a different ignored directory with `--output` for each attempt; preserve
previous reports before repeating a run. Reports record the source HEAD, Codex
SHA, Rust host, and source/lockfile hashes. A changed input invalidates the run.
`passed` applies only to the listed stages, not to all release gates.
On macOS, Linux isolation is explicitly `not_run` and the overall result is
`incomplete`; Linux CI evidence is still required. macOS CI additionally checks
PTY and filesystem contracts. Neither result alone replaces the other platform.

The dependency stage uses locked, target-filtered Cargo metadata and follows
normal/build edges from product roots, excluding development edges. It rejects
agent/product crates and Runner-to-sandbox-library edges with a dependency path.
This checks package reachability, not whether a binary executes every linked API.
Cargo's resolved feature unification can conservatively include optional edges.

Generate a candidate report and compare it with an archived report for the same
Rust target (the baseline is never updated automatically):

```bash
python3 scripts/upstream_dependencies.py --target x86_64-unknown-linux-gnu \
--compare target/baseline/dependencies.json \
--output target/candidate/dependencies.json
```

The comparison lists added/removed package identities and edges. A version or
source change appears as removal plus addition. Ordinary changes require review;
forbidden dependencies, malformed metadata, missing roots, or Cargo failure fail
the gate. Source paths are repository-relative, never machine-specific identities.
CI uploads reports/logs even on failed validation. The legacy pin script still
checks only SHA and patch tests; it is not a complete qualification command.
3 changes: 2 additions & 1 deletion scripts/check-upstream-pin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ if [[ "${PIN_ONLY:-}" == "1" ]]; then
exit 0
fi

cargo test --manifest-path crates/patch/Cargo.toml
echo "This gate covers SHA + patch only; full qualification: python3 scripts/validate-upstream.py all"
cargo test --locked --manifest-path crates/patch/Cargo.toml
Loading
Loading