From e07cdc3ee984f28dd877ed89b682838cf011948a Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 8 Nov 2026 18:22:11 +0900 Subject: [PATCH 1/5] ci: add targeted Windows native claim diagnostic lane --- .github/scripts/release-evidence-policy.json | 32 +++++++++ .github/scripts/release-evidence.test.cjs | 2 +- .github/workflows/ci.yml | 49 ++++++++++++- structure/ops/cross-platform-ci.md | 4 ++ tests/ci-workflows/ci-review-lanes.test.ts | 9 ++- tests/ci-workflows/ci-scope-reduction.test.ts | 72 +++++++++++++++++-- tests/ci-workflows/ci-workflows.test.ts | 16 ++--- 7 files changed, 163 insertions(+), 21 deletions(-) diff --git a/.github/scripts/release-evidence-policy.json b/.github/scripts/release-evidence-policy.json index 9002cc3ae92..ff83e3148f9 100644 --- a/.github/scripts/release-evidence-policy.json +++ b/.github/scripts/release-evidence-policy.json @@ -102,5 +102,37 @@ "desktop/scripts/appimage-patchelf.py": "63e78ef74160bc48fbcdd882065f1d416db6d13c", "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" } + }, + { + "kind": "ci", + "version": 1, + "files": { + ".github/workflows/ci.yml": "dab526d07a43d19d3131f46b8be06ea0d1e630b2", + ".github/actions/setup-project-bun/action.yml": "cf28f6f9d4385d69784f4e3610914f6952ce789d", + ".github/scripts/release-evidence.cjs": "c4cd8c762abbfc1c0674937535c5a36b9cf705fd", + ".github/scripts/release-evidence.test.cjs": "de30ad37105cf0e7dd8c538efb5ee1cfafe02990", + "scripts/ci/select-test-lane.sh": "b25836cb55f962d1ffd9e68ed00f45f8b45becc8", + "scripts/ci/windows-sensitive-diff.sh": "03495f256c671aff57e750febffe19ca7face6a1", + "scripts/ci/run-bun-test-batches.sh": "f8aa18e80aff1c95c584cd2c879dfbb6806c11fb", + "scripts/ci/bun-crash-signatures.sh": "d34662ddd07864d97bed909135095db33fe42c9e", + "scripts/ci/bun-install-frozen.sh": "e6d7bc3aec044e12297c7c4c04015a33df0eb22b", + "scripts/ci/docker-smoke.ts": "a8424f93986a31625a0a236cdab5667db67b3405", + "scripts/ci/test-durations.tsv": "0c7b8eeac35a5f09414d1b77185733b5936c594f", + "scripts/ci/test-durations.windows.tsv": "5ee31b6bd6cada6ab7d806ea03dd0d8e46fe1d29", + "scripts/test.ts": "363bf072c4b689478fdbb76a61bf4eadbc054548", + "scripts/lib/test-runner-bun.ts": "8763806104a851dbdefeea8cffe8d7dd8d1c8da1", + "scripts/test-temp-lock.ts": "ee83fb254b728de119028f52b9894bf52d7aea15", + "scripts/ci/run-changed-tests.sh": "9a54d3a5db9849bcbe0dd766954a840185419fa5", + "scripts/keyring-smoke.ts": "cd3be07ad57796172696dfa053ea1fb129f29365", + "scripts/privacy-scan.ts": "86314e891b45e49739b94c76cddd9e4cc2d4340f", + "scripts/structure-ssot.ts": "40ba68401df9b77156afed66d6a11c61df8e9a41", + "scripts/generate-ocx-skill-surface.ts": "750ea21e5668aa512a83aa5a7f8cd7bc5cbd79db", + "desktop/scripts/prepare-sidecar.ts": "20f7eec61f73635ea681e25f13dd850b62bb41e2", + "desktop/scripts/build-widget.sh": "38e0eb9d713804b603aa75c32383aa18ef01d06c", + "desktop/scripts/verify-macos-runtime.sh": "43f1a31375c6f6fe7dcafb9d96c231b98d1f6ba4", + "desktop/scripts/verify-linux-sidecar.sh": "c502bb5c8ba8346c722a8f5274f2274bb325a934", + "desktop/scripts/appimage-patchelf.py": "63e78ef74160bc48fbcdd882065f1d416db6d13c", + "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" + } } ] diff --git a/.github/scripts/release-evidence.test.cjs b/.github/scripts/release-evidence.test.cjs index 4ecdd6ea059..de30ad37105 100644 --- a/.github/scripts/release-evidence.test.cjs +++ b/.github/scripts/release-evidence.test.cjs @@ -67,7 +67,7 @@ test('run identity rejects forged titles, wrong repository, branch, event and at const workflow = {id:42}; const run = {id:7, workflow_id:42, repository:{id:1273824907}, head_repository:{id:1273824907}, event:'workflow_dispatch', head_branch:'dev', status:'completed', conclusion:'success', run_attempt:1, head_sha:SHA, display_title:`ocx-ci-v1 lane=all sha=${SHA}`}; assert.doesNotThrow(() => validateRun(run, workflow, 'ci', SHA)); - for (const change of [{event:'push'}, {head_branch:'main'}, {conclusion:'failure'}, {run_attempt:0}, {repository:{id:2}}, {head_repository:{id:2}}, {display_title:`ocx-ci-v1 lane=release-gates sha=${SHA}`}, {head_sha:'b'.repeat(40)}]) { + for (const change of [{event:'push'}, {head_branch:'main'}, {conclusion:'failure'}, {run_attempt:0}, {repository:{id:2}}, {head_repository:{id:2}}, {display_title:`ocx-ci-v1 lane=release-gates sha=${SHA}`}, {display_title:`ocx-ci-v1 lane=windows-native-claim sha=${SHA}`}, {head_sha:'b'.repeat(40)}]) { assert.throws(() => validateRun({...run, ...change}, workflow, 'ci', SHA)); } }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a53d40c6d97..dab526d07a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,10 +34,10 @@ on: workflow_dispatch: inputs: lane: - description: "all (default), release-gates, or macos-control" + description: "all (default), release-gates, macos-control, or windows-native-claim" type: choice default: all - options: [all, release-gates, macos-control] + options: [all, release-gates, macos-control, windows-native-claim] permissions: contents: read @@ -103,6 +103,17 @@ jobs: runner: ${{ steps.pick.outputs.runner }} label: ${{ steps.pick.outputs.label }} steps: + # This job also runs when the diagnostic lane skips changes. + - name: Validate dispatch source + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} + run: | + if [ "$EVENT_NAME" = workflow_dispatch ] && [ "$REF" != refs/heads/dev ]; then + echo '::error::manual cross-platform CI must run from dev' + exit 1 + fi - name: Pick runner id: pick env: @@ -142,6 +153,7 @@ jobs: # in the `on:` block above. changes: name: changes + if: github.event_name != 'workflow_dispatch' || github.event.inputs.lane != 'windows-native-claim' runs-on: ubuntu-latest timeout-minutes: 5 # The workflow grants only `contents: read`, and specifying any permission @@ -987,6 +999,37 @@ jobs: - name: CLI help smoke run: bun run src/cli/index.ts help + # A diagnostic run deliberately bypasses changes and the full CI graph. Its + # title identifies it as a partial run, never release evidence. + windows-native-claim: + name: windows native-main claim diagnostic + if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev' && github.event.inputs.lane == 'windows-native-claim' + runs-on: windows-latest + timeout-minutes: 30 + steps: + - name: Checkout workflow commit + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + ref: ${{ github.sha }} + persist-credentials: false + - name: Setup project Bun + uses: ./.github/actions/setup-project-bun + with: + role: test-runner + - name: Install dependencies + run: bun install --frozen-lockfile + - name: Isolate homes + shell: pwsh + run: | + $root = Join-Path $env:RUNNER_TEMP "ocx-claim-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" + $ocxHome = Join-Path $root 'opencodex' + $codexHome = Join-Path $root 'codex' + New-Item -ItemType Directory -Path $ocxHome, $codexHome -ErrorAction Stop | Out-Null + "OPENCODEX_HOME=$ocxHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + "CODEX_HOME=$codexHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Run native-main claim diagnostics + run: bun test tests/codex-integration/main-quota-provenance.test.ts --timeout 60000 + # Windows shards run only on a full manual dispatch. Pull requests retain # fast Ubuntu checks, including when the Windows-sensitive filter is set. platform-windows: @@ -1683,7 +1726,7 @@ jobs: ci: name: ci - if: always() + if: always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.lane != 'windows-native-claim') # Every producer, including the ones that only feed other jobs. `needs` holds # direct dependencies only, so a failing `select-windows-runner` would # otherwise reach this gate as nothing at all while its dependents report diff --git a/structure/ops/cross-platform-ci.md b/structure/ops/cross-platform-ci.md index d01156c2b1f..a4fd75b5478 100644 --- a/structure/ops/cross-platform-ci.md +++ b/structure/ops/cross-platform-ci.md @@ -11,6 +11,10 @@ manual dispatch from `dev`, not on PRs. A full `lane=all` dispatch provides evid source SHA; a PR green or a narrower dispatch is not full-platform evidence. After a batch of merges, maintainers run one full dispatch on `dev` rather than one per PR. +The manual `windows-native-claim` lane runs the instrumented native-main quota +provenance test on one hosted Windows runner, without starting the full matrix. +It is diagnostic evidence only: it cannot mark a commit green for release. + Two paths sit outside the `ci` filter on purpose and get narrow jobs instead of the full matrix. A change under `.github/actions/` runs `setup-action` on Linux for PRs and on Linux, Windows and macOS for full dispatch: it runs the composite Bun setup and requires the installed runtime to equal the version `package.json` diff --git a/tests/ci-workflows/ci-review-lanes.test.ts b/tests/ci-workflows/ci-review-lanes.test.ts index 3d3782d1c64..2d83451a9af 100644 --- a/tests/ci-workflows/ci-review-lanes.test.ts +++ b/tests/ci-workflows/ci-review-lanes.test.ts @@ -260,7 +260,7 @@ describe("CI review lanes", () => { expect(enabled("macos-control", "schedule", "", "false", "false", "false")).toBe(true); expect(ci.jobs["select-windows-runner"]!.if).toBeUndefined(); expect(ci.jobs["platform-windows"]!.needs).toEqual(["changes", "select-windows-runner"]); - expect(ci.jobs.ci!.if).toBe("always()"); + expect(ci.jobs.ci!.if).toBe("always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.lane != 'windows-native-claim')"); expect(ci.jobs.ci!.needs).toEqual(expect.arrayContaining([ "changes", "select-windows-runner", "test", "platform-macos", "gates", "storage-policy", "api-usage", @@ -376,15 +376,22 @@ describe("Windows and nightly CI contracts", () => { expect(windows?.run).toBe("bash scripts/ci/windows-sensitive-diff.sh"); expect(windows?.run).not.toContain("${{"); let checkouts = 0; + let diagnosticCheckouts = 0; for (const [name, job] of Object.entries(ci.jobs)) { if (name === "changes") continue; for (const step of job.steps ?? []) { if (!step.uses?.startsWith("actions/checkout@")) continue; + if (name === "windows-native-claim") { + expect(step.with?.ref, `${name} checkout`).toBe("${{ github.sha }}"); + diagnosticCheckouts += 1; + continue; + } expect(step.with?.ref, `${name} checkout`).toBe("${{ needs.changes.outputs.checkout_ref }}"); checkouts += 1; } } expect(checkouts).toBe(18); + expect(diagnosticCheckouts).toBe(1); }); test.skipIf(process.platform === "win32" || !Bun.which("jq"))("aggregate never requests Windows on a pull request and requests both diagnostic jobs nightly", async () => { diff --git a/tests/ci-workflows/ci-scope-reduction.test.ts b/tests/ci-workflows/ci-scope-reduction.test.ts index 11b0813dcc9..99b2d3bc471 100644 --- a/tests/ci-workflows/ci-scope-reduction.test.ts +++ b/tests/ci-workflows/ci-scope-reduction.test.ts @@ -23,7 +23,7 @@ import { repoPath } from "../helpers/repo-root"; */ const workflow = Bun.YAML.parse(readFileSync(repoPath(".github", "workflows", "ci.yml"), "utf8")) as { - on?: { push?: { branches?: string[] } }; + on?: { push?: { branches?: string[] }; workflow_dispatch?: { inputs?: { lane?: { options?: string[] } } } }; jobs?: Record; uses?: string; @@ -63,7 +64,7 @@ test("release history checks run with complete git history in gates", () => { expect(checkout?.with?.["fetch-depth"]).toBe(0); }); -type SelectionInputs = { event_name: string; ci: string; native: string; desktop?: string; docker?: string }; +type SelectionInputs = { event_name: string; ci: string; native: string; desktop?: string; docker?: string; lane?: string; ref?: string }; function term(source: string, inputs: SelectionInputs): string | boolean { const text = source.trim(); @@ -76,6 +77,9 @@ function term(source: string, inputs: SelectionInputs): string | boolean { const literal = /^'([^']*)'$/.exec(text); if (literal) return literal[1]!; if (text === "github.event_name") return inputs.event_name; + if (text === "github.event.inputs.lane") return inputs.lane ?? ""; + if (text === "github.ref") return inputs.ref ?? ""; + if (text === "always()") return true; const output = /^needs\.changes\.outputs\.([a-z-]+)$/.exec(text); if (output) { if (output[1] === "ci") return inputs.ci; @@ -324,6 +328,60 @@ describe("the dispatch trigger", () => { }); }); +test.skipIf(process.platform === "win32")("the unavoidable selector refuses non-dev manual dispatches", () => { + const selector = jobs["select-windows-runner"]!; + expect(selector.if).toBeUndefined(); + expect(selector.needs).toBeUndefined(); + const guard = selector.steps?.find(step => step.name === "Validate dispatch source"); + expect(guard?.shell).toBe("bash"); + expect(guard?.env).toEqual({ EVENT_NAME: "${{ github.event_name }}", REF: "${{ github.ref }}" }); + expect(guard?.run).toBeDefined(); + expect(guard?.run).not.toContain("${{"); + for (const [event, ref, expected] of [ + ["workflow_dispatch", "refs/heads/main", 1], + ["workflow_dispatch", "refs/heads/feature", 1], + ["workflow_dispatch", "refs/heads/dev", 0], + ["pull_request", "refs/pull/1/merge", 0], + ] as const) { + const result = Bun.spawnSync(["/bin/bash", "-e", "-c", guard!.run!], { env: { EVENT_NAME: event, REF: ref } }); + expect(result.exitCode, `${event} ${ref}`).toBe(expected); + } +}); + +test("Windows claim diagnostic is dev-only and bypasses the full graph", () => { + const lane = "windows-native-claim"; + expect(workflow.on?.workflow_dispatch?.inputs?.lane?.options).toContain(lane); + const diagnostic = jobs[lane]!; + expect(diagnostic["runs-on"]).toBe("windows-latest"); + expect(diagnostic.needs).toBeUndefined(); + const base = { ci: "false", native: "false", event_name: "workflow_dispatch", ref: "refs/heads/dev", lane }; + expect(evaluate(diagnostic.if!, base)).toBe(true); + for (const variation of [{ event_name: "pull_request" }, { event_name: "push" }, { ref: "refs/heads/main" }, { lane: "all" }, { lane: "release-gates" }, { lane: "macos-control" }, { lane: "" }]) { + expect(evaluate(diagnostic.if!, { ...base, ...variation })).toBe(false); + } + expect(evaluate(changes!.if!, base)).toBe(false); + expect(evaluate(jobs.ci!.if!, base)).toBe(false); + for (const lane of ["", "all", "release-gates", "macos-control"]) { + expect(evaluate(changes!.if!, { ...base, lane })).toBe(true); + expect(evaluate(jobs.ci!.if!, { ...base, lane })).toBe(true); + } + expect(evaluate(changes!.if!, { ...base, event_name: "pull_request" })).toBe(true); + expect(evaluate(jobs.ci!.if!, { ...base, event_name: "pull_request" })).toBe(true); + for (const [name, job] of Object.entries(jobs)) { + if ([lane, "select-windows-runner", "changes", "ci"].includes(name)) continue; + expect(Array.isArray(job.needs) ? job.needs : [job.needs], name).toContain("changes"); + expect(job.if ?? "", name).not.toMatch(/\b(always|failure|cancelled)\s*\(/); + } + const steps = diagnostic.steps ?? []; + const checkout = steps.find(step => step.uses?.startsWith("actions/checkout@")); + expect(checkout?.with?.ref).toBe("${{ github.sha }}"); + expect(checkout?.with?.["persist-credentials"]).toBe(false); + expect(steps.find(step => step.uses === "./.github/actions/setup-project-bun")?.with?.role).toBe("test-runner"); + expect(steps.some(step => step.run?.includes("bun install --frozen-lockfile"))).toBe(true); + expect(steps.some(step => step.run?.includes("bun test tests/codex-integration/main-quota-provenance.test.ts"))).toBe(true); + expect(steps.some(step => step.run?.includes("OPENCODEX_HOME") && step.run.includes("CODEX_HOME") && step.run.includes("GITHUB_ENV"))).toBe(true); +}); + describe("Docker PR selection", () => { test("selects only its Ubuntu smoke for matching inputs without widening event policy", () => { const job = jobs["docker-smoke"]!; @@ -513,9 +571,9 @@ describe("the aggregate gate", () => { expect(step?.env?.CHANGES_CI).toBe("${{ needs.changes.outputs.ci }}"); }); - test.skipIf(cannotRunGate)("gates every job by name, with the aggregate itself excepted", () => { + test.skipIf(cannotRunGate)("gates every ordinary job by name, excluding the standalone diagnostic", () => { const { gatedJobs } = runGate(PR_WITHOUT_NATIVE, {}); - expect(gatedJobs.sort()).toEqual(Object.keys(jobs).filter(name => name !== "ci").sort()); + expect(gatedJobs.sort()).toEqual(Object.keys(jobs).filter(name => !["ci", "windows-native-claim"].includes(name)).sort()); }); test.skipIf(cannotRunGate)("treats an unselected native job as not-requested and stays green over skips", () => { @@ -531,7 +589,7 @@ describe("the aggregate gate", () => { // Results consistent with those expectations must pass the gate: skipped is // the correct outcome for a job this event never asked for. const consistent = Object.fromEntries(Object.keys(jobs) - .filter(name => name !== "ci") + .filter(name => !["ci", "windows-native-claim"].includes(name)) .map(name => [name, expectations[name] === "requested" ? "success" : "skipped"])); const { bad } = runGate(PR_WITHOUT_NATIVE, consistent); expect(bad).toBe(""); @@ -559,7 +617,7 @@ describe("the aggregate gate", () => { expect([outOfScope.test, outOfScope["test-changed"]]).toEqual(["not-requested", "not-requested"]); // Results consistent with the fast lane pass; a shard that ran anyway fails by name. const consistent = Object.fromEntries(Object.keys(jobs) - .filter(name => name !== "ci") + .filter(name => !["ci", "windows-native-claim"].includes(name)) .map(name => [name, fast[name] === "requested" ? "success" : "skipped"])); expect(runGate({ ...PR_WITHOUT_NATIVE, fast: "true" }, consistent).bad).toBe(""); expect(runGate({ ...PR_WITHOUT_NATIVE, fast: "true" }, { ...consistent, test: "success" }).bad).toContain("test was not requested"); @@ -627,7 +685,7 @@ describe("the aggregate gate", () => { } // Results consistent with the narrowed expectations pass; a smoke job that ran anyway fails by name. const consistent = Object.fromEntries(Object.keys(jobs) - .filter(name => name !== "ci") + .filter(name => !["ci", "windows-native-claim"].includes(name)) .map(name => [name, expectations[name] === "requested" ? "success" : "skipped"])); expect(runGate(narrow, consistent).bad).toBe(""); expect(runGate(narrow, { ...consistent, "docker-smoke": "success" }).bad).toContain("docker-smoke"); diff --git a/tests/ci-workflows/ci-workflows.test.ts b/tests/ci-workflows/ci-workflows.test.ts index 3c5036e6833..b004d4926ba 100644 --- a/tests/ci-workflows/ci-workflows.test.ts +++ b/tests/ci-workflows/ci-workflows.test.ts @@ -227,14 +227,12 @@ describe("GitHub Actions hardening", () => { expect(ci.jobs?.["platform-windows"]?.name).toBe(`windows \${{ matrix.shard }}/${windowsShards.length}`); expect(workflow).toContain(`shards=${windowsShards.length}`); - // The aggregate gate is the check a human trusts. Three ways to break it - // silently: drop `if: always()` so it skips (and a skipped job reports - // success), shrink `needs:` so it stops covering a job, or add a job and - // forget to gate it. Deriving the expected list from the workflow's own job - // keys closes all three — a hardcoded list rots on the next job added. + // The aggregate must always run for ordinary events and cover every job + // except the standalone diagnostic, whose condition is independently pinned. const gate = ci.jobs?.ci as { if?: unknown; needs?: string[] } | undefined; - expect(gate?.if).toBe("always()"); - const ungated = new Set(["ci"]); + expect(gate?.if).toBe("always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.lane != 'windows-native-claim')"); + expect(ci.jobs?.["windows-native-claim"]?.if).toBe("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev' && github.event.inputs.lane == 'windows-native-claim'"); + const ungated = new Set(["ci", "windows-native-claim"]); expect([...(gate?.needs ?? [])].sort()) .toEqual(Object.keys(ci.jobs ?? {}).filter(name => !ungated.has(name)).sort()); @@ -336,10 +334,10 @@ describe("GitHub Actions hardening", () => { // cannot fail the unsharded macOS control run. A plain dispatch still runs // everything, including Windows, which is what empty-or-all encodes. expect(ci.on?.workflow_dispatch?.inputs?.lane).toEqual({ - description: "all (default), release-gates, or macos-control", + description: "all (default), release-gates, macos-control, or windows-native-claim", type: "choice", default: "all", - options: ["all", "release-gates", "macos-control"], + options: ["all", "release-gates", "macos-control", "windows-native-claim"], }); // Windows runs the same suite, sharded like the Linux legs, and keeps the From 39ef8d73b26fde67843e36e837354cc91753c076 Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 8 Nov 2026 18:26:12 +0900 Subject: [PATCH 2/5] fix: retain Windows claim diagnostic evidence --- .github/scripts/release-evidence-policy.json | 32 ++++++++++++++ .github/workflows/ci.yml | 2 + tests/ci-workflows/ci-workflows.test.ts | 7 +++ .../main-quota-provenance.test.ts | 43 ++++++++++++++++--- .../helpers/native-main-claim-diagnostics.ts | 31 +++++++++++++ 5 files changed, 109 insertions(+), 6 deletions(-) diff --git a/.github/scripts/release-evidence-policy.json b/.github/scripts/release-evidence-policy.json index ff83e3148f9..45826c28371 100644 --- a/.github/scripts/release-evidence-policy.json +++ b/.github/scripts/release-evidence-policy.json @@ -134,5 +134,37 @@ "desktop/scripts/appimage-patchelf.py": "63e78ef74160bc48fbcdd882065f1d416db6d13c", "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" } + }, + { + "kind": "ci", + "version": 1, + "files": { + ".github/workflows/ci.yml": "562e63d6bd140944d8c6ba808010913463c75826", + ".github/actions/setup-project-bun/action.yml": "cf28f6f9d4385d69784f4e3610914f6952ce789d", + ".github/scripts/release-evidence.cjs": "c4cd8c762abbfc1c0674937535c5a36b9cf705fd", + ".github/scripts/release-evidence.test.cjs": "de30ad37105cf0e7dd8c538efb5ee1cfafe02990", + "scripts/ci/select-test-lane.sh": "b25836cb55f962d1ffd9e68ed00f45f8b45becc8", + "scripts/ci/windows-sensitive-diff.sh": "03495f256c671aff57e750febffe19ca7face6a1", + "scripts/ci/run-bun-test-batches.sh": "f8aa18e80aff1c95c584cd2c879dfbb6806c11fb", + "scripts/ci/bun-crash-signatures.sh": "d34662ddd07864d97bed909135095db33fe42c9e", + "scripts/ci/bun-install-frozen.sh": "e6d7bc3aec044e12297c7c4c04015a33df0eb22b", + "scripts/ci/docker-smoke.ts": "a8424f93986a31625a0a236cdab5667db67b3405", + "scripts/ci/test-durations.tsv": "0c7b8eeac35a5f09414d1b77185733b5936c594f", + "scripts/ci/test-durations.windows.tsv": "5ee31b6bd6cada6ab7d806ea03dd0d8e46fe1d29", + "scripts/test.ts": "363bf072c4b689478fdbb76a61bf4eadbc054548", + "scripts/lib/test-runner-bun.ts": "8763806104a851dbdefeea8cffe8d7dd8d1c8da1", + "scripts/test-temp-lock.ts": "ee83fb254b728de119028f52b9894bf52d7aea15", + "scripts/ci/run-changed-tests.sh": "9a54d3a5db9849bcbe0dd766954a840185419fa5", + "scripts/keyring-smoke.ts": "cd3be07ad57796172696dfa053ea1fb129f29365", + "scripts/privacy-scan.ts": "86314e891b45e49739b94c76cddd9e4cc2d4340f", + "scripts/structure-ssot.ts": "40ba68401df9b77156afed66d6a11c61df8e9a41", + "scripts/generate-ocx-skill-surface.ts": "750ea21e5668aa512a83aa5a7f8cd7bc5cbd79db", + "desktop/scripts/prepare-sidecar.ts": "20f7eec61f73635ea681e25f13dd850b62bb41e2", + "desktop/scripts/build-widget.sh": "38e0eb9d713804b603aa75c32383aa18ef01d06c", + "desktop/scripts/verify-macos-runtime.sh": "43f1a31375c6f6fe7dcafb9d96c231b98d1f6ba4", + "desktop/scripts/verify-linux-sidecar.sh": "c502bb5c8ba8346c722a8f5274f2274bb325a934", + "desktop/scripts/appimage-patchelf.py": "63e78ef74160bc48fbcdd882065f1d416db6d13c", + "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" + } } ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dab526d07a4..562e63d6bd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1028,6 +1028,8 @@ jobs: "OPENCODEX_HOME=$ocxHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "CODEX_HOME=$codexHome" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Run native-main claim diagnostics + env: + OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC: '1' run: bun test tests/codex-integration/main-quota-provenance.test.ts --timeout 60000 # Windows shards run only on a full manual dispatch. Pull requests retain diff --git a/tests/ci-workflows/ci-workflows.test.ts b/tests/ci-workflows/ci-workflows.test.ts index b004d4926ba..6ee7cab0002 100644 --- a/tests/ci-workflows/ci-workflows.test.ts +++ b/tests/ci-workflows/ci-workflows.test.ts @@ -232,6 +232,13 @@ describe("GitHub Actions hardening", () => { const gate = ci.jobs?.ci as { if?: unknown; needs?: string[] } | undefined; expect(gate?.if).toBe("always() && (github.event_name != 'workflow_dispatch' || github.event.inputs.lane != 'windows-native-claim')"); expect(ci.jobs?.["windows-native-claim"]?.if).toBe("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev' && github.event.inputs.lane == 'windows-native-claim'"); + const diagnosticSteps = (ci.jobs?.["windows-native-claim"] as { steps?: Array<{ name?: string; env?: Record; run?: string }> })?.steps ?? []; + const diagnosticStep = diagnosticSteps.find(step => step.name === "Run native-main claim diagnostics"); + expect(diagnosticStep?.env?.OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC).toBe("1"); + expect(diagnosticStep?.run).toBe("bun test tests/codex-integration/main-quota-provenance.test.ts --timeout 60000"); + expect(windowsTest?.env?.OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC).toBeUndefined(); + expect(diagnosticSteps.filter(step => step.env?.OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC !== undefined)).toEqual([diagnosticStep]); + expect((ci.jobs?.["windows-native-claim"] as { env?: Record }).env?.OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC).toBeUndefined(); const ungated = new Set(["ci", "windows-native-claim"]); expect([...(gate?.needs ?? [])].sort()) .toEqual(Object.keys(ci.jobs ?? {}).filter(name => !ungated.has(name)).sort()); diff --git a/tests/codex-integration/main-quota-provenance.test.ts b/tests/codex-integration/main-quota-provenance.test.ts index 043339335aa..f56eaa84519 100644 --- a/tests/codex-integration/main-quota-provenance.test.ts +++ b/tests/codex-integration/main-quota-provenance.test.ts @@ -5,7 +5,6 @@ import { Database } from "bun:sqlite"; import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { hostedWindowsQuarantine } from "../helpers/windows-ci-quarantine"; import { MAIN_CODEX_ACCOUNT_ID as MAIN } from "../../src/codex/account-id"; import { isCodexAccountHeldForCredits } from "../../src/codex/account-credit-use"; import { hasSpendableCodexCredits } from "../../src/codex/quota-types"; @@ -40,7 +39,7 @@ import { type WhamUsageResponse, } from "../../src/codex/quota"; import { COLD_SPAWN_WARMUP_HOOK_BUDGET_MS, warmModuleGraph } from "../helpers/cold-spawn-warmup"; -import { observeNativeMainClaim } from "../helpers/native-main-claim-diagnostics"; +import { formatClaimDiagnostics, observeNativeMainClaim, skipInvalidQuotaTag } from "../helpers/native-main-claim-diagnostics"; import * as nativeMainLockFile from "../../src/codex/native-main-lock-file"; import { withNativeMainExclusiveClaim, withNativeMainSharedClaim } from "../../src/codex/native-main-claim"; import type { NativeProfileContext } from "../../src/codex/native-profile-store"; @@ -370,7 +369,7 @@ describe("main policy quota durability and lifecycle", () => { { name: "startup file replacement", account: "fixture-main-b", tag: "known", selected: true, source: "file" }, { name: "startup same-account file", account: "fixture-main-a", tag: "known", selected: false, source: "file", refusal: "CodexMainAccountHardLockError" }, ]) { - test.skipIf(hostedWindowsQuarantine && scenario.name === "invalid quota tag")(`cold keyring Pool preserves quota unless replacement is proven: ${scenario.name}`, async () => { + test.skipIf(skipInvalidQuotaTag(process.platform, process.env.GITHUB_ACTIONS, process.env.OCX_TEST_WINDOWS_NATIVE_CLAIM_DIAGNOSTIC) && scenario.name === "invalid quota tag")(`cold keyring Pool preserves quota unless replacement is proven: ${scenario.name}`, async () => { const writer = writerFor("fixture-main-a"); setAccountQuotaFromParsed(MAIN, { weeklyPercent: 100, weeklyResetAt: Date.now() / 1000 + 3600 }, undefined, writer); setAccountQuotaFromParsed("fixture-stored", { weeklyPercent: 33 }); @@ -399,7 +398,8 @@ describe("main policy quota durability and lifecycle", () => { providers: { openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" } } }; let selected = false; let failure, failureDetail; - const claimObserver = observeNativeMainClaim(${JSON.stringify(scenario.injection)}); + const claimObserver = observeNativeMainClaim(${JSON.stringify(scenario.injection)} ?? (${JSON.stringify(scenario.name === "verified replacement")} && process.env.OCX_TEST_CLAIM_REGRESSION ? "open" : undefined)); + process.on("exit", () => console.log(JSON.stringify({ fixture: "claim-diagnostics", diagnostics: claimObserver.snapshot() }))); try { const result = await resolveCodexAuthContext(new Headers(), config, "pool", { nativeMainRefreshDependencies: { codexAuthStatus: async () => { reads++; return { authMethod: "chatgpt", authToken: token, requiresOpenaiAuth: true }; } }, @@ -415,6 +415,7 @@ describe("main policy quota durability and lifecycle", () => { } const claimDiagnostics = claimObserver.snapshot(); claimObserver.restore(); + if (process.env.OCX_TEST_CLAIM_REGRESSION === "exit" && ${JSON.stringify(scenario.name === "verified replacement")}) throw new Error("private-marker-child-stderr"); console.log(JSON.stringify({ fixture: "cold-keyring-quota", before, selected, failure, failureDetail, reads, initialized, claimDiagnostics, legacy: getAccountQuota("__main__"), policy: getMainPolicyQuota(), stored: getAccountQuota("fixture-stored"), @@ -422,12 +423,13 @@ describe("main policy quota durability and lifecycle", () => { `; const child = Bun.spawnSync({ cmd: [process.execPath, "--eval", script], cwd: repoRoot(), env: process.env, timeout: process.platform === "win32" ? SPAWN_BUDGET_MS - INTERNAL_DEADLINE_MS : 10_000 }); - expect(child.exitCode).toBe(0); + const diagnostics = formatClaimDiagnostics(child.stdout.toString()); + expect(child.exitCode, `claim diagnostics: ${diagnostics}`).toBe(0); const line = child.stdout.toString().split("\n").find(row => row.startsWith('{"fixture":"cold-keyring-quota"')); expect(line).toBeDefined(); const result = JSON.parse(line!); expect(result.before.weeklyPercent).toBe(100); - expect(result.selected).toBe(scenario.selected); + expect(result.selected, `claim diagnostics: ${diagnostics}`).toBe(scenario.selected); expect(result.stored.weeklyPercent).toBe(33); expect(result.paused).toEqual(scenario.paused ? [MAIN] : []); if (scenario.source === "file") expect(result.initialized).toBe(true); @@ -460,6 +462,35 @@ describe("main policy quota durability and lifecycle", () => { }, SPAWN_BUDGET_MS); } + test("invalid quota quarantine is bypassed only by the diagnostic opt-in", () => { + expect(skipInvalidQuotaTag("win32", "true", undefined)).toBe(true); + expect(skipInvalidQuotaTag("win32", "true", "1")).toBe(false); + expect(skipInvalidQuotaTag("win32", "true", "other")).toBe(true); + expect(skipInvalidQuotaTag("linux", "true", undefined)).toBe(false); + expect(skipInvalidQuotaTag("win32", undefined, undefined)).toBe(false); + }); + + test("child diagnostic rendering rejects unrecognized fields and missing records", () => { + const line = JSON.stringify({ fixture: "claim-diagnostics", diagnostics: { phase: "open", private: "private-marker", + failure: { phase: "open", code: "EICACLS", step: "private-marker", stage: "private-marker", timedOut: false, + message: "private-marker" } } }); + expect(formatClaimDiagnostics(line)).toBe('{"phase":"open","failure":{"phase":"open","code":"EICACLS","timedOut":false}}'); + expect(formatClaimDiagnostics("private-marker stderr")).toBe("claim diagnostics unavailable"); + expect(formatClaimDiagnostics('{"fixture":"claim-diagnostics",broken')).toBe("claim diagnostics unavailable"); + }); + + for (const mode of ["selection", "exit"] as const) { + test(`positive selection ${mode} failure reports sanitized claim diagnostics`, () => { + const child = Bun.spawnSync({ cmd: [process.execPath, "test", "tests/codex-integration/main-quota-provenance.test.ts", "-t", "cold keyring Pool preserves quota unless replacement is proven: verified replacement", "--timeout", "60000"], + cwd: repoRoot(), env: { ...process.env, OCX_TEST_CLAIM_REGRESSION: mode }, timeout: SPAWN_BUDGET_MS }); + const output = child.stdout.toString() + child.stderr.toString(); + expect(child.exitCode).not.toBe(0); + expect(output).toContain('"phase":"open"'); + expect(output).toContain('"code":"EICACLS"'); + expect(output).not.toContain("private-marker"); + }, SPAWN_BUDGET_MS + 10_000); + } + test("claim diagnostic wrappers rethrow the original error", async () => { const observer = observeNativeMainClaim("hardening"); try { diff --git a/tests/helpers/native-main-claim-diagnostics.ts b/tests/helpers/native-main-claim-diagnostics.ts index 305d425a6eb..a16cda04422 100644 --- a/tests/helpers/native-main-claim-diagnostics.ts +++ b/tests/helpers/native-main-claim-diagnostics.ts @@ -5,6 +5,37 @@ import * as lockFile from "../../src/codex/native-main-lock-file"; type Injection = "open" | "hardening" | "sqlite"; +export function skipInvalidQuotaTag(platform: string, githubActions: string | undefined, diagnostic: string | undefined): boolean { + return platform === "win32" && githubActions === "true" && diagnostic !== "1"; +} + +/** Accept only the observer's bounded fields, never arbitrary child output. */ +export function formatClaimDiagnostics(stdout: string): string { + const prefix = '{"fixture":"claim-diagnostics",'; + const line = stdout.split("\n").find(row => row.startsWith(prefix)); + if (!line) return "claim diagnostics unavailable"; + try { + const { diagnostics } = JSON.parse(line); + const phases = ["not-started", "open", "hardening", "sqlite-open", "sqlite-pragma", "sqlite-journal-mode", + "identity-after-hardening", "identity-after-sqlite-setup", "identity-after-transaction", "identity-unknown", + "transaction", "transaction-read", "operation"]; + const codes = ["EACCES", "EPERM", "ENOENT", "EBUSY", "ETIMEDOUT", "EICACLS", "EACLIDENTITY", + "SQLITE_BUSY", "SQLITE_LOCKED", "SQLITE_CANTOPEN", "SQLITE_NOTADB"]; + const steps = ["/grant:r", "/inheritance:r", "/remove:g", "/findsid"]; + const stages = ["deadline_before_spawn", "spawn_failed", "runner_rejected", "runner_watchdog", "child_result"]; + const failure = diagnostics?.failure; + const safeFailure = failure && typeof failure === "object" ? { + ...(phases.includes(failure.phase) ? { phase: failure.phase } : {}), + ...(codes.includes(failure.code) ? { code: failure.code } : {}), + ...(steps.includes(failure.step) ? { step: failure.step } : {}), + ...(stages.includes(failure.stage) ? { stage: failure.stage } : {}), + ...(typeof failure.timedOut === "boolean" ? { timedOut: failure.timedOut } : {}), + } : undefined; + return JSON.stringify({ ...(phases.includes(diagnostics?.phase) ? { phase: diagnostics.phase } : {}), + ...(safeFailure ? { failure: safeFailure } : {}) }); + } catch { return "claim diagnostics unavailable"; } +} + /** Observe the real claim setup without publishing paths, messages, or subprocess output. */ export function observeNativeMainClaim(inject?: Injection, testError?: Error) { let phase = "not-started"; From ef9a730366071029a3f453215b5bfdc53133ae25 Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 8 Nov 2026 18:32:23 +0900 Subject: [PATCH 3/5] Preserve claim diagnostics across child timeouts --- .../main-quota-provenance.test.ts | 23 +++++++++++++++---- .../helpers/native-main-claim-diagnostics.ts | 23 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/tests/codex-integration/main-quota-provenance.test.ts b/tests/codex-integration/main-quota-provenance.test.ts index f56eaa84519..c0041db603b 100644 --- a/tests/codex-integration/main-quota-provenance.test.ts +++ b/tests/codex-integration/main-quota-provenance.test.ts @@ -377,6 +377,8 @@ describe("main policy quota durability and lifecycle", () => { if (scenario.tag === "missing") delete snapshot.mainPolicyQuota; if (scenario.tag === "invalid") snapshot.mainPolicyQuota.identityKey = "invalid-fixture-identity"; writeSnapshot(snapshot); + const diagnosticsPath = join(testDir, "claim-diagnostics.jsonl"); + writeFileSync(diagnosticsPath, ""); const script = ` ${KEYRING_QUOTA_RESTART_IMPORTS} globalThis.fetch = async () => { throw new Error("unexpected fixture network request"); }; @@ -398,8 +400,7 @@ describe("main policy quota durability and lifecycle", () => { providers: { openai: { adapter: "openai-responses", baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" } } }; let selected = false; let failure, failureDetail; - const claimObserver = observeNativeMainClaim(${JSON.stringify(scenario.injection)} ?? (${JSON.stringify(scenario.name === "verified replacement")} && process.env.OCX_TEST_CLAIM_REGRESSION ? "open" : undefined)); - process.on("exit", () => console.log(JSON.stringify({ fixture: "claim-diagnostics", diagnostics: claimObserver.snapshot() }))); + const claimObserver = observeNativeMainClaim(${JSON.stringify(scenario.injection)} ?? (${JSON.stringify(scenario.name === "verified replacement")} && process.env.OCX_TEST_CLAIM_REGRESSION === "timeout" ? "stall" : ${JSON.stringify(scenario.name === "verified replacement")} && ["selection", "exit"].includes(process.env.OCX_TEST_CLAIM_REGRESSION) ? "open" : undefined), undefined, ${JSON.stringify(diagnosticsPath)}); try { const result = await resolveCodexAuthContext(new Headers(), config, "pool", { nativeMainRefreshDependencies: { codexAuthStatus: async () => { reads++; return { authMethod: "chatgpt", authToken: token, requiresOpenaiAuth: true }; } }, @@ -422,8 +423,9 @@ describe("main policy quota durability and lifecycle", () => { paused: config.pausedCodexAccountIds })); `; const child = Bun.spawnSync({ cmd: [process.execPath, "--eval", script], cwd: repoRoot(), env: process.env, - timeout: process.platform === "win32" ? SPAWN_BUDGET_MS - INTERNAL_DEADLINE_MS : 10_000 }); - const diagnostics = formatClaimDiagnostics(child.stdout.toString()); + timeout: process.env.OCX_TEST_CLAIM_REGRESSION === "timeout" ? 15_000 : process.platform === "win32" ? SPAWN_BUDGET_MS - INTERNAL_DEADLINE_MS : 10_000 }); + const diagnostics = formatClaimDiagnostics(readFileSync(diagnosticsPath, "utf8")); + if (process.env.OCX_TEST_CLAIM_REGRESSION === "timeout") expect(child.exitCode).toBeNull(); expect(child.exitCode, `claim diagnostics: ${diagnostics}`).toBe(0); const line = child.stdout.toString().split("\n").find(row => row.startsWith('{"fixture":"cold-keyring-quota"')); expect(line).toBeDefined(); @@ -477,6 +479,7 @@ describe("main policy quota durability and lifecycle", () => { expect(formatClaimDiagnostics(line)).toBe('{"phase":"open","failure":{"phase":"open","code":"EICACLS","timedOut":false}}'); expect(formatClaimDiagnostics("private-marker stderr")).toBe("claim diagnostics unavailable"); expect(formatClaimDiagnostics('{"fixture":"claim-diagnostics",broken')).toBe("claim diagnostics unavailable"); + expect(formatClaimDiagnostics(`${line}\n${JSON.stringify({ fixture: "claim-diagnostics", diagnostics: { phase: "hardening" } })}\n{"fixture":"claim-diagnostics",broken`)).toBe('{"phase":"hardening"}'); }); for (const mode of ["selection", "exit"] as const) { @@ -491,6 +494,18 @@ describe("main policy quota durability and lifecycle", () => { }, SPAWN_BUDGET_MS + 10_000); } + test("forced claim setup timeout reports the latest sanitized phase", () => { + const child = Bun.spawnSync({ cmd: [process.execPath, "test", "tests/codex-integration/main-quota-provenance.test.ts", "-t", "cold keyring Pool preserves quota unless replacement is proven: verified replacement", "--timeout", "60000"], + cwd: repoRoot(), env: { ...process.env, OCX_TEST_CLAIM_REGRESSION: "timeout" }, timeout: SPAWN_BUDGET_MS }); + const output = child.stdout.toString() + child.stderr.toString(); + expect(child.exitCode).not.toBe(0); + expect(output).toContain('claim diagnostics: {"phase":"hardening"}'); + expect(output).toContain("Received: null"); + expect(output).not.toContain("private-marker"); + expect(output).not.toContain("/private-claim-path"); + expect(output).not.toContain("fixture-secret-token"); + }, SPAWN_BUDGET_MS + 10_000); + test("claim diagnostic wrappers rethrow the original error", async () => { const observer = observeNativeMainClaim("hardening"); try { diff --git a/tests/helpers/native-main-claim-diagnostics.ts b/tests/helpers/native-main-claim-diagnostics.ts index a16cda04422..cd8788b0a82 100644 --- a/tests/helpers/native-main-claim-diagnostics.ts +++ b/tests/helpers/native-main-claim-diagnostics.ts @@ -1,18 +1,21 @@ import { spyOn } from "bun:test"; import { Database } from "bun:sqlite"; +import { appendFileSync } from "node:fs"; import { types } from "node:util"; import * as lockFile from "../../src/codex/native-main-lock-file"; -type Injection = "open" | "hardening" | "sqlite"; +type Injection = "open" | "hardening" | "sqlite" | "stall"; export function skipInvalidQuotaTag(platform: string, githubActions: string | undefined, diagnostic: string | undefined): boolean { return platform === "win32" && githubActions === "true" && diagnostic !== "1"; } /** Accept only the observer's bounded fields, never arbitrary child output. */ -export function formatClaimDiagnostics(stdout: string): string { +export function formatClaimDiagnostics(records: string): string { const prefix = '{"fixture":"claim-diagnostics",'; - const line = stdout.split("\n").find(row => row.startsWith(prefix)); + const line = records.split("\n").filter(row => row.startsWith(prefix)).findLast(row => { + try { JSON.parse(row); return true; } catch { return false; } + }); if (!line) return "claim diagnostics unavailable"; try { const { diagnostics } = JSON.parse(line); @@ -37,12 +40,17 @@ export function formatClaimDiagnostics(stdout: string): string { } /** Observe the real claim setup without publishing paths, messages, or subprocess output. */ -export function observeNativeMainClaim(inject?: Injection, testError?: Error) { +export function observeNativeMainClaim(inject?: Injection, testError?: Error, diagnosticsPath?: string) { let phase = "not-started"; const events: string[] = []; - function setPhase(next: string): void { phase = next; events.push(next); } let insideOpen = false; let failure: Record | undefined; + // Synchronous append makes each bounded snapshot available before a forced child termination. + function publish(): void { + if (diagnosticsPath) appendFileSync(diagnosticsPath, JSON.stringify({ fixture: "claim-diagnostics", diagnostics: { phase, failure } }) + "\n"); + } + function setPhase(next: string): void { phase = next; events.push(next); publish(); } + publish(); const ownDescriptor = Object.getOwnPropertyDescriptor; const hasOwn = Object.hasOwn; const isProxy = types.isProxy; @@ -67,6 +75,7 @@ export function observeNativeMainClaim(inject?: Injection, testError?: Error) { failure = { phase, ...(code ? { code } : {}), ...(step ? { step } : {}), ...(stage ? { stage } : {}), ...(typeof timedOut === "boolean" ? { timedOut } : {}) }; } catch { failure = { phase }; } + publish(); } const injectedError = testError ?? Object.assign(new Error("private-marker-path-token-message"), { code: "EICACLS", aclFailure: { step: "/inheritance:r", stage: "child_result", timedOut: false, stdout: "private-marker-stdout", @@ -86,6 +95,10 @@ export function observeNativeMainClaim(inject?: Injection, testError?: Error) { }); const hardenSpy = spyOn(lockFile, "hardenStableLockFile").mockImplementation(async (...args) => { setPhase("hardening"); + if (inject === "stall") { + console.error("private-marker-child-stderr /private-claim-path fixture-secret-token"); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 120_000); + } try { if (inject === "hardening") throw injectedError; await harden(...args); } catch (error) { record(error); throw error; } }); From e7ee0d5ff98641f2b106620926d090e6b2a627ef Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 8 Nov 2026 18:36:08 +0900 Subject: [PATCH 4/5] test: assert timeout without platform exit-code assumptions --- tests/codex-integration/main-quota-provenance.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/codex-integration/main-quota-provenance.test.ts b/tests/codex-integration/main-quota-provenance.test.ts index c0041db603b..61451e177ae 100644 --- a/tests/codex-integration/main-quota-provenance.test.ts +++ b/tests/codex-integration/main-quota-provenance.test.ts @@ -425,7 +425,7 @@ describe("main policy quota durability and lifecycle", () => { const child = Bun.spawnSync({ cmd: [process.execPath, "--eval", script], cwd: repoRoot(), env: process.env, timeout: process.env.OCX_TEST_CLAIM_REGRESSION === "timeout" ? 15_000 : process.platform === "win32" ? SPAWN_BUDGET_MS - INTERNAL_DEADLINE_MS : 10_000 }); const diagnostics = formatClaimDiagnostics(readFileSync(diagnosticsPath, "utf8")); - if (process.env.OCX_TEST_CLAIM_REGRESSION === "timeout") expect(child.exitCode).toBeNull(); + if (process.env.OCX_TEST_CLAIM_REGRESSION === "timeout") expect(child.exitedDueToTimeout).toBe(true); expect(child.exitCode, `claim diagnostics: ${diagnostics}`).toBe(0); const line = child.stdout.toString().split("\n").find(row => row.startsWith('{"fixture":"cold-keyring-quota"')); expect(line).toBeDefined(); @@ -500,7 +500,6 @@ describe("main policy quota durability and lifecycle", () => { const output = child.stdout.toString() + child.stderr.toString(); expect(child.exitCode).not.toBe(0); expect(output).toContain('claim diagnostics: {"phase":"hardening"}'); - expect(output).toContain("Received: null"); expect(output).not.toContain("private-marker"); expect(output).not.toContain("/private-claim-path"); expect(output).not.toContain("fixture-secret-token"); From 3ce23f89a629a0d2cd8d798f78b75bbfedd099a4 Mon Sep 17 00:00:00 2001 From: JUN Date: Sun, 8 Nov 2026 19:17:19 +0900 Subject: [PATCH 5/5] ci: remove unmerged evidence policy fingerprint --- .github/scripts/release-evidence-policy.json | 32 -------------------- 1 file changed, 32 deletions(-) diff --git a/.github/scripts/release-evidence-policy.json b/.github/scripts/release-evidence-policy.json index 45826c28371..88c10c6ccbf 100644 --- a/.github/scripts/release-evidence-policy.json +++ b/.github/scripts/release-evidence-policy.json @@ -103,38 +103,6 @@ "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" } }, - { - "kind": "ci", - "version": 1, - "files": { - ".github/workflows/ci.yml": "dab526d07a43d19d3131f46b8be06ea0d1e630b2", - ".github/actions/setup-project-bun/action.yml": "cf28f6f9d4385d69784f4e3610914f6952ce789d", - ".github/scripts/release-evidence.cjs": "c4cd8c762abbfc1c0674937535c5a36b9cf705fd", - ".github/scripts/release-evidence.test.cjs": "de30ad37105cf0e7dd8c538efb5ee1cfafe02990", - "scripts/ci/select-test-lane.sh": "b25836cb55f962d1ffd9e68ed00f45f8b45becc8", - "scripts/ci/windows-sensitive-diff.sh": "03495f256c671aff57e750febffe19ca7face6a1", - "scripts/ci/run-bun-test-batches.sh": "f8aa18e80aff1c95c584cd2c879dfbb6806c11fb", - "scripts/ci/bun-crash-signatures.sh": "d34662ddd07864d97bed909135095db33fe42c9e", - "scripts/ci/bun-install-frozen.sh": "e6d7bc3aec044e12297c7c4c04015a33df0eb22b", - "scripts/ci/docker-smoke.ts": "a8424f93986a31625a0a236cdab5667db67b3405", - "scripts/ci/test-durations.tsv": "0c7b8eeac35a5f09414d1b77185733b5936c594f", - "scripts/ci/test-durations.windows.tsv": "5ee31b6bd6cada6ab7d806ea03dd0d8e46fe1d29", - "scripts/test.ts": "363bf072c4b689478fdbb76a61bf4eadbc054548", - "scripts/lib/test-runner-bun.ts": "8763806104a851dbdefeea8cffe8d7dd8d1c8da1", - "scripts/test-temp-lock.ts": "ee83fb254b728de119028f52b9894bf52d7aea15", - "scripts/ci/run-changed-tests.sh": "9a54d3a5db9849bcbe0dd766954a840185419fa5", - "scripts/keyring-smoke.ts": "cd3be07ad57796172696dfa053ea1fb129f29365", - "scripts/privacy-scan.ts": "86314e891b45e49739b94c76cddd9e4cc2d4340f", - "scripts/structure-ssot.ts": "40ba68401df9b77156afed66d6a11c61df8e9a41", - "scripts/generate-ocx-skill-surface.ts": "750ea21e5668aa512a83aa5a7f8cd7bc5cbd79db", - "desktop/scripts/prepare-sidecar.ts": "20f7eec61f73635ea681e25f13dd850b62bb41e2", - "desktop/scripts/build-widget.sh": "38e0eb9d713804b603aa75c32383aa18ef01d06c", - "desktop/scripts/verify-macos-runtime.sh": "43f1a31375c6f6fe7dcafb9d96c231b98d1f6ba4", - "desktop/scripts/verify-linux-sidecar.sh": "c502bb5c8ba8346c722a8f5274f2274bb325a934", - "desktop/scripts/appimage-patchelf.py": "63e78ef74160bc48fbcdd882065f1d416db6d13c", - "desktop/scripts/linux-packaged-e2e.ts": "9ca92352668ff881c85caea44a9b4747bedc7e2d" - } - }, { "kind": "ci", "version": 1,