Skip to content

feat(coding-agent): complete memory pressure guard implementation #7

feat(coding-agent): complete memory pressure guard implementation

feat(coding-agent): complete memory pressure guard implementation #7

Workflow file for this run

name: Dev CI
on:
push:
branches: [dev]
pull_request:
branches: [dev]
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
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# 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
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_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; }
- 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: 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' }}
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') }}
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 }
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')) }}
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.event.pull_request.head.sha || github.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'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test packages/coding-agent/test/notifications-telegram-daemon.test.ts --test-name-pattern 'Windows production preflight|concurrent ensureTelegramDaemonRunning|stale dead-pid lock|lock written before|parent-format|transition lock|provisional|readiness|reload failure|pre-upgrade owner|current-generation live owner|rollback preserves legacy unmanaged root|runDaemonInternal rewrites persisted owner pid|heartbeat fails closed'
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 }
# 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
affected-python-matrix:
name: Affected path validation / Python ${{ matrix.python-version }}
needs: [affected-plan, affected-native]
if: ${{ always() && needs.affected-plan.outputs.has_python == 'true' && needs.affected-native.result != 'failure' && needs.affected-native.result != 'cancelled' }}
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
env:
CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
GJC_REAL_SESSION_TESTS: "1"
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"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python-version }}
- name: Download native addon(s)
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: dev-affected-native-${{ github.run_id }}
path: packages/natives/native
- run: bun install --frozen-lockfile
- run: bun run check:py-sdk
- run: bun run test:py-sdk
- run: bun run ci:test:py-sdk-build
if: ${{ matrix.python-version == '3.12' }}
# 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() }}
needs: [affected-plan, affected-native, affected-python-matrix, affected-shards, telegram-daemon-generation, windows-dev-doctor, 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: ${{ needs.affected-python-matrix.result == 'skipped' && 'skipped' || needs.affected-python-matrix.result }}
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' }}
CI_DEV_TELEGRAM_GUARD_RESULT: ${{ needs.telegram-daemon-generation.result }}
CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ needs.affected-plan.outputs.relevant }}
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') }}
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() }}
needs: [affected-evidence-producer, affected-plan, affected-native, affected-python-matrix, affected-shards, telegram-daemon-generation, windows-dev-doctor, 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: ${{ needs.affected-python-matrix.result == 'skipped' && 'skipped' || needs.affected-python-matrix.result }}
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' }}
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: ${{ needs.affected-plan.outputs.relevant }}
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') }}
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 }}
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() }}
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