Skip to content

Merge feature/omlx-presets into dev: apply oMLX reasoning settings + … #8

Merge feature/omlx-presets into dev: apply oMLX reasoning settings + …

Merge feature/omlx-presets into dev: apply oMLX reasoning settings + … #8

Workflow file for this run

name: Dev CI
on:
push:
branches: [dev]
pull_request:
branches: [dev]
types: [opened, edited, synchronize, reopened, ready_for_review]
workflow_dispatch:
inputs:
base_ref:
description: Base branch ref whose tip must equal base_sha.
required: true
type: string
base_sha:
description: Exact 40-hex base commit SHA to compare against base_ref.
required: true
type: string
base_repository:
description: Base owner/repository containing base_ref (must be this repository).
required: true
type: string
head_sha:
description: Exact 40-hex PR head commit SHA to validate against the latest terminal-green dev state.
required: false
type: string
base_sha_override:
description: Optional exact terminal-green dev SHA; validation rejects a mismatch.
required: false
type: string
# Least privilege by default: no dev-ci job needs a write-scoped GITHUB_TOKEN.
# Every job only checks out, installs, tests, and exchanges artifacts through the
# Actions artifact API, so a read-scoped token is sufficient.
permissions:
contents: read
concurrency:
group: ${{ github.event_name == 'workflow_dispatch' && inputs.head_sha != '' && 'dev-ci-virtual-integration' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
jobs:
pr-contract-bootstrap:
name: PR contract bootstrap
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
permissions:
contents: read
pull-requests: read
env:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Validate bootstrap PR contract
shell: bash
run: |
set -euo pipefail
unset BUN_OPTIONS
empty_bunfig="$RUNNER_TEMP/gjc-pr-contract-empty-bunfig.toml"
printf '# trusted empty Bun configuration\n' > "$empty_bunfig"
bun --no-env-file --config="$empty_bunfig" -e '
const pattern = /^gajae\.pr-review-verdict\.v1 (merge-approved|merge-blocked|needs-human) sha256:([0-9a-f]{64}) reviewer:(architect|critic|human) reviewer-id:([^\s]+) evidence:(.+)$/;
const lines = (Bun.env.PR_BODY ?? "").split(/\r?\n/).map(line => line.trim()).filter(line => line.startsWith("gajae.pr-review-verdict.v1"));
if (lines.length !== 1) throw new Error(`Expected exactly one verdict line; found ${lines.length}.`);
const match = pattern.exec(lines[0]);
if (!match) throw new Error("Malformed gajae.pr-review-verdict.v1 line.");
if (Bun.env.PR_BASE_REF !== "dev") throw new Error(`PR base must be dev, not ${Bun.env.PR_BASE_REF}.`);
const [verdict, declaredDigest, , reviewerId] = match.slice(1);
if (verdict !== "merge-approved") throw new Error(`Verdict ${verdict} intentionally blocks merge.`);
if (reviewerId.toLowerCase() === (Bun.env.PR_AUTHOR ?? "").toLowerCase()) throw new Error("merge-approved cannot be self-approved.");
const run = async argv => { const child = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe" }); const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).bytes(), new Response(child.stderr).text(), child.exited]); if (exitCode !== 0) throw new Error(`${argv.join(" ")} failed: ${stderr}`); return stdout; };
const head = new TextDecoder().decode(await run(["git", "rev-parse", "HEAD"])).trim();
if (head !== Bun.env.PR_HEAD_SHA) throw new Error(`Checked-out head ${head} != event head ${Bun.env.PR_HEAD_SHA}.`);
await run(["git", "fetch", "--no-tags", "origin", Bun.env.PR_BASE_SHA]);
await run(["git", "merge-base", "--is-ancestor", Bun.env.PR_BASE_SHA, head]);
const diff = await run(["git", "diff", "--binary", "--full-index", "--no-ext-diff", `${Bun.env.PR_BASE_SHA}...${head}`]);
const digest = new Bun.CryptoHasher("sha256").update(diff).digest("hex");
if (digest !== declaredDigest) throw new Error(`Stale verdict digest ${declaredDigest}; exact digest is ${digest}.`);
const reviews = [];
for (let page = 1; ; page++) { const response = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/pulls/${Bun.env.PR_NUMBER}/reviews?per_page=100&page=${page}`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } }); if (!response.ok) throw new Error(`Reviews API failed: ${response.status}`); const batch = await response.json(); reviews.push(...batch); if (batch.length < 100) break; }
const latest = reviews.filter(review => review.user?.login?.toLowerCase() === reviewerId.toLowerCase() && review.state !== "COMMENTED" && review.commit_id === head).at(-1);
if (latest?.state !== "APPROVED" || latest.commit_id !== head) throw new Error(`Reviewer ${reviewerId} lacks an effective exact-head approval.`);
const permissionResponse = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/collaborators/${encodeURIComponent(reviewerId)}/permission`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } });
if (!permissionResponse.ok) throw new Error(`Reviewer permission lookup failed: ${permissionResponse.status}`);
const permission = (await permissionResponse.json()).permission;
if (!["admin", "maintain", "write"].includes(permission)) throw new Error(`Reviewer ${reviewerId} lacks repository review authority.`);
console.log(`Bootstrap PR contract valid: ${digest}`);
'
- name: Run bootstrap G1 writer gate
run: bun scripts/verify-gjc-state-writers.ts --fail --root .
# Planner: resolve changed-path relevance and the affected task plan once, then
# fan the plan out into per-task shards. Emits the matrix, has_tasks/has_native
# flags, and the resolved changed paths so every downstream job reuses this
# exact diff via CI_DEV_CHANGED_PATHS instead of re-resolving the base ref.
affected-plan:
name: Affected path validation / plan
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
relevant: ${{ steps.relevance.outputs.relevant }}
matrix: ${{ steps.plan.outputs.matrix }}
has_tasks: ${{ steps.plan.outputs.has_tasks }}
has_native: ${{ steps.plan.outputs.has_native }}
has_python: ${{ steps.plan.outputs.has_python }}
has_risk_canaries: ${{ steps.plan.outputs.has_risk_canaries }}
has_darwin_arm64_tab_worker_smoke: ${{ steps.plan.outputs.has_darwin_arm64_tab_worker_smoke }}
has_windows_session_path: ${{ steps.plan.outputs.has_windows_session_path }}
changed_paths: ${{ steps.plan.outputs.changed_paths }}
plan_mode: ${{ steps.plan.outputs.plan_mode }}
plan_digest: ${{ steps.plan.outputs.plan_digest }}
plan_source_sha: ${{ steps.plan.outputs.plan_source_sha }}
env:
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
GITHUB_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.base_sha || github.event.before }}
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
- name: Verify PR head contains exact base
if: ${{ github.event_name == 'pull_request' }}
shell: bash
run: |
set -euo pipefail
if ! git fetch --no-tags origin "${GITHUB_BASE_SHA}"; then
echo "::error::Could not fetch immutable event base ${GITHUB_BASE_SHA} from ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}; rerun the PR event or rebase onto current ${GITHUB_BASE_REF}."
exit 1
fi
if git merge-base --is-ancestor "${GITHUB_BASE_SHA}" HEAD; then
:
else
status=$?
if [ "$status" -eq 1 ]; then
echo "::error::Exact-head CI requires this PR head to contain base ${GITHUB_BASE_SHA}; rebase onto current ${GITHUB_BASE_REF}."
else
echo "::error::Could not compare exact PR head ${CI_DEV_SOURCE_SHA} with immutable event base ${GITHUB_BASE_SHA} (merge-base exit ${status})."
fi
exit 1
fi
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
# Released CHANGELOG sections are append-only. These files have no
# `merge=union` driver, so a rebase conflicts here for real and a bad
# resolution can silently drop the whole file — which is exactly what
# happened to ten open PRs across six authors within ten minutes of the
# driver being removed. Runs in affected-plan because it already has
# full history and the immutable event base sha.
- name: Guard released CHANGELOG history
if: ${{ github.event_name == 'pull_request' }}
run: bun scripts/changelog-history-guard.ts
- name: Compute changed-path relevance
id: relevance
run: bun scripts/ci-job-relevance.ts
- name: Compute affected task matrix
id: plan
run: bun scripts/ci-dev-affected.ts --matrix-json
- name: Upload canonical affected plan
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dev-affected-plan-${{ github.run_id }}
path: .ci-dev-affected-plan.json
include-hidden-files: true
if-no-files-found: error
retention-days: 1
overwrite: true
telegram-daemon-generation:
name: Telegram daemon generation guard
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json')) }}
runs-on: ubuntu-22.04
timeout-minutes: 15
env:
GITHUB_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.base_sha || github.event.before }}
GITHUB_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || github.event_name == 'workflow_dispatch' && inputs.base_ref || github.ref_name }}
HEAD_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }}
BASE_REPOSITORY: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.repo.full_name || github.event_name == 'workflow_dispatch' && inputs.base_repository || github.repository }}
HEAD_REPOSITORY: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
GUARD_EVENT_NAME: ${{ github.event_name }}
GUARD_REPOSITORY: ${{ github.repository }}
steps:
- name: Validate exact guard inputs
shell: bash
run: |
set -euo pipefail
sha='^[0-9a-fA-F]{40}$'
repo='^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$'
[[ "${GITHUB_BASE_SHA}" =~ ${sha} ]] || { echo "Guard base SHA must be an exact 40-hex commit"; exit 1; }
[[ "${GITHUB_HEAD_SHA}" =~ ${sha} ]] || { echo "Guard head SHA must be an exact 40-hex commit"; exit 1; }
[[ "${BASE_REPOSITORY}" =~ ${repo} ]] || { echo "Guard base repository is invalid"; exit 1; }
[[ "${HEAD_REPOSITORY}" =~ ${repo} ]] || { echo "Guard head repository is invalid"; exit 1; }
[[ "${GUARD_REPOSITORY}" =~ ${repo} ]] || { echo "Guard repository is invalid"; exit 1; }
git check-ref-format --branch "${BASE_REF}" >/dev/null || { echo "Guard base ref is not a valid branch ref"; exit 1; }
git check-ref-format --branch "${HEAD_REF}" >/dev/null || { echo "Guard head ref is not a valid branch ref"; exit 1; }
case "${GUARD_EVENT_NAME}" in
pull_request) [[ "${BASE_REPOSITORY}" == "${GUARD_REPOSITORY}" ]] || { echo "PR base repository must be this repository"; exit 1; } ;;
push|workflow_dispatch) [[ "${BASE_REPOSITORY}" == "${GUARD_REPOSITORY}" && "${HEAD_REPOSITORY}" == "${GUARD_REPOSITORY}" ]] || { echo "Push and dispatch repositories must be this repository"; exit 1; } ;;
*) echo "Unsupported guard event"; exit 1 ;;
esac
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: ${{ env.HEAD_REPOSITORY }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: Verify checked-out source head
shell: bash
run: |
set -euo pipefail
head="$(git rev-parse HEAD)"
[[ "${head}" == "${GITHUB_HEAD_SHA}" ]] || { echo "Checked-out SHA ${head} does not match ${GITHUB_HEAD_SHA}"; exit 1; }
- name: Fetch and prove authoritative guard revisions
shell: bash
run: |
set -euo pipefail
git remote add guard-head "https://github.com/${HEAD_REPOSITORY}.git"
git remote add guard-base "https://github.com/${BASE_REPOSITORY}.git"
git fetch --no-tags guard-head "refs/heads/${HEAD_REF}:refs/remotes/guard-head/${HEAD_REF}"
base_ref_sha=''
case "${GUARD_EVENT_NAME}" in
pull_request)
# The immutable event base object remains authoritative while a queued
# pull request's live base branch advances.
git fetch --no-tags guard-base "${GITHUB_BASE_SHA}"
;;
workflow_dispatch)
git fetch --no-tags guard-base "refs/heads/${BASE_REF}:refs/remotes/guard-base/${BASE_REF}"
base_ref_sha="$(git rev-parse --verify "refs/remotes/guard-base/${BASE_REF}^{commit}")"
[[ "${base_ref_sha}" == "${GITHUB_BASE_SHA}" ]] || { echo "Dispatch base ref ${BASE_REF} resolves to ${base_ref_sha}, not ${GITHUB_BASE_SHA}"; exit 1; }
;;
push)
git fetch --no-tags guard-base "${GITHUB_BASE_SHA}"
;;
esac
{
echo "GUARD_CHECKED_OUT_HEAD=$(git rev-parse --verify HEAD^{commit})"
echo "GUARD_HEAD_REF_SHA=$(git rev-parse --verify "refs/remotes/guard-head/${HEAD_REF}^{commit}")"
echo "GUARD_BASE_OBJECT_SHA=$(git rev-parse --verify "${GITHUB_BASE_SHA}^{commit}")"
echo "GUARD_BASE_REF_SHA=${base_ref_sha}"
} >> "${GITHUB_ENV}"
printf 'guard evidence: base %s@%s; head %s@%s\n' "${BASE_REPOSITORY}" "${GITHUB_BASE_SHA}" "${HEAD_REPOSITORY}" "${HEAD_REF}"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
# The guard's AST canonicalization is parser-version sensitive: install the
# pinned @babel/parser from the lockfile so the current-tree digest check is
# deterministic and matches the committed attestations (no auto-install drift).
- run: bun install --frozen-lockfile
- run: bun scripts/telegram-daemon-generation-guard.ts --check-authority
- run: bun scripts/telegram-daemon-generation-guard.ts
windows-dev-doctor:
name: Windows dev:doctor + session-path regression
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts')) }}
runs-on: windows-latest
timeout-minutes: 60
env:
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: pwsh
run: |
$head = (git rev-parse HEAD).Trim()
if ($head -ne $env:CI_DEV_SOURCE_SHA) { throw "Checked-out SHA $head does not match $env:CI_DEV_SOURCE_SHA" }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- name: Prepend rustup toolchain bin to PATH
shell: bash
run: |
toolchain_bin="$(dirname "$(rustup which cargo)")"
echo "$toolchain_bin" >> "$GITHUB_PATH"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: windows-dev-doctor-win32-x64
cache-on-failure: true
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- run: bun install --frozen-lockfile
- name: Build native addon (win32-x64 baseline)
env:
TARGET_PLATFORM: win32
TARGET_ARCH: x64
TARGET_VARIANTS: baseline
run: bun run ci:build:native
- name: Verify Windows workspace shim and doctor
shell: pwsh
run: |
bun test ./scripts/dev-link.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun run dev:doctor
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Windows session-path canonicalization regression
shell: pwsh
run: |
bun test ./packages/coding-agent/test/session-manager/windows-canonical-path.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/session/managed-lock-lease.windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/sdk-session-directory.windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/sdk-session-index-fsync.windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
windows-native-build-toolchain:
name: Windows native build toolchain path
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/rust-toolchain-path.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/build-native-profile.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts')) }}
runs-on: windows-latest
timeout-minutes: 60
env:
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: pwsh
run: |
$head = (git rev-parse HEAD).Trim()
if ($head -ne $env:CI_DEV_SOURCE_SHA) { throw "Checked-out SHA $head does not match $env:CI_DEV_SOURCE_SHA" }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- run: bun install --frozen-lockfile
- name: Run native build toolchain tests
run: bun test packages/natives/test/build-native-profile.test.ts
- name: Build native addon (win32-x64 baseline)
env:
TARGET_PLATFORM: win32
TARGET_ARCH: x64
TARGET_VARIANTS: baseline
run: bun run ci:build:native
windows-telegram-daemon-safety:
name: Windows Telegram daemon safety
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon-control.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'daemon-control.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'notifications-telegram-daemon.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/ps.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-shell/src/process.rs') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.d.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.js') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/path-identity-windows.test.ts')) }}
runs-on: windows-latest
timeout-minutes: 60
env:
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: pwsh
run: |
$head = (git rev-parse HEAD).Trim()
if ($head -ne $env:CI_DEV_SOURCE_SHA) { throw "Checked-out SHA $head does not match $env:CI_DEV_SOURCE_SHA" }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- name: Prepend rustup toolchain bin to PATH
shell: bash
run: |
toolchain_bin="$(dirname "$(rustup which cargo)")"
echo "$toolchain_bin" >> "$GITHUB_PATH"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: windows-telegram-daemon-safety-win32-x64
cache-on-failure: true
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- run: bun install --frozen-lockfile
- name: Build native addon (win32-x64 baseline)
env:
TARGET_PLATFORM: win32
TARGET_ARCH: x64
TARGET_VARIANTS: baseline
run: bun run ci:build:native
- name: Run Windows daemon provenance safety contract
shell: pwsh
run: |
bun test ./packages/coding-agent/test/daemon-control.test.ts --test-name-pattern 'incarnation|captured-owner|owner-lock|poll overlap|configured chat providers|hard Windows authority'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/notifications-telegram-daemon.test.ts --test-name-pattern 'provider owner state contains transport authority|constructing or restarting provider transport cannot mutate session lifecycle'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/natives/test/native.test.ts --test-name-pattern 'signals only the pinned root process'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/natives/test/path-identity-windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Native addon build runs at most once per run and publishes the built `.node`
# files as an artifact the runtime-dependent shards download. A content-hash
# cache of the native sources lets PRs that don't touch crates/natives restore
# the prebuilt addon and skip the Rust/native source build entirely; only a
# cache miss (native source actually changed) recompiles. Skipped when the plan
# needs no native build at all.
affected-native:
name: Affected path validation / native-build
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.has_native == 'true' }}
runs-on: ubuntu-22.04
timeout-minutes: 30
env:
CI_DEV_CHANGED_PATHS: ${{ needs.affected-plan.outputs.changed_paths }}
CI_DEV_PLAN_MODE: ${{ needs.affected-plan.outputs.plan_mode }}
CI_DEV_AFFECTED_PLAN: .ci-dev-affected-plan.json
CI_DEV_PLAN_DIGEST: ${{ needs.affected-plan.outputs.plan_digest }}
CI_DEV_PLAN_SOURCE_SHA: ${{ needs.affected-plan.outputs.plan_source_sha }}
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Download and validate canonical affected plan
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-plan-${{ github.run_id }}
path: .
- run: bun scripts/ci-dev-affected.ts --validate-plan
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: dev-affected-native-linux-x64
cache-on-failure: true
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- name: Install system deps
run: bash scripts/ci-install-system-deps.sh
- run: bun install --frozen-lockfile
- name: Build affected native addon(s)
run: bun scripts/ci-dev-affected.ts --native-build
- name: Verify required native addon variants
run: |
test -f packages/natives/native/pi_natives.linux-x64-baseline.node
test -f packages/natives/native/pi_natives.linux-x64-modern.node
- name: Upload native addon(s)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dev-affected-native-${{ github.run_id }}
path: |
packages/natives/native/pi_natives.linux-x64-baseline.node
packages/natives/native/pi_natives.linux-x64-modern.node
if-no-files-found: error
retention-days: 1
overwrite: true
# Darwin arm64 builds the checked-out PR head end-to-end for every compiled
# tab-worker smoke-graph path. The planner emits this canonical relevance flag.
affected-darwin-arm64-tab-worker-smoke:
name: Affected path validation / darwin-arm64 tab-worker smoke
needs: [affected-plan]
if: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' }}
runs-on: macos-14
timeout-minutes: 45
env:
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
node -e 'if (process.platform !== "darwin" || process.arch !== "arm64") { throw new Error(`Expected darwin/arm64, got ${process.platform}/${process.arch}`) }'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: dev-affected-darwin-arm64-tab-worker
cache-on-failure: true
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- run: bun install --frozen-lockfile
- name: Build native addon (darwin-arm64)
env:
TARGET_PLATFORM: darwin
TARGET_ARCH: arm64
run: bun run ci:build:native
- name: Build darwin-arm64 coding-agent binary
run: bun --cwd=packages/coding-agent run build
- name: Smoke compiled tab worker with fresh owner directories
shell: bash
run: |
runtime_dir="$(mktemp -d)"
mkdir -p "$runtime_dir/home" "$runtime_dir/xdg"
HOME="$runtime_dir/home" XDG_CONFIG_HOME="$runtime_dir/xdg/config" XDG_DATA_HOME="$runtime_dir/xdg/data" XDG_CACHE_HOME="$runtime_dir/xdg/cache" packages/coding-agent/dist/gjc --smoke-test
printf 'CI_DEV_DARWIN_SMOKE_HOME=%s\n' "$runtime_dir/home" >> "$GITHUB_ENV"
printf 'CI_DEV_DARWIN_SMOKE_XDG_CONFIG_HOME=%s\n' "$runtime_dir/xdg/config" >> "$GITHUB_ENV"
printf 'CI_DEV_DARWIN_SMOKE_XDG_DATA_HOME=%s\n' "$runtime_dir/xdg/data" >> "$GITHUB_ENV"
printf 'CI_DEV_DARWIN_SMOKE_XDG_CACHE_HOME=%s\n' "$runtime_dir/xdg/cache" >> "$GITHUB_ENV"
- name: Write immutable Darwin smoke receipt
env:
CI_DEV_DARWIN_BINARY: packages/coding-agent/dist/gjc
CI_DEV_DARWIN_NATIVE_ADDON: packages/natives/native/pi_natives.darwin-arm64.node
run: bun scripts/ci-validate-darwin-receipt.ts --write
- name: Upload Darwin smoke receipt
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dev-affected-darwin-receipt-${{ github.run_id }}
path: .ci-dev-darwin-arm64-receipt.json
include-hidden-files: true
if-no-files-found: error
retention-days: 1
overwrite: true
# One shard per planned task on the broad runner. Native build tasks are
# excluded (they run in affected-native); shards that load the native addon at
# runtime download the prebuilt artifact instead of rebuilding it.
affected-shards:
name: Affected path validation / ${{ matrix.key }}
needs: [affected-plan, affected-native]
if: ${{ always() && needs.affected-plan.outputs.has_tasks == 'true' && needs.affected-native.result != 'failure' && needs.affected-native.result != 'cancelled' }}
runs-on: ubuntu-22.04
# Broad push-mode coding-agent/root test shards can need up to 90 minutes, but
# the bounded root-check must retain the same 30-minute fail-fast contract as
# Main CI. SDK closure remains outside that CI command.
timeout-minutes: ${{ matrix.key == 'root-check' && 30 || 90 }}
strategy:
fail-fast: false
max-parallel: 8
matrix: ${{ fromJSON(needs.affected-plan.outputs.matrix) }}
env:
CI_DEV_CHANGED_PATHS: ${{ needs.affected-plan.outputs.changed_paths }}
CI_DEV_PLAN_MODE: ${{ needs.affected-plan.outputs.plan_mode }}
CI_DEV_AFFECTED_PLAN: .ci-dev-affected-plan.json
CI_DEV_PLAN_DIGEST: ${{ needs.affected-plan.outputs.plan_digest }}
CI_DEV_PLAN_SOURCE_SHA: ${{ needs.affected-plan.outputs.plan_source_sha }}
CI_DEV_MATRIX_KEY: ${{ matrix.key }}
CI_DEV_MATRIX_IDENTITY: ${{ matrix.identity }}
CI_DEV_SHARD_INDEX: ${{ strategy.job-index }}
CI_DEV_MATRIX_RUST: ${{ matrix.rust }}
CI_DEV_MATRIX_NEXTEST: ${{ matrix.nextest }}
CI_DEV_MATRIX_NATIVE: ${{ matrix.native }}
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Download and validate canonical affected plan
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-plan-${{ github.run_id }}
path: .
- run: bun scripts/ci-dev-affected.ts --validate-plan
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
if: ${{ matrix.rust }}
with:
toolchain: nightly-2026-04-29
- uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2
if: ${{ matrix.nextest }}
with:
tool: nextest@0.9.137
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: ${{ matrix.rust }}
with:
shared-key: dev-affected-rust-linux-x64
cache-on-failure: true
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- name: Install system deps
run: bash scripts/ci-install-system-deps.sh
- run: bun install --frozen-lockfile
- name: Download native addon(s)
if: ${{ matrix.native }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-native-${{ github.run_id }}
path: packages/natives/native
- name: Run affected task shard
env:
AFFECTED_TASK_KEY: ${{ matrix.key }}
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
GITHUB_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.base_sha || github.event.before }}
run: bun scripts/ci-dev-affected.ts --task="$AFFECTED_TASK_KEY"
- name: Write shard completion receipt
run: |
bun -e 'await Bun.write(`.ci-dev-shard-receipts/${process.env.CI_DEV_SHARD_INDEX}.json`, JSON.stringify({ key: process.env.AFFECTED_TASK_KEY, identity: process.env.CI_DEV_MATRIX_IDENTITY }))'
env:
AFFECTED_TASK_KEY: ${{ matrix.key }}
- name: Upload shard completion receipt
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dev-affected-shard-${{ github.run_id }}-${{ strategy.job-index }}
path: .ci-dev-shard-receipts/${{ strategy.job-index }}.json
include-hidden-files: true
if-no-files-found: error
retention-days: 1
overwrite: true
# This producer is deliberately not the protected status: the downstream job
# validates the finalized bundle downloaded by its immutable artifact ID.
affected-evidence-producer:
name: Affected path validation / evidence producer
if: ${{ always() && !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
needs: [affected-plan, affected-native, affected-shards, telegram-daemon-generation, windows-dev-doctor, windows-native-build-toolchain, windows-telegram-daemon-safety, affected-darwin-arm64-tab-worker-smoke]
runs-on: ubuntu-22.04
timeout-minutes: 5
outputs:
artifact_id: ${{ steps.upload-evidence.outputs.artifact-id }}
artifact_digest: ${{ steps.upload-evidence.outputs.artifact-digest }}
env:
CI_DEV_AFFECTED_PLAN: .ci-dev-affected-plan.json
CI_DEV_PLAN_DIGEST: ${{ needs.affected-plan.outputs.plan_digest }}
CI_DEV_PLAN_SOURCE_SHA: ${{ needs.affected-plan.outputs.plan_source_sha }}
CI_DEV_PLAN_MODE: ${{ needs.affected-plan.outputs.plan_mode }}
CI_DEV_SHARD_RECEIPTS: .ci-dev-shard-receipts
CI_DEV_EVIDENCE_ROOT: .
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Download canonical affected plan
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-plan-${{ github.run_id }}
path: .
- name: Download shard completion receipts
if: ${{ needs.affected-plan.result == 'success' && needs.affected-plan.outputs.has_tasks == 'true' && needs.affected-shards.result == 'success' }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: dev-affected-shard-${{ github.run_id }}-*
path: .ci-dev-shard-receipts
merge-multiple: true
- name: Validate canonical shard completion
if: ${{ needs.affected-plan.result == 'success' && needs.affected-plan.outputs.has_tasks == 'true' && needs.affected-shards.result == 'success' }}
run: bun scripts/ci-dev-affected.ts --validate-shard-receipts
- name: Download Darwin smoke receipt
if: ${{ needs.affected-plan.result == 'success' && needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' && needs.affected-darwin-arm64-tab-worker-smoke.result == 'success' }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-darwin-receipt-${{ github.run_id }}
path: .
- name: Validate Darwin smoke receipt
if: ${{ needs.affected-plan.result == 'success' && needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' && needs.affected-darwin-arm64-tab-worker-smoke.result == 'success' }}
run: bun scripts/ci-validate-darwin-receipt.ts
- name: Produce affected evidence
env:
CI_DEV_PLAN_RESULT: ${{ needs.affected-plan.result }}
CI_DEV_NATIVE_RESULT: ${{ needs.affected-native.result }}
CI_DEV_SHARDS_RESULT: ${{ needs.affected-shards.result }}
CI_DEV_HAS_NATIVE: ${{ needs.affected-plan.outputs.has_native }}
CI_DEV_HAS_TASKS: ${{ needs.affected-plan.outputs.has_tasks }}
CI_DEV_HAS_PYTHON: ${{ needs.affected-plan.outputs.has_python }}
CI_DEV_PYTHON_RESULT: skipped
CI_DEV_WINDOWS_DOCTOR_RESULT: ${{ needs.windows-dev-doctor.result }}
CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_RESULT: ${{ needs.windows-native-build-toolchain.result }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/rust-toolchain-path.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/build-native-profile.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts') }}
CI_DEV_TELEGRAM_GUARD_RESULT: ${{ needs.telegram-daemon-generation.result }}
CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json') }}
CI_DEV_TELEGRAM_WINDOWS_RESULT: ${{ needs.windows-telegram-daemon-safety.result }}
CI_DEV_TELEGRAM_WINDOWS_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon-control.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'daemon-control.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'notifications-telegram-daemon.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/ps.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-shell/src/process.rs') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.d.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.js') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/path-identity-windows.test.ts') }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_RESULT: ${{ needs.affected-darwin-arm64-tab-worker-smoke.result }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_REQUIRED: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke }}
run: bun scripts/ci-dev-affected.ts --write-affected-evidence
- name: Upload affected evidence
id: upload-evidence
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dev-affected-evidence-${{ github.run_id }}
path: |
.ci-dev-affected-evidence.json
.ci-dev-affected-evidence.receipt.json
.ci-dev-affected-plan.json
.ci-dev-shard-receipts
.ci-dev-darwin-arm64-receipt.json
include-hidden-files: true
if-no-files-found: error
retention-days: 1
overwrite: true
affected:
name: Affected path validation
if: ${{ always() && !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
needs: [affected-evidence-producer, affected-plan, affected-native, affected-shards, telegram-daemon-generation, windows-dev-doctor, windows-native-build-toolchain, windows-telegram-daemon-safety, affected-darwin-arm64-tab-worker-smoke]
runs-on: ubuntu-22.04
timeout-minutes: 5
env:
CI_DEV_PLAN_DIGEST: ${{ needs.affected-plan.outputs.plan_digest }}
CI_DEV_PLAN_SOURCE_SHA: ${{ needs.affected-plan.outputs.plan_source_sha }}
CI_DEV_PLAN_MODE: ${{ needs.affected-plan.outputs.plan_mode }}
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
CI_DEV_PLAN_RESULT: ${{ needs.affected-plan.result }}
CI_DEV_NATIVE_RESULT: ${{ needs.affected-native.result }}
CI_DEV_SHARDS_RESULT: ${{ needs.affected-shards.result }}
CI_DEV_HAS_NATIVE: ${{ needs.affected-plan.outputs.has_native }}
CI_DEV_HAS_TASKS: ${{ needs.affected-plan.outputs.has_tasks }}
CI_DEV_HAS_PYTHON: ${{ needs.affected-plan.outputs.has_python }}
CI_DEV_PYTHON_RESULT: skipped
CI_DEV_WINDOWS_DOCTOR_RESULT: ${{ needs.windows-dev-doctor.result }}
CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_RESULT: ${{ needs.windows-native-build-toolchain.result }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/rust-toolchain-path.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/build-native-profile.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts') }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_RESULT: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' && 'success' || 'skipped' }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_REQUIRED: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke }}
CI_DEV_TELEGRAM_GUARD_RESULT: ${{ needs.telegram-daemon-generation.result }}
CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json') }}
CI_DEV_TELEGRAM_WINDOWS_RESULT: ${{ needs.windows-telegram-daemon-safety.result }}
CI_DEV_TELEGRAM_WINDOWS_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon-control.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'daemon-control.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'notifications-telegram-daemon.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/ps.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-shell/src/process.rs') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.d.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.js') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/path-identity-windows.test.ts') }}
steps:
- name: Fail closed on producer and live dependency results
env:
CI_DEV_EVIDENCE_ROOT: ${{ runner.temp }}/ci-dev-affected-evidence
shell: bash
run: |
test '${{ needs.affected-evidence-producer.result }}' = success
test '${{ needs.affected-plan.result }}' = success
test '${{ needs.affected-evidence-producer.outputs.artifact_id }}' != ''
test '${{ needs.affected-evidence-producer.outputs.artifact_digest }}' != ''
test "$CI_DEV_EVIDENCE_ROOT" != "$GITHUB_WORKSPACE"
case "$CI_DEV_EVIDENCE_ROOT" in "$GITHUB_WORKSPACE"/*) exit 1;; esac
rm -rf "$CI_DEV_EVIDENCE_ROOT"
mkdir -p "$CI_DEV_EVIDENCE_ROOT"
- name: Download finalized affected evidence
# download-artifact selects the immutable upload by artifact ID; the pinned
# action exposes no downloaded digest output to compare, so artifact_digest
# remains a required producer audit binding rather than a path selector.
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
artifact-ids: ${{ needs.affected-evidence-producer.outputs.artifact_id }}
path: ${{ runner.temp }}/ci-dev-affected-evidence
merge-multiple: true
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Validate finalized Darwin smoke receipt
if: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' }}
env:
CI_DEV_DARWIN_RECEIPT: ${{ runner.temp }}/ci-dev-affected-evidence/.ci-dev-darwin-arm64-receipt.json
run: bun scripts/ci-validate-darwin-receipt.ts
- name: Validate finalized affected evidence
env:
CI_DEV_EVIDENCE_ROOT: ${{ runner.temp }}/ci-dev-affected-evidence
run: bun scripts/ci-dev-affected.ts --validate-affected-evidence
- name: Validate live affected aggregate
env:
CI_DEV_AFFECTED_PLAN: ${{ runner.temp }}/ci-dev-affected-evidence/.ci-dev-affected-plan.json
run: bun scripts/ci-dev-affected.ts --validate-aggregate
gjc-state-gates-matrix:
name: gjc-state-gates / ${{ matrix.group }}
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
group: [static, runtime, integrity, read]
env:
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
GITHUB_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event_name == 'workflow_dispatch' && inputs.base_sha || github.event.before }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Restore bun dependency cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-1.3.14-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
restore-keys: |
bun-1.3.14-${{ runner.os }}-
- run: bun install --frozen-lockfile
- name: Run GJC state gate shard
run: bun scripts/ci-gjc-state-gates.ts --group=${{ matrix.group }}
# Branch protection must keep requiring this stable aggregate status.
gjc-state-gates:
name: gjc-state-gates
if: ${{ always() && !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') }}
needs: [gjc-state-gates-matrix]
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Aggregate GJC state gate shards
run: |
result='${{ needs.gjc-state-gates-matrix.result }}'
echo "gjc-state-gates shard result: $result"
test "$result" = success
# Stable pre-merge status: this runs for every PR after the protected affected
# aggregate, then only materializes a virtual merge when the canonical planner
# selected risk canaries. A dispatch remains available for exact-head bootstrap
# evidence. Every merge candidate shares one non-cancelling lane: a candidate
# selects and materializes against dev, so overlapping candidates could otherwise
# validate against incompatible integration state. Keep up to 100 candidates
# pending instead of replacing an older pending validation.
virtual-integration:
name: Virtual integration validation
needs: [affected-plan, affected]
if: ${{ always() && ((github.event_name == 'pull_request' && needs.affected.result == 'success') || (github.event_name == 'workflow_dispatch' && inputs.head_sha != '')) }}
concurrency:
group: dev-ci-virtual-integration
cancel-in-progress: false
queue: max
runs-on: ubuntu-22.04
timeout-minutes: 30
permissions:
contents: read
actions: read
env:
CI_VI_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || inputs.head_sha }}
# CI_VI_BASE_SHA is set per-step from the authoritative terminal-green
# dev base selected by --select-base, never from the stale PR event base.
CI_VI_REQUIRED: ${{ github.event_name == 'workflow_dispatch' && 'true' || needs.affected-plan.outputs.has_risk_canaries }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || inputs.head_sha }}
- name: Verify checked-out source head
shell: bash
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_VI_HEAD_SHA" || { echo "Checked-out SHA $head does not match $CI_VI_HEAD_SHA"; exit 1; }
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Select authoritative terminal-green dev base
id: green-dev
env:
GH_TOKEN: ${{ github.token }}
CI_VI_HEAD_SHA: ${{ env.CI_VI_HEAD_SHA }}
shell: bash
run: |
set -euo pipefail
bun scripts/ci-virtual-integration.ts --select-base |
{
base_sha=""
base_run_id=""
base_conclusion=""
while IFS='=' read -r key value; do
case "$key" in
authority_base_sha) base_sha="$value" ;;
authority_base_run_id) base_run_id="$value" ;;
authority_base_conclusion) base_conclusion="$value" ;;
esac
done
test -n "$base_sha" || { echo "::error::No authoritative terminal-green dev base selected"; exit 1; }
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
echo "base_run_id=$base_run_id" >> "$GITHUB_OUTPUT"
echo "base_conclusion=$base_conclusion" >> "$GITHUB_OUTPUT"
}
- name: Fetch and verify authoritative base
env:
CI_VI_AUTHORITY_BASE_SHA: ${{ steps.green-dev.outputs.base_sha }}
run: |
git fetch --no-tags origin "${CI_VI_AUTHORITY_BASE_SHA}:refs/remotes/origin/ci-virtual-base"
fetched="$(git rev-parse origin/ci-virtual-base)"
test "$fetched" = "$CI_VI_AUTHORITY_BASE_SHA" || { echo "::error::Fetched base $fetched != $CI_VI_AUTHORITY_BASE_SHA"; exit 1; }
- name: Validate virtual integration evidence
if: ${{ env.CI_VI_REQUIRED == 'true' }}
env:
CI_VI_BASE_SHA: ${{ steps.green-dev.outputs.base_sha }}
CI_VI_BASE_SHA_OVERRIDE: ${{ inputs.base_sha_override }}
CI_VI_BASE_RUN_ID: ${{ steps.green-dev.outputs.base_run_id }}
CI_VI_BASE_CONCLUSION: ${{ steps.green-dev.outputs.base_conclusion }}
run: bun scripts/ci-virtual-integration.ts --validate
- name: Provision pinned Rust toolchain for canary native build
if: ${{ env.CI_VI_REQUIRED == 'true' }}
uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly-2026-04-29
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: ${{ env.CI_VI_REQUIRED == 'true' }}
with:
shared-key: dev-virtual-integration-linux-x64
- name: Run risk-selected canaries in the materialized merge
if: ${{ env.CI_VI_REQUIRED == 'true' }}
env:
CI_VI_BASE_SHA: ${{ steps.green-dev.outputs.base_sha }}
CI_VI_BASE_RUN_ID: ${{ steps.green-dev.outputs.base_run_id }}
run: bun scripts/ci-virtual-integration.ts --run-canaries
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: ${{ env.CI_VI_REQUIRED == 'true' }}
with:
name: dev-virtual-integration-${{ github.run_id }}
path: .ci-virtual-integration.json
include-hidden-files: true
if-no-files-found: error
retention-days: 1
overwrite: true