From c5adf55bc87abde6f10d370b61c5ba4156482851 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Thu, 3 Sep 2026 13:37:26 -0700 Subject: [PATCH] CI: the first full run on the branch, three fixes - The oracle harness masks the climb to the root a /-prefixed target produces (/): the number of `../` is the staged tmpdir's depth (7 on macOS, 2 on Linux), not the verb's behavior. surface-brief-path-slash re-recorded. - Two context test helpers canonicalized their temp dir, which on Windows yields a \\?\ verbatim path that takes `/` literally; they strip the prefix like Node's realpathSync. The critique-storage identity test compares against the platform's own resolved path. - Every job that drives the binary end to end (live-e2e smoke and full, accept-cleanup, the DeepSeek sweep, the remote CLI smoke) builds it from the checkout first; before, they looked for a release that does not exist. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --- .github/workflows/ci.yml | 56 +++++++++++++++++++ crates/context/src/critique_storage.rs | 13 ++++- crates/context/src/staleness_deep.rs | 6 +- .../golden/surface-brief-path-slash.json | 2 +- tests/oracle/lib.mjs | 5 ++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45844dabb..b4fe32df2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -280,6 +280,16 @@ jobs: - name: Install dependencies run: bun install + # The live verbs are the engine binary; build it from this checkout so the + # suite tests the branch, not the last published release. + - name: Install the pinned toolchain + run: rustup show + + - uses: Swatinem/rust-cache@v2 + + - name: Build the engine + run: cargo build --release -p impeccable + - name: Run remote CLI E2E smoke run: bun run test:cli-remote-e2e @@ -335,6 +345,16 @@ jobs: - name: Install Playwright Chromium run: npx playwright install chromium + # The live verbs are the engine binary; build it from this checkout so the + # suite tests the branch, not the last published release. + - name: Install the pinned toolchain + run: rustup show + + - uses: Swatinem/rust-cache@v2 + + - name: Build the engine + run: cargo build --release -p impeccable + - name: Run live E2E tests run: bun run test:live-e2e env: @@ -411,6 +431,16 @@ jobs: - name: Install Playwright Chromium run: npx playwright install chromium + # The live verbs are the engine binary; build it from this checkout so the + # suite tests the branch, not the last published release. + - name: Install the pinned toolchain + run: rustup show + + - uses: Swatinem/rust-cache@v2 + + - name: Build the engine + run: cargo build --release -p impeccable + - name: Run live E2E tests run: bun run test:live-e2e env: @@ -483,6 +513,19 @@ jobs: if: ${{ env.ANTHROPIC_API_KEY != '' || env.DEEPSEEK_API_KEY != '' }} run: npx playwright install chromium + # The live verbs are the engine binary; build it from this checkout so the + # suite tests the branch, not the last published release. + - name: Install the pinned toolchain + if: ${{ env.ANTHROPIC_API_KEY != '' || env.DEEPSEEK_API_KEY != '' }} + run: rustup show + + - uses: Swatinem/rust-cache@v2 + if: ${{ env.ANTHROPIC_API_KEY != '' || env.DEEPSEEK_API_KEY != '' }} + + - name: Build the engine + if: ${{ env.ANTHROPIC_API_KEY != '' || env.DEEPSEEK_API_KEY != '' }} + run: cargo build --release -p impeccable + - name: Run accept cleanup regression if: ${{ env.ANTHROPIC_API_KEY != '' || env.DEEPSEEK_API_KEY != '' }} run: | @@ -547,6 +590,19 @@ jobs: if: ${{ env.DEEPSEEK_API_KEY != '' }} run: npx playwright install chromium + # The live verbs are the engine binary; build it from this checkout so the + # suite tests the branch, not the last published release. + - name: Install the pinned toolchain + if: ${{ env.DEEPSEEK_API_KEY != '' }} + run: rustup show + + - uses: Swatinem/rust-cache@v2 + if: ${{ env.DEEPSEEK_API_KEY != '' }} + + - name: Build the engine + if: ${{ env.DEEPSEEK_API_KEY != '' }} + run: cargo build --release -p impeccable + - name: Run Svelte adapter DeepSeek sweep if: ${{ env.DEEPSEEK_API_KEY != '' }} run: bun run test:live-svelte-adapter-deepseek diff --git a/crates/context/src/critique_storage.rs b/crates/context/src/critique_storage.rs index 7299ee885..b4cceba8e 100644 --- a/crates/context/src/critique_storage.rs +++ b/crates/context/src/critique_storage.rs @@ -694,7 +694,10 @@ mod tests_660 { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() )); std::fs::create_dir_all(&base).unwrap(); - std::fs::canonicalize(&base).unwrap().to_string_lossy().into_owned() + // Like Node's `realpathSync`: no `\\\\?\\` verbatim prefix on Windows, + // so paths the verb joins with `/` resolve under this root. + let real = std::fs::canonicalize(&base).unwrap().to_string_lossy().into_owned(); + real.strip_prefix(r"\\\\?\\").map(str::to_string).unwrap_or(real) } fn run_capture(cwd: &str, args: &[&str]) -> (i32, String, String) { @@ -719,7 +722,10 @@ mod tests_660 { #[test] fn target_identity_file_url_and_trailing_slash() { let cwd = "/work"; - assert_eq!(resolve_target_identity("src/App.tsx", cwd), Some("file:/work/src/App.tsx".to_string())); + // The file identity is the platform's resolved path (Node `path.resolve` + // semantics through `jsp`), so it carries the host's separators. + let expected_file = format!("file:{}", jsp::resolve(cwd, &["src/App.tsx"])); + assert_eq!(resolve_target_identity("src/App.tsx", cwd), Some(expected_file)); assert_eq!(resolve_target_identity("http://example.com/pricing/", cwd), Some("url:http://example.com/pricing".to_string())); assert_eq!(resolve_target_identity("http://example.com", cwd), Some("url:http://example.com/".to_string())); assert_eq!(resolve_target_identity("", cwd), None); @@ -746,7 +752,8 @@ mod tests_660 { let dir = jsp::join(&[&cwd, ".impeccable", "critique"]); std::fs::create_dir_all(&dir).unwrap(); let name = "2026-05-12T18-30-00Z__app-tsx.md"; - let identity = format!("file:{}/App.tsx", cwd); + // Joined the way the verb resolves it, so the separators match on Windows. + let identity = format!("file:{}", jsp::join(&[&cwd, "App.tsx"])); let body = format!("---\ntarget_identity: \"{}\"\nslug: app-tsx\n---\n# Critique\n", identity); std::fs::write(jsp::join(&[&dir, name]), &body).unwrap(); diff --git a/crates/context/src/staleness_deep.rs b/crates/context/src/staleness_deep.rs index 1c5faeded..10dfbc772 100644 --- a/crates/context/src/staleness_deep.rs +++ b/crates/context/src/staleness_deep.rs @@ -444,7 +444,11 @@ mod tests { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() )); std::fs::create_dir_all(&base).unwrap(); - std::fs::canonicalize(&base).unwrap().to_string_lossy().into_owned() + // Like Node's `realpathSync`: on Windows, `canonicalize` yields a + // `\\?\` verbatim path, which the kernel takes literally, so the `/` + // separators a hook command appends would not resolve under it. + let real = std::fs::canonicalize(&base).unwrap().to_string_lossy().into_owned(); + real.strip_prefix(r"\\?\").map(str::to_string).unwrap_or(real) } fn write(root: &str, rel: &str, body: &str) { diff --git a/tests/oracle/golden/surface-brief-path-slash.json b/tests/oracle/golden/surface-brief-path-slash.json index 7cc222172..eba2a27bb 100644 --- a/tests/oracle/golden/surface-brief-path-slash.json +++ b/tests/oracle/golden/surface-brief-path-slash.json @@ -1,5 +1,5 @@ { - "stdout": "../../../../../../../.impeccable/surfaces/route.md\n", + "stdout": "/.impeccable/surfaces/route.md\n", "stderr": "", "exit": 0, "signal": null, diff --git a/tests/oracle/lib.mjs b/tests/oracle/lib.mjs index d22bd2552..c889abdf9 100644 --- a/tests/oracle/lib.mjs +++ b/tests/oracle/lib.mjs @@ -151,6 +151,11 @@ export function normalize(text, { ws, home = os.homedir() }) { // (2026-05-12T18-30-00Z), both in the file name (__.md) and in // the `timestamp:` frontmatter it writes. out = out.replace(/\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z/g, ''); + // A target given as an absolute path outside the workspace makes the verb + // print the surface path relative to the root, which climbs as many levels + // as the staged tmpdir is deep (7 on macOS, 2 on Linux). The climb is a + // property of the machine, not of the verb. + out = out.replace(/(?:\.\.\/){2,}(?=\.impeccable\/)/g, '/'); // Hook audit entries carry wall-clock durations. out = out.replace(/"durationMs":\s*\d+(?:\.\d+)?/g, '"durationMs": '); // ISO timestamps and epoch millis are run-dependent.