diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69093bf4..c77a9024 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,10 @@ name: CI +# Linux clippy/test/coverage/TUI also run on AWS CodeBuild when +# vars.AWS_CODEBUILD_ROLE_ARN is set. Status checks: cortex-cli-gha-x64 +# and cortex-cli-gha-arm64. See deploy/aws/codebuild/README.md. +# Jobs in this workflow stay required and unchanged. + on: push: branches: [main] diff --git a/.github/workflows/codebuild.yml b/.github/workflows/codebuild.yml new file mode 100644 index 00000000..d151bccc --- /dev/null +++ b/.github/workflows/codebuild.yml @@ -0,0 +1,172 @@ +# AWS CodeBuild CI for CortexLM/cli (Linux x64 + arm64). +# Marker: CLI_CODEBUILD_CI_READY +# +# Assumes a dedicated IAM role via GitHub OIDC. No long-lived AWS keys. +# Role ARN and region come from repository *variables*, not secrets. +# See deploy/aws/codebuild/README.md for the one-time admin steps. +# +# Does not replace ci.yml / release.yml / publish-r2.yml / homebrew.yml / +# winget.yml / version-bump.yml / test-stability.yml. + +name: CodeBuild CI + +"on": + push: + branches: [main] + # Unprivileged wiring only. StartBuild is skipped for this event because + # the workflow file would come from the unapproved head. + pull_request: + branches: [main] + # Privileged StartBuild. Workflow YAML and OIDC come from main. + pull_request_target: + branches: [main] + workflow_dispatch: + +concurrency: + group: codebuild-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write + statuses: write + +jobs: + wiring: + name: CodeBuild wiring + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + enabled: ${{ steps.gate.outputs.enabled }} + same_repo: ${{ steps.gate.outputs.same_repo }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + # pull_request must validate the PR tree (github.sha). pin the base + # only for pull_request_target, which must not execute unapproved code. + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.base.sha || github.sha }} + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python -m pip install -r scripts/readiness/requirements.txt + - name: Validate CodeBuild assets + run: python -B -m unittest discover -s scripts/readiness -p test_codebuild.py -v + - name: Decide whether StartBuild is configured + id: gate + env: + ROLE_ARN: ${{ vars.AWS_CODEBUILD_ROLE_ARN }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + run: | + same_repo=false + if [ "$PR_HEAD_REPO" = "${{ github.repository }}" ]; then + same_repo=true + fi + echo "same_repo=$same_repo" >> "$GITHUB_OUTPUT" + if [ -z "$ROLE_ARN" ]; then + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "AWS_CODEBUILD_ROLE_ARN is unset. Skipping StartBuild. One-time IAM is in deploy/aws/codebuild/README.md" + exit 0 + fi + case "$ROLE_ARN" in + arn:aws:iam::*:role/*) ;; + *) + echo "::error::AWS_CODEBUILD_ROLE_ARN must be an IAM role ARN (set a variable, do not commit it)" + exit 1 + ;; + esac + echo "enabled=true" >> "$GITHUB_OUTPUT" + + codebuild: + name: ${{ matrix.context }} + needs: wiring + if: needs.wiring.outputs.enabled == 'true' && needs.wiring.outputs.same_repo == 'true' && github.event_name != 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + include: + - context: cortex-cli-gha-x64 + project_var: AWS_CODEBUILD_PROJECT_X64 + project_default: cortex-cli-gha-x64 + pr_project_var: AWS_CODEBUILD_PROJECT_X64_PR + pr_project_default: cortex-cli-gha-x64-pr + - context: cortex-cli-gha-arm64 + project_var: AWS_CODEBUILD_PROJECT_ARM64 + project_default: cortex-cli-gha-arm64 + pr_project_var: AWS_CODEBUILD_PROJECT_ARM64_PR + pr_project_default: cortex-cli-gha-arm64-pr + env: + STATUS_CONTEXT: ${{ matrix.context }} + QUALITY_BASE: ${{ github.event.pull_request.base.sha || github.event.before }} + CORTEX_GITHUB_REPOSITORY: ${{ github.repository }} + steps: + - name: Checkout trusted buildspec revision + uses: actions/checkout@v7 + with: + persist-credentials: false + # pull_request_target already runs on the base. Pin the ref so a + # later step cannot silently check out the unapproved head. + ref: ${{ github.event.pull_request.base.sha || github.sha }} + - name: Resolve head SHA + id: rev + run: echo "sha=${{ github.event.pull_request.head.sha || github.sha }}" >> "$GITHUB_OUTPUT" + - name: Post pending status + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHA: ${{ steps.rev.outputs.sha }} + run: | + gh api "repos/${{ github.repository }}/statuses/${SHA}" \ + --field state=pending \ + --field context="${STATUS_CONTEXT}" \ + --field description="AWS CodeBuild starting" \ + --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.AWS_CODEBUILD_ROLE_ARN }} + aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} + role-session-name: ${{ matrix.context }} + - name: Read trusted buildspec + id: spec + run: | + test -f deploy/aws/codebuild/buildspec-ci.yml + { + echo "yaml<> "$GITHUB_OUTPUT" + - name: Run CodeBuild + uses: aws-actions/aws-codebuild-run-build@v1 + with: + # Only push/workflow_dispatch on main use the S3-cached projects. + # pull_request_target always uses *-pr (logs-only, NO_CACHE). + project-name: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (vars[matrix.project_var] || matrix.project_default) || (vars[matrix.pr_project_var] || matrix.pr_project_default) }} + disable-source-override: true + buildspec-override: ${{ steps.spec.outputs.yaml }} + env-vars-for-codebuild: | + QUALITY_BASE, + CORTEX_SOURCE_SHA, + CORTEX_GITHUB_REPOSITORY + env: + CORTEX_SOURCE_SHA: ${{ steps.rev.outputs.sha }} + - name: Post final status + if: always() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHA: ${{ steps.rev.outputs.sha }} + OUTCOME: ${{ job.status }} + run: | + if [ "$OUTCOME" = "success" ]; then + state=success + desc="AWS CodeBuild passed" + else + state=failure + desc="AWS CodeBuild failed" + fi + gh api "repos/${{ github.repository }}/statuses/${SHA}" \ + --field state="$state" \ + --field context="${STATUS_CONTEXT}" \ + --field description="$desc" \ + --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/AGENTS.md b/AGENTS.md index 2fd3dacb..5a29449f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,6 +23,7 @@ Working branch: **`main`**. Version bumps land on `main` via PR; `.github/workfl | `docs/` | User and plugin docs | | `.rules/` | Engineering rules (security, errors, TUI, tests, …) | | `scripts/` | Version bump / consistency / release helpers | +| `deploy/aws/codebuild/` | Public-safe CodeBuild buildspecs and OIDC IAM ([README](deploy/aws/codebuild/README.md)) | ## Non-negotiables diff --git a/README.md b/README.md index c68c8b28..1e1c5b8f 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,8 @@ via [`publish-r2.yml`](.github/workflows/publish-r2.yml). This repository does not invent cloud accounts. The secret *names* CI expects are listed in [docs/CI_SECRETS.md](./docs/CI_SECRETS.md). Values never go in git. +Linux CI can additionally run on AWS CodeBuild via GitHub OIDC; the one-time +IAM steps are in [deploy/aws/codebuild/README.md](./deploy/aws/codebuild/README.md). ## Contributing diff --git a/deploy/aws/codebuild/README.md b/deploy/aws/codebuild/README.md new file mode 100644 index 00000000..da5e1d26 --- /dev/null +++ b/deploy/aws/codebuild/README.md @@ -0,0 +1,163 @@ +# AWS CodeBuild CI for CortexLM/cli + +Public-repo CodeBuild integration for Linux **x64** and **arm64**. GitHub +Actions assumes a dedicated IAM role with **OIDC** (no long-lived AWS keys) +and starts the projects. Each project posts a commit status comparable to +the CortexLM/backend checks `cortex-gha-x64` / `cortex-gha-arm64`. + +Marker: `CLI_CODEBUILD_CI_READY` + +Existing workflows stay in place: `ci.yml`, `release.yml`, `publish-r2.yml`, +`homebrew.yml`, `winget.yml`, `version-bump.yml`, `test-stability.yml`. +CodeBuild **extends** them. It does not replace R2 publishing, version +bumps, or macOS/Windows release jobs. Staging/prod app deploy remains +unchanged (prod HOLD). + +Windows CodeBuild is **out of scope**. Compliance treats Windows CI as +outside the production gate; keep `windows-latest` on GitHub-hosted runners +in `ci.yml` / `release.yml` until a separate follow-up. + +Do not commit AWS account IDs, access keys, PATs, or internal hostnames. + +## Status checks (branch protection) + +After the one-time AWS setup below, add these **required** checks on `main`: + +| Context | Push/`main` project (S3 cache) | Pull-request project (no cache) | Arch | +|---------|--------------------------------|----------------------------------|------| +| `cortex-cli-gha-x64` | `cortex-cli-gha-x64` | `cortex-cli-gha-x64-pr` | Linux x86_64 | +| `cortex-cli-gha-arm64` | `cortex-cli-gha-arm64` | `cortex-cli-gha-arm64-pr` | Linux aarch64 | + +Unapproved same-repository pull requests are handled with +`pull_request_target` so this workflow file and the OIDC token come from +`main`, not from the unapproved head. Those runs start the `*-pr` projects +only (logs-only service role, `NO_CACHE`). They cannot read, write, or +delete the shared cargo cache. The runner checks out the PR **base** for +`buildspec-ci.yml` and passes the head SHA into CodeBuild as +`CORTEX_SOURCE_SHA`. OIDC trust is only +`repo:CortexLM/cli:ref:refs/heads/main` plus +`job_workflow_ref` for `.github/workflows/codebuild.yml` on `main`. +Untrusted `pull_request` workflows cannot assume the role. + +Keep the existing `ci.yml` checks (`Format`, `Clippy`, `Test`, `TUI checks`, +`Security Audit`, `Source and dependency policy`, `Changed-line coverage`, +`CLI Version and Distribution`, `CI Success`). Do not remove them in this +change. After CodeBuild is required and stable, a later PR can slim the +duplicate GitHub-hosted Linux cargo jobs. + +Same-repo PRs start the `*-pr` projects via `pull_request_target`. Pushes +to `main` start the cached projects. Fork PRs keep using GitHub-hosted +`ci.yml` only (StartBuild is skipped when the head repo is not this +repository). + +## Prefer existing org projects? + +If this AWS account already hosts backend projects `cortex-gha-x64` / +`cortex-gha-arm64`, **reuse the GitHub OIDC provider** and the account, not +the projects. A CodeBuild project has one source/buildspec; do not point +backend projects at this public CLI repo. Create dedicated +`cortex-cli-gha-*` projects. Override names only via GitHub **variables** +if an admin already created equivalent CLI projects. + +## One-time admin setup + +### 1. Reuse or create the GitHub OIDC provider + +In the AWS account that already runs CortexLM/backend CodeBuild (or a new +account dedicated to public CLI CI): + +1. IAM → Identity providers → `token.actions.githubusercontent.com`. +2. If it exists, **do not recreate it**. Continue to the role. +3. If it does not exist, create it: + - Provider URL: `https://token.actions.githubusercontent.com` + - Audience: `sts.amazonaws.com` + - Or pass `CreateGithubOidcProvider=true` to the stack below. + +### 2. Deploy the stack (recommended) + +From a workstation that can assume an admin role (never from this repo's +CI, and never with keys committed here): + +```bash +aws cloudformation deploy \ + --stack-name cortex-cli-codebuild \ + --template-file deploy/aws/codebuild/cloudformation.yaml \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameter-overrides \ + GitHubOrgRepo=CortexLM/cli \ + ProjectNameX64=cortex-cli-gha-x64 \ + ProjectNameArm64=cortex-cli-gha-arm64 \ + GhaRoleName=cortex-cli-codebuild-gha \ + CreateGithubOidcProvider=false +``` + +Copy the `GithubActionsRoleArn` output. It contains the account ID; store +it as a GitHub **variable**, not in git. + +### 3. Manual IAM if you do not use CloudFormation + +1. Create role `cortex-cli-codebuild-gha`. +2. Trust policy: `iam-trust-policy.json` with `ACCOUNT_ID` replaced at + deploy time. The subject must be only + `repo:CortexLM/cli:ref:refs/heads/main`. Also require + `job_workflow_ref` `CortexLM/cli/.github/workflows/codebuild.yml@refs/heads/main`. + Do not trust `repo:CortexLM/cli:pull_request` — that would let an + unapproved `pull_request` workflow assume the role. +3. Permissions: `iam-gha-permissions.json` with `ACCOUNT_ID` and `REGION` + replaced. Actions are only `codebuild:StartBuild`, + `codebuild:BatchGetBuilds`, and `logs:GetLogEvents` on the two CLI + projects. +4. Create cached projects `cortex-cli-gha-x64` / `cortex-cli-gha-arm64` + (`BUILD_GENERAL1_LARGE`, source **NO_SOURCE**, S3 cache, 90 minute + timeout) and matching `*-pr` projects with **NO_CACHE**. +5. Cached-project service role: CloudWatch Logs plus read/write on the + cache bucket. PR-project service role: CloudWatch Logs **only**. No + deploy, no R2, no production secrets. + +### 4. GitHub repository variables (not secrets) + +On `CortexLM/cli` → Settings → Secrets and variables → Actions → Variables: + +| Variable | Value | +|----------|--------| +| `AWS_CODEBUILD_ROLE_ARN` | `GithubActionsRoleArn` stack output | +| `AWS_REGION` | Region of the stack (default in the workflow is `us-east-1`) | +| `AWS_CODEBUILD_PROJECT_X64` | Optional override; default `cortex-cli-gha-x64` | +| `AWS_CODEBUILD_PROJECT_ARM64` | Optional override; default `cortex-cli-gha-arm64` | + +Do **not** add `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`. Do not put +staging or production app secrets on these projects. + +Until `AWS_CODEBUILD_ROLE_ARN` is set, `.github/workflows/codebuild.yml` +validates the in-repo assets and **skips** StartBuild. It does not post a +green `cortex-cli-gha-*` status for that skip (no mock-success). + +### 5. Require the checks + +Branch protection / ruleset on `main`: require +`cortex-cli-gha-x64` and `cortex-cli-gha-arm64` in addition to the +existing `ci.yml` jobs. Require these only after a successful StartBuild +has been observed on a test PR. + +## What CodeBuild runs + +`buildspec-ci.yml` clones the public `CortexLM/cli` commit over HTTPS +(no PAT) and runs `run-ci.sh`: + +- `cargo fmt --all -- --check` +- `./scripts/clippy.sh` +- `./scripts/check-cli-version.sh` +- `python3 scripts/readiness/tests.py` +- `cargo test --locked --workspace --doc` +- `python3 scripts/readiness/schema.py` +- `cargo build --locked -p cortex-cli -p cortex-app-server` +- `python3 scripts/readiness/qa.py` +- headless TUI / snapshot packages (same set as `ci.yml`) +- changed-line coverage against the real PR base SHA + +Cargo registry, git, rustup, and `target/` are cached in S3. + +## Follow-up (Windows) + +Not in this change. If Windows CodeBuild is added later, use a separate +project and a non-required check. Do not block production on it. diff --git a/deploy/aws/codebuild/buildspec-ci.yml b/deploy/aws/codebuild/buildspec-ci.yml new file mode 100644 index 00000000..3d487d6e --- /dev/null +++ b/deploy/aws/codebuild/buildspec-ci.yml @@ -0,0 +1,67 @@ +# Linux CI for CortexLM/cli on AWS CodeBuild (x64 and arm64). +# Marker: CLI_CODEBUILD_CI_READY +# +# GitHub Actions loads this file from the PR *base* (or pushed main SHA), +# never from an unapproved PR head. The install phase then clones the +# commit under test over public HTTPS (no PAT). Cargo artifacts live in +# CARGO_TARGET_DIR so a source refresh cannot delete the restored cache. +version: 0.2 + +env: + variables: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: "0" + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + RUST_BACKTRACE: "1" + GIT_TERMINAL_PROMPT: "0" + CARGO_TARGET_DIR: /tmp/cortex-cli-target + +phases: + install: + runtime-versions: + python: 3.12 + nodejs: 22 + commands: + - | + set -euo pipefail + REPO="${CORTEX_GITHUB_REPOSITORY:-${GITHUB_REPOSITORY:-CortexLM/cli}}" + SHA="${CORTEX_SOURCE_SHA:-${GITHUB_SHA:-}}" + if [ -z "$SHA" ]; then + echo "Missing CORTEX_SOURCE_SHA / GITHUB_SHA" >&2 + exit 1 + fi + TARGET="${CARGO_TARGET_DIR:-/tmp/cortex-cli-target}" + mkdir -p "$TARGET" + if [ -f "${CODEBUILD_SRC_DIR:-}/Cargo.toml" ]; then + SRC="$CODEBUILD_SRC_DIR" + else + SRC=/tmp/cortex-cli-src + # Wipe only the source clone. Cargo artifacts live in + # CARGO_TARGET_DIR (/tmp/cortex-cli-target), which cache restores + # and this command must not delete. + rm -rf "$SRC" + git clone --no-tags "https://github.com/${REPO}.git" "$SRC" + git -C "$SRC" checkout --detach "$SHA" + fi + { + printf 'export CORTEX_CLI_SRC=%q\n' "$SRC" + printf 'export CARGO_TARGET_DIR=%q\n' "$TARGET" + } > /tmp/cortex-cli-env.sh + # shellcheck disable=SC1091 + . /tmp/cortex-cli-env.sh + bash "$CORTEX_CLI_SRC/deploy/aws/codebuild/install-deps.sh" + + build: + commands: + - | + set -euo pipefail + # shellcheck disable=SC1091 + . /tmp/cortex-cli-env.sh + bash "$CORTEX_CLI_SRC/deploy/aws/codebuild/run-ci.sh" + +cache: + paths: + - /root/.cargo/registry/**/* + - /root/.cargo/git/**/* + - /root/.rustup/toolchains/**/* + - /tmp/cortex-cli-target/**/* diff --git a/deploy/aws/codebuild/cloudformation.yaml b/deploy/aws/codebuild/cloudformation.yaml new file mode 100644 index 00000000..a56ba0a7 --- /dev/null +++ b/deploy/aws/codebuild/cloudformation.yaml @@ -0,0 +1,360 @@ +# One-time AWS resources for CortexLM/cli CodeBuild CI. +# Public-safe: no secrets, no hardcoded account IDs, no long-lived keys. +# Substitute ACCOUNT_ID / REGION only at deploy time (never commit values). +# +# Marker: CLI_CODEBUILD_CI_READY +AWSTemplateFormatVersion: "2010-09-09" +Description: > + Cortex CLI CodeBuild projects (cortex-cli-gha-x64 / cortex-cli-gha-arm64) + plus a GitHub Actions OIDC role. Reuse an existing GitHub OIDC provider + when the account already runs CortexLM/backend CodeBuild. + +Parameters: + GitHubOrgRepo: + Type: String + Default: CortexLM/cli + Description: GitHub repository allowed to assume the GHA role (org/name). + ProjectNameX64: + Type: String + Default: cortex-cli-gha-x64 + Description: CodeBuild project name and GitHub status-check context (x64). + ProjectNameArm64: + Type: String + Default: cortex-cli-gha-arm64 + Description: CodeBuild project name and GitHub status-check context (arm64). + ProjectNameX64Pr: + Type: String + Default: cortex-cli-gha-x64-pr + Description: Pull-request x64 project (no S3 cache; logs-only service role). + ProjectNameArm64Pr: + Type: String + Default: cortex-cli-gha-arm64-pr + Description: Pull-request arm64 project (no S3 cache; logs-only service role). + GhaRoleName: + Type: String + Default: cortex-cli-codebuild-gha + Description: IAM role assumed by GitHub Actions via OIDC. + CreateGithubOidcProvider: + Type: String + Default: "false" + AllowedValues: ["true", "false"] + Description: > + Set true only if this account has no token.actions.githubusercontent.com + OIDC provider yet. Backend accounts should leave this false and reuse + the existing provider. + ComputeType: + Type: String + Default: BUILD_GENERAL1_LARGE + AllowedValues: + - BUILD_GENERAL1_MEDIUM + - BUILD_GENERAL1_LARGE + - BUILD_GENERAL1_XLARGE + Description: CodeBuild compute size (LARGE = 8 vCPU / 15 GiB). + +Conditions: + ShouldCreateOidcProvider: !Equals [!Ref CreateGithubOidcProvider, "true"] + +Resources: + GithubOidcProvider: + Type: AWS::IAM::OIDCProvider + Condition: ShouldCreateOidcProvider + Properties: + Url: https://token.actions.githubusercontent.com + ClientIdList: + - sts.amazonaws.com + ThumbprintList: + - 6938fd4d98bab03faadb97b34396831e3780aea1 + + CacheBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Sub "cortex-cli-codebuild-cache-${AWS::AccountId}-${AWS::Region}" + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + VersioningConfiguration: + Status: Enabled + LifecycleConfiguration: + Rules: + - Id: expire-cache + Status: Enabled + ExpirationInDays: 30 + + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + RoleName: cortex-cli-codebuild-service + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: cortex-cli-codebuild-service + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64}" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64}:*" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64}" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64}:*" + - Sid: CargoCacheBucket + Effect: Allow + Action: + - s3:GetObject + - s3:GetObjectVersion + - s3:PutObject + - s3:DeleteObject + Resource: !Sub "${CacheBucket.Arn}/*" + - Sid: CargoCacheList + Effect: Allow + Action: + - s3:ListBucket + - s3:GetBucketLocation + Resource: !GetAtt CacheBucket.Arn + + CodeBuildPrServiceRole: + Type: AWS::IAM::Role + Properties: + RoleName: cortex-cli-codebuild-service-pr + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: cortex-cli-codebuild-service-pr + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64Pr}" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64Pr}:*" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64Pr}" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64Pr}:*" + + GithubActionsRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Ref GhaRoleName + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: GitHubActionsOidc + Effect: Allow + Principal: + Federated: !If + - ShouldCreateOidcProvider + - !GetAtt GithubOidcProvider.Arn + - !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + token.actions.githubusercontent.com:aud: sts.amazonaws.com + token.actions.githubusercontent.com:sub: !Sub "repo:${GitHubOrgRepo}:ref:refs/heads/main" + StringLike: + token.actions.githubusercontent.com:job_workflow_ref: !Sub "${GitHubOrgRepo}/.github/workflows/codebuild.yml@refs/heads/main" + Policies: + - PolicyName: start-cli-codebuild + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: StartAndObserve + Effect: Allow + Action: + - codebuild:StartBuild + - codebuild:BatchGetBuilds + Resource: + - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${ProjectNameX64}" + - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${ProjectNameArm64}" + - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${ProjectNameX64Pr}" + - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${ProjectNameArm64Pr}" + - Sid: StreamLogs + Effect: Allow + Action: + - logs:GetLogEvents + Resource: + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64}:*" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64}:*" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameX64Pr}:*" + - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${ProjectNameArm64Pr}:*" + + ProjectX64: + Type: AWS::CodeBuild::Project + Properties: + Name: !Ref ProjectNameX64 + Description: Cortex CLI Linux x64 CI (GitHub Actions OIDC trigger) + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + TimeoutInMinutes: 90 + QueuedTimeoutInMinutes: 30 + BadgeEnabled: false + Artifacts: + Type: NO_ARTIFACTS + Cache: + Type: S3 + Location: !Sub "${CacheBucket}/x64" + Environment: + Type: LINUX_CONTAINER + ComputeType: !Ref ComputeType + Image: aws/codebuild/standard:7.0 + ImagePullCredentialsType: CODEBUILD + PrivilegedMode: false + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: !Sub "/aws/codebuild/${ProjectNameX64}" + Source: + Type: NO_SOURCE + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - echo "Start builds from .github/workflows/codebuild.yml (inline buildspec override)." + - exit 1 + + ProjectArm64: + Type: AWS::CodeBuild::Project + Properties: + Name: !Ref ProjectNameArm64 + Description: Cortex CLI Linux arm64 CI (GitHub Actions OIDC trigger) + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + TimeoutInMinutes: 90 + QueuedTimeoutInMinutes: 30 + BadgeEnabled: false + Artifacts: + Type: NO_ARTIFACTS + Cache: + Type: S3 + Location: !Sub "${CacheBucket}/arm64" + Environment: + Type: ARM_CONTAINER + ComputeType: !Ref ComputeType + Image: aws/codebuild/amazonlinux-aarch64-standard:3.0 + ImagePullCredentialsType: CODEBUILD + PrivilegedMode: false + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: !Sub "/aws/codebuild/${ProjectNameArm64}" + Source: + Type: NO_SOURCE + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - echo "Start builds from .github/workflows/codebuild.yml (inline buildspec override)." + - exit 1 + + ProjectX64Pr: + Type: AWS::CodeBuild::Project + Properties: + Name: !Ref ProjectNameX64Pr + Description: Cortex CLI Linux x64 CI for unapproved PRs (no shared cache) + ServiceRole: !GetAtt CodeBuildPrServiceRole.Arn + TimeoutInMinutes: 90 + QueuedTimeoutInMinutes: 30 + BadgeEnabled: false + Artifacts: + Type: NO_ARTIFACTS + Cache: + Type: NO_CACHE + Environment: + Type: LINUX_CONTAINER + ComputeType: !Ref ComputeType + Image: aws/codebuild/standard:7.0 + ImagePullCredentialsType: CODEBUILD + PrivilegedMode: false + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: !Sub "/aws/codebuild/${ProjectNameX64Pr}" + Source: + Type: NO_SOURCE + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - echo "Start builds from .github/workflows/codebuild.yml (inline buildspec override)." + - exit 1 + + ProjectArm64Pr: + Type: AWS::CodeBuild::Project + Properties: + Name: !Ref ProjectNameArm64Pr + Description: Cortex CLI Linux arm64 CI for unapproved PRs (no shared cache) + ServiceRole: !GetAtt CodeBuildPrServiceRole.Arn + TimeoutInMinutes: 90 + QueuedTimeoutInMinutes: 30 + BadgeEnabled: false + Artifacts: + Type: NO_ARTIFACTS + Cache: + Type: NO_CACHE + Environment: + Type: ARM_CONTAINER + ComputeType: !Ref ComputeType + Image: aws/codebuild/amazonlinux-aarch64-standard:3.0 + ImagePullCredentialsType: CODEBUILD + PrivilegedMode: false + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: !Sub "/aws/codebuild/${ProjectNameArm64Pr}" + Source: + Type: NO_SOURCE + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - echo "Start builds from .github/workflows/codebuild.yml (inline buildspec override)." + - exit 1 + +Outputs: + GithubActionsRoleArn: + Description: Set GitHub Actions variable AWS_CODEBUILD_ROLE_ARN to this value + Value: !GetAtt GithubActionsRole.Arn + ProjectX64Name: + Description: Required GitHub status-check context (x64) + Value: !Ref ProjectNameX64 + ProjectArm64Name: + Description: Required GitHub status-check context (arm64) + Value: !Ref ProjectNameArm64 + ProjectX64PrName: + Description: Pull-request x64 project (logs-only role, no S3 cache) + Value: !Ref ProjectNameX64Pr + ProjectArm64PrName: + Description: Pull-request arm64 project (logs-only role, no S3 cache) + Value: !Ref ProjectNameArm64Pr + CacheBucketName: + Description: Private cargo cache bucket + Value: !Ref CacheBucket + Region: + Description: Set GitHub Actions variable AWS_REGION if not us-east-1 + Value: !Ref AWS::Region diff --git a/deploy/aws/codebuild/iam-gha-permissions.json b/deploy/aws/codebuild/iam-gha-permissions.json new file mode 100644 index 00000000..b7f3a103 --- /dev/null +++ b/deploy/aws/codebuild/iam-gha-permissions.json @@ -0,0 +1,32 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "StartAndObserveCliCodeBuild", + "Effect": "Allow", + "Action": [ + "codebuild:StartBuild", + "codebuild:BatchGetBuilds" + ], + "Resource": [ + "arn:aws:codebuild:REGION:ACCOUNT_ID:project/cortex-cli-gha-x64", + "arn:aws:codebuild:REGION:ACCOUNT_ID:project/cortex-cli-gha-arm64", + "arn:aws:codebuild:REGION:ACCOUNT_ID:project/cortex-cli-gha-x64-pr", + "arn:aws:codebuild:REGION:ACCOUNT_ID:project/cortex-cli-gha-arm64-pr" + ] + }, + { + "Sid": "StreamCliCodeBuildLogs", + "Effect": "Allow", + "Action": [ + "logs:GetLogEvents" + ], + "Resource": [ + "arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/codebuild/cortex-cli-gha-x64:*", + "arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/codebuild/cortex-cli-gha-arm64:*", + "arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/codebuild/cortex-cli-gha-x64-pr:*", + "arn:aws:logs:REGION:ACCOUNT_ID:log-group:/aws/codebuild/cortex-cli-gha-arm64-pr:*" + ] + } + ] +} diff --git a/deploy/aws/codebuild/iam-trust-policy.json b/deploy/aws/codebuild/iam-trust-policy.json new file mode 100644 index 00000000..3bc9e554 --- /dev/null +++ b/deploy/aws/codebuild/iam-trust-policy.json @@ -0,0 +1,22 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "GitHubActionsOidcCortexLmCli", + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", + "token.actions.githubusercontent.com:sub": "repo:CortexLM/cli:ref:refs/heads/main" + }, + "StringLike": { + "token.actions.githubusercontent.com:job_workflow_ref": "CortexLM/cli/.github/workflows/codebuild.yml@refs/heads/main" + } + } + } + ] +} diff --git a/deploy/aws/codebuild/install-deps.sh b/deploy/aws/codebuild/install-deps.sh new file mode 100755 index 00000000..16a71635 --- /dev/null +++ b/deploy/aws/codebuild/install-deps.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# System + Rust toolchain for CodeBuild Linux CI. Fail closed; no mock-success. +set -euo pipefail + +if [[ -z "${CORTEX_CLI_SRC:-}" ]]; then + echo "CORTEX_CLI_SRC is required" >&2 + exit 1 +fi +cd "$CORTEX_CLI_SRC" + +export DEBIAN_FRONTEND=noninteractive +export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" +export RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" +mkdir -p "$CARGO_HOME/bin" +export PATH="$CARGO_HOME/bin:$PATH" + +install_apt() { + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + git \ + libasound2-dev \ + libssl-dev \ + pkg-config \ + python3 \ + python3-pip \ + python3-venv \ + ripgrep +} + +install_dnf() { + dnf install -y \ + alsa-lib-devel \ + ca-certificates \ + curl \ + gcc \ + gcc-c++ \ + git \ + make \ + openssl-devel \ + pkgconf-pkg-config \ + python3 \ + python3-pip \ + tar \ + gzip + dnf install -y ripgrep || true +} + +install_ripgrep_tarball() { + local target url tmp + case "$(uname -m)" in + x86_64) target="x86_64-unknown-linux-musl" ;; + aarch64 | arm64) target="aarch64-unknown-linux-gnu" ;; + *) + echo "Unsupported architecture for ripgrep fallback: $(uname -m)" >&2 + return 1 + ;; + esac + url="https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-${target}.tar.gz" + tmp="$(mktemp -d)" + curl -LsSf "$url" | tar zxf - -C "$tmp" + install -m 0755 "$tmp"/ripgrep-*/rg /usr/local/bin/rg + rm -rf "$tmp" +} + +if command -v apt-get >/dev/null 2>&1; then + install_apt +elif command -v dnf >/dev/null 2>&1; then + install_dnf +else + echo "Unsupported CodeBuild image: need apt-get or dnf" >&2 + exit 1 +fi + +if ! command -v rg >/dev/null 2>&1; then + install_ripgrep_tarball +fi +if ! command -v rg >/dev/null 2>&1; then + echo "ripgrep (rg) is required for readiness tests" >&2 + exit 1 +fi + +channel="1.98.0" +if [[ -f rust-toolchain.toml ]]; then + channel="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml | head -n1)" +fi +if [[ -z "$channel" ]]; then + echo "Could not determine Rust toolchain channel" >&2 + exit 1 +fi + +if ! command -v rustup >/dev/null 2>&1; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain "$channel" --profile minimal \ + --component rustfmt --component clippy --component llvm-tools-preview +fi +# shellcheck disable=SC1091 +. "$CARGO_HOME/env" +rustup toolchain install "$channel" --profile minimal --component rustfmt,clippy,llvm-tools-preview +rustup default "$channel" + +arch="$(uname -m)" +case "$arch" in + x86_64) + nextest_dist="linux" + llvm_cov_target="x86_64-unknown-linux-musl" + ;; + aarch64 | arm64) + nextest_dist="linux-arm" + llvm_cov_target="aarch64-unknown-linux-musl" + ;; + *) + echo "Unsupported architecture: $arch" >&2 + exit 1 + ;; +esac + +if ! cargo nextest --version 2>/dev/null | grep -q '0.9.102'; then + curl -LsSf "https://get.nexte.st/0.9.102/${nextest_dist}" | tar zxf - -C "$CARGO_HOME/bin" +fi +if ! cargo llvm-cov --version 2>/dev/null | grep -q '0.6.21'; then + tmp="$(mktemp -d)" + curl -LsSf \ + "https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.6.21/cargo-llvm-cov-${llvm_cov_target}.tar.gz" \ + | tar zxf - -C "$tmp" + install -m 0755 "$tmp/cargo-llvm-cov" "$CARGO_HOME/bin/cargo-llvm-cov" + rm -rf "$tmp" +fi + +python3 -m pip install --disable-pip-version-check -r scripts/readiness/requirements.txt + +command -v rustc >/dev/null +command -v cargo >/dev/null +command -v python3 >/dev/null +command -v node >/dev/null +command -v git >/dev/null +command -v rg >/dev/null +cargo nextest --version +cargo llvm-cov --version +node --version +echo "CodeBuild CI dependencies ready (toolchain $channel, arch $arch)" diff --git a/deploy/aws/codebuild/run-ci.sh b/deploy/aws/codebuild/run-ci.sh new file mode 100755 index 00000000..18ba33a0 --- /dev/null +++ b/deploy/aws/codebuild/run-ci.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Heavy Linux CI suite for CodeBuild. Mirrors .github/workflows/ci.yml +# clippy / test / tui / coverage / schema / QA. Fail closed. +set -euo pipefail + +if [[ -z "${CORTEX_CLI_SRC:-}" ]]; then + echo "CORTEX_CLI_SRC is required" >&2 + exit 1 +fi +cd "$CORTEX_CLI_SRC" + +export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" +export RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" +export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-/tmp/cortex-cli-target}" +mkdir -p "$CARGO_TARGET_DIR" +# shellcheck disable=SC1091 +. "$CARGO_HOME/env" +export PATH="$CARGO_HOME/bin:$PATH" +export CARGO_TERM_COLOR="${CARGO_TERM_COLOR:-always}" +export CARGO_INCREMENTAL="${CARGO_INCREMENTAL:-0}" +export CARGO_REGISTRIES_CRATES_IO_PROTOCOL="${CARGO_REGISTRIES_CRATES_IO_PROTOCOL:-sparse}" +export RUST_BACKTRACE="${RUST_BACKTRACE:-1}" + +QUALITY_BASE="${QUALITY_BASE:-}" +if [[ -z "$QUALITY_BASE" || "$QUALITY_BASE" == "0000000000000000000000000000000000000000" ]]; then + git fetch --no-tags origin main + QUALITY_BASE="$(git rev-parse --verify origin/main)" +fi +git fetch --no-tags origin "$QUALITY_BASE" +QUALITY_BASE="$(git rev-parse --verify "${QUALITY_BASE}^{commit}")" + +echo "==> format" +cargo fmt --all -- --check + +echo "==> clippy" +./scripts/clippy.sh + +echo "==> CLI version" +./scripts/check-cli-version.sh + +echo "==> tests" +python3 scripts/readiness/tests.py + +echo "==> doctests" +cargo test --locked --workspace --doc + +echo "==> API contracts" +python3 scripts/readiness/schema.py + +echo "==> local QA binaries" +cargo build --locked -p cortex-cli -p cortex-app-server + +echo "==> local functional QA" +python3 scripts/readiness/qa.py + +echo "==> TUI / snapshot tests" +cargo test -p cortex-tui -p cortex-tui-capture -p cortex-tui-components \ + -p cortex-tui-framework -p cortex-tui-core -p cortex-tui-buffer \ + -p cortex-tui-widgets -p cortex-tui-layout -p cortex-tui-text \ + -p cortex-tui-input -p cortex-tui-terminal -p cortex-tui-syntax + +echo "==> changed-line coverage" +mkdir -p target/readiness +cargo llvm-cov nextest --locked -p cortex-cli -p cortex-app-server -p cortex-common \ + --profile ci --lcov --output-path target/readiness/lcov.info +python3 scripts/readiness/coverage.py --base "$QUALITY_BASE" + +echo "CodeBuild Linux CI passed" diff --git a/docs/CI_SECRETS.md b/docs/CI_SECRETS.md index cd3efc6b..633b93f5 100644 --- a/docs/CI_SECRETS.md +++ b/docs/CI_SECRETS.md @@ -8,6 +8,27 @@ None of these values belong in git. Do not add AWS access keys or an IAM user fo No secrets. `fmt`, `clippy`, `test`, `audit`, and TUI jobs use the public crates.io index and `GITHUB_TOKEN`. +## Optional Linux acceleration (`.github/workflows/codebuild.yml`) + +OIDC only. **Variables**, not secrets. Do not add `AWS_ACCESS_KEY_ID` or +`AWS_SECRET_ACCESS_KEY`. One-time IAM is in +[`deploy/aws/codebuild/README.md`](../deploy/aws/codebuild/README.md). +Marker: `CLI_CODEBUILD_CI_READY`. + +| Variable | Used for | +|----------|----------| +| `AWS_CODEBUILD_ROLE_ARN` | IAM role assumed by GitHub Actions (`repo:CortexLM/cli:ref:refs/heads/main` only; this workflow on `main`) | +| `AWS_REGION` | CodeBuild region (workflow default `us-east-1`) | +| `AWS_CODEBUILD_PROJECT_X64` | Optional; default project `cortex-cli-gha-x64` (push to `main`, S3 cache) | +| `AWS_CODEBUILD_PROJECT_ARM64` | Optional; default project `cortex-cli-gha-arm64` (push to `main`, S3 cache) | +| `AWS_CODEBUILD_PROJECT_X64_PR` | Optional; default `cortex-cli-gha-x64-pr` (PRs, no cache) | +| `AWS_CODEBUILD_PROJECT_ARM64_PR` | Optional; default `cortex-cli-gha-arm64-pr` (PRs, no cache) | + +Until `AWS_CODEBUILD_ROLE_ARN` is set, the workflow validates in-repo +buildspecs and skips StartBuild. It does not post a green +`cortex-cli-gha-*` status for that skip. Staging/prod app secrets stay +out of this repository and off these projects. + ## Version bump / tag (`.github/workflows/version-bump.yml`) | Secret | Used for | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 11416df5..30173510 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -62,6 +62,12 @@ Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`. ## The gates +PRs to `main` also grow two CodeBuild status checks once an admin has +applied the one-time IAM in +[`deploy/aws/codebuild/README.md`](../deploy/aws/codebuild/README.md): +`cortex-cli-gha-x64` and `cortex-cli-gha-arm64`. Until that role variable +is set, GitHub-hosted `ci.yml` remains the merge gate. + These are exactly what CI runs, so run them before you push: ```bash diff --git a/docs/README.md b/docs/README.md index 2c807b0c..4a54741b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -64,6 +64,7 @@ New here? Start with **[Getting started](guides/getting-started.md)**, then keep | [Troubleshooting](troubleshooting.md) | Common failures and how to diagnose them | | [Contributing](CONTRIBUTING.md) | Filing issues, PR conventions, required checks | | [CI secrets](CI_SECRETS.md) | Secret *names* the release workflows expect | +| [CodeBuild CI](../deploy/aws/codebuild/README.md) | OIDC + AWS CodeBuild Linux x64/arm64 status checks | | [Local operations](guides/operations.md) | Alerts, error investigation, deployment comparison and profiling | | [Maintenance](guides/maintenance.md) | Ownership, labels, backlog triage and release review | | [Readiness remediation](guides/readiness-remediation.md) | Local validation snapshot and explicit remaining limits | diff --git a/docs/guides/development.md b/docs/guides/development.md index 8be3963e..4a35c731 100644 --- a/docs/guides/development.md +++ b/docs/guides/development.md @@ -95,6 +95,12 @@ The append regression test checks immediate visibility after Tokio 1.53.1 file writes. An awaited `flush` finishes the pending write; it is not an `fsync` durability guarantee. Do not replace this check with sleeps or retries. +Linux clippy, tests, TUI, schema, local QA, and changed-line coverage also run +on AWS CodeBuild (`cortex-cli-gha-x64` / `cortex-cli-gha-arm64`) when +`AWS_CODEBUILD_ROLE_ARN` is configured. Setup is +[CodeBuild CI](../../deploy/aws/codebuild/README.md). That path uses GitHub +OIDC; it does not add AWS keys to this repository. + ## Verification MCP (hidden) `cortex mcp-server --verify` is a hidden stdio JSON-RPC server (`hide = true` diff --git a/docs/guides/quality.md b/docs/guides/quality.md index 00886e9c..5e01c34b 100644 --- a/docs/guides/quality.md +++ b/docs/guides/quality.md @@ -49,3 +49,5 @@ registry dates, or remove `--locked`. The existing formatting, Clippy, audit, version, and TUI gates remain required. CI Success also depends on source policy and changed-line coverage. Test and coverage artifacts are retained for 14–30 days, not sent to a third-party service. +Linux CodeBuild status checks (`cortex-cli-gha-x64` / `cortex-cli-gha-arm64`) +are documented in [CodeBuild CI](../../deploy/aws/codebuild/README.md). diff --git a/scripts/readiness/test_codebuild.py b/scripts/readiness/test_codebuild.py new file mode 100644 index 00000000..80798857 --- /dev/null +++ b/scripts/readiness/test_codebuild.py @@ -0,0 +1,196 @@ +"""Public-safe CodeBuild CI wiring. No live AWS calls.""" + +import json +import re +import unittest +from pathlib import Path + +import yaml + +ROOT = Path(__file__).resolve().parents[2] +DEPLOY = ROOT / "deploy/aws/codebuild" +WORKFLOWS = ROOT / ".github/workflows" + +ACCOUNT_ID = re.compile(r"(?