Replace direct Gitea-Actions-secret bot-token consumption with the proven #971/#3274 INFISICAL_CI KMS-fetch pattern. Each consumer job gains a "Fetch <CRED> from Infisical SSOT" step (INFISICAL_CI universal-auth login -> read_secret GET /api/v3/secrets/raw/<KEY> environment=prod -> null-safe extractor -> [ -z ] fail-closed -> ::add-mask:: -> export to $GITHUB_ENV), then the consumer switches secrets.<OLD> -> env.<CRED_KEY>, exported under the env-var NAME the workflow/script already expects so scripts are unchanged. CI_STATUS_TOKEN (prod /shared/ci-status) replaces: - STATUS_POST_TOKEN in qa-review.yml (POST status step) - STATUS_POST_TOKEN in security-review.yml (POST status step) - STATUS_POST_TOKEN in reserved-path-review.yml (POST status step) - STATUS_POST_TOKEN in sop-checklist.yml (qa + security refire steps) - STATUS_REAPER_TOKEN in status-reaper.yml (compensation POST) DRIFT_BOT_TOKEN (prod /shared/drift-bot) replaces: - DRIFT_BOT_TOKEN in ci-required-drift.yml - DRIFT_BOT_TOKEN in lint-continue-on-error-tracking.yml - DRIFT_BOT_TOKEN in lint-required-no-paths.yml - DRIFT_BOT_TOKEN in lint-no-coe-on-required.yml - DRIFT_BOT_TOKEN in lint-bp-context-emit-match.yml - DRIFT_BOT_TOKEN in lint-required-context-exists-in-bp.yml test_gate_review_auto_fire.py: the four assertions that pinned secrets.STATUS_POST_TOKEN now assert env.CI_STATUS_TOKEN (qa/security POST steps + qa/security refire steps). validate-before-delete: the old Gitea Actions secrets (STATUS_POST_TOKEN, STATUS_REAPER_TOKEN, DRIFT_BOT_TOKEN) are intentionally LEFT IN PLACE. Delete them only after a green run proves the Infisical fetch path works. Both consolidated Infisical secrets already exist in prod (verified non-empty). Bootstrap INFISICAL_CI_* and the auto-injected GITEA_TOKEN/GITHUB_TOKEN are untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
159 lines
7.4 KiB
YAML
159 lines
7.4 KiB
YAML
name: lint-required-context-exists-in-bp
|
|
|
|
# Tier 2g hard-gate lint (per mc#1982) — diff-based PR-time
|
|
# check. When a PR adds a NEW commit-status emission (workflow YAML
|
|
# `name:` + job `name:`-or-key + on:-event), the workflow file must
|
|
# carry one of three directives adjacent to the new job:
|
|
#
|
|
# - `# bp-required: yes` — and BP must list the context
|
|
# - `# bp-required: pending #NNN` — acknowledged asymmetry + tracker
|
|
# - `# bp-exempt: <reason>` — informational job, not a gate
|
|
#
|
|
# Default (no directive on a new emitter) = FAIL.
|
|
#
|
|
# Why this exists
|
|
# ---------------
|
|
# PR#656 added `CI / all-required (pull_request)` as a sentinel
|
|
# context that workflows emit, but BP did NOT list it. When
|
|
# platform-build failed, all-required failed, but BP let the PR
|
|
# merge anyway → cascade to mc#1982. With this lint, PR#656 would
|
|
# have been blocked until either the BP PATCH ran alongside OR
|
|
# the author added a `bp-required: pending` directive.
|
|
#
|
|
# Tier 2g vs Tier 2f
|
|
# ------------------
|
|
# Tier 2g runs at PR-time (diff-based) and BLOCKS the merge.
|
|
# Tier 2f runs daily (scheduled) and FILES a drift issue. They
|
|
# share the workflow-context enumeration helpers
|
|
# (`_event_map`, `workflow_contexts`, `_job_display`) but the
|
|
# semantics are intentionally distinct so they're separate scripts.
|
|
# Co-design is documented in mc#1982.
|
|
#
|
|
# Directive comment lives in the workflow file (NOT PR body)
|
|
# ----------------------------------------------------------
|
|
# A PR-body claim of "BP exempt" evaporates on merge — the
|
|
# asymmetry returns to undetected state and Tier 2f's daily
|
|
# scheduled audit can't see it. The directive must live with the
|
|
# emitter so both PR-time (Tier 2g) and post-merge (Tier 2f)
|
|
# readers consume the same source.
|
|
#
|
|
# Phase contract (RFC internal#219 §1 ladder)
|
|
# -------------------------------------------
|
|
# Lands at `continue-on-error: true` (Phase 3 — surface the
|
|
# pattern without blocking PRs while the directive convention
|
|
# beds in). After 7 days of clean runs on `main` with no false
|
|
# positives, follow-up flips to `false`. Tracking: mc#1982.
|
|
#
|
|
# Cross-links
|
|
# -----------
|
|
# - mc#1982 (the RFC that specs this lint)
|
|
# - PR#656 (the empirical case)
|
|
# - mc#1982 (the surfaced cascade)
|
|
# - feedback_phantom_required_check_after_gitea_migration (Tier 2f cousin)
|
|
# - feedback_behavior_based_ast_gates
|
|
#
|
|
# Auth: DRIFT_BOT_TOKEN (repo-admin for branch_protections read).
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
paths:
|
|
- '.gitea/workflows/**'
|
|
- '.gitea/scripts/lint_required_context_exists_in_bp.py'
|
|
- '.gitea/workflows/lint-required-context-exists-in-bp.yml'
|
|
- 'tests/test_lint_required_context_exists_in_bp.py'
|
|
|
|
env:
|
|
GITHUB_SERVER_URL: https://git.moleculesai.app
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: lint-required-context-exists-in-bp-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# bp-exempt: this lint is a PR-time advisory and is not intended to
|
|
# be a required gate on main. The directive eat-our-own-dogfood
|
|
# confirms the convention works on the lint that defines it.
|
|
lint:
|
|
name: lint-required-context-exists-in-bp
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
# Phase 4 (RFC #219 §1): 22 days green since 2026-05-11 port,
|
|
# well past the 7-clean-day threshold. PR-time failure is now
|
|
# a hard CI signal.
|
|
continue-on-error: false
|
|
steps:
|
|
- name: Check out PR head with full history (need base SHA blobs)
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `git show <base-sha>:<path>` needs the base SHA's blobs.
|
|
# Same rationale as PR#673 and check-migration-collisions.yml.
|
|
fetch-depth: 0
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.12'
|
|
- name: Install PyYAML
|
|
run: python -m pip install --quiet 'PyYAML==6.0.2'
|
|
- name: Ensure base ref is reachable locally
|
|
# Cheap insurance against runner-version drift.
|
|
run: |
|
|
git fetch origin "${{ github.event.pull_request.base.ref }}" || true
|
|
# KMS consolidation (#971/#3274 pattern): the lint below previously read
|
|
# the per-bot Gitea Actions secret `DRIFT_BOT_TOKEN`. We now fetch the
|
|
# consolidated DRIFT_BOT_TOKEN from the Infisical SSOT (prod
|
|
# /shared/drift-bot) via the CI machine identity and export it to
|
|
# $GITHUB_ENV. The lint keeps reading it under the GITEA_TOKEN env var
|
|
# name it already expects, so the script is unchanged. The old
|
|
# DRIFT_BOT_TOKEN Gitea secret is left in place until a green run
|
|
# validates this path (validate-before-delete).
|
|
#
|
|
# Trust boundary: molecule-core PRs are same-repo (not forks), so the
|
|
# INFISICAL_CI_* machine-identity secrets are present on pull_request —
|
|
# the same assumption the prior direct DRIFT_BOT_TOKEN consumption made.
|
|
- name: Fetch DRIFT_BOT_TOKEN from Infisical SSOT
|
|
env:
|
|
INFISICAL_CI_CLIENT_ID: ${{ secrets.INFISICAL_CI_CLIENT_ID }}
|
|
INFISICAL_CI_CLIENT_SECRET: ${{ secrets.INFISICAL_CI_CLIENT_SECRET }}
|
|
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_CI_PROJECT_ID }}
|
|
run: |
|
|
set -uo pipefail
|
|
BASE="https://key.moleculesai.app"
|
|
TOK=$(curl -fsS -X POST "$BASE/api/v1/auth/universal-auth/login" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "{\"clientId\":\"$INFISICAL_CI_CLIENT_ID\",\"clientSecret\":\"$INFISICAL_CI_CLIENT_SECRET\"}" \
|
|
| python3 -c 'import sys,json; v=json.load(sys.stdin).get("accessToken"); sys.stdout.write(v if isinstance(v,str) else "")')
|
|
if [ -z "$TOK" ]; then echo "::error::Infisical accessToken empty - failing closed"; exit 1; fi
|
|
read_secret() {
|
|
curl -fsS "$BASE/api/v3/secrets/raw/$1?workspaceId=$INFISICAL_PROJECT_ID&environment=prod&secretPath=$2" \
|
|
-H "Authorization: Bearer $TOK" \
|
|
| python3 -c 'import sys,json; v=json.load(sys.stdin).get("secret",{}).get("secretValue"); sys.stdout.write(v if isinstance(v,str) else "")'
|
|
}
|
|
DRIFT_BOT_TOKEN=$(read_secret DRIFT_BOT_TOKEN %2Fshared%2Fdrift-bot)
|
|
echo "::add-mask::$DRIFT_BOT_TOKEN"
|
|
if [ -z "$DRIFT_BOT_TOKEN" ]; then
|
|
echo "::error::Infisical returned empty DRIFT_BOT_TOKEN at prod /shared/drift-bot — failing closed."
|
|
exit 1
|
|
fi
|
|
echo "DRIFT_BOT_TOKEN=$DRIFT_BOT_TOKEN" >> "$GITHUB_ENV"
|
|
echo "DRIFT_BOT_TOKEN loaded from Infisical (len=${#DRIFT_BOT_TOKEN})"
|
|
- name: Run lint-required-context-exists-in-bp
|
|
env:
|
|
# DRIFT_BOT_TOKEN — repo-admin (needed for branch_protections).
|
|
# KMS consolidation: now arrives via $GITHUB_ENV from the fetch step
|
|
# above (was secrets.DRIFT_BOT_TOKEN).
|
|
GITEA_TOKEN: ${{ env.DRIFT_BOT_TOKEN }}
|
|
GITEA_HOST: git.moleculesai.app
|
|
REPO: ${{ github.repository }}
|
|
BRANCH: main
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
WORKFLOWS_DIR: .gitea/workflows
|
|
run: python3 .gitea/scripts/lint_required_context_exists_in_bp.py
|
|
- name: Run lint-required-context-exists-in-bp unit tests
|
|
run: |
|
|
python -m pip install --quiet pytest
|
|
python3 -m pytest tests/test_lint_required_context_exists_in_bp.py -v
|