Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 18s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 33s
Concierge Creates Workspace Hermetic / Concierge Creates Workspace Hermetic (pull_request) Successful in 37s
Lint forbidden hand-written mcp__ tool-id literals / Scan for hand-written mcp__ tool-id literals (pull_request) Successful in 26s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 46s
ci-required-drift / drift (pull_request) Successful in 48s
lint-phantom-gate / lint-phantom-gate (pull_request) Successful in 35s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 37s
lint-bp-context-emit-match / lint-bp-context-emit-match (pull_request) Successful in 46s
Lint publish-runner timeout-minutes / Lint publish-runner timeout-minutes (pull_request) Successful in 17s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 18s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 56s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 23s
lint-staging-tenant-cd-gate-chain / lint-staging-tenant-cd-gate-chain (pull_request) Successful in 23s
Lint workflow YAML (repository compatibility policy) / Lint workflow YAML for repository compatibility policy (pull_request) Successful in 26s
CI / Python Lint & Test (pull_request) Successful in 1m24s
sdk-pin-drift / SDK consumer pin vs molecule-ai-sdk main (pull_request) Successful in 29s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 24s
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m37s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 1m35s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 53s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m31s
template-delivery-e2e / detect-changes (pull_request) Successful in 37s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 1m46s
template-delivery-e2e / Template-asset delivery (fresh seo-agent — config+prompts via asset channel, seo-all via plugin reconcile) (pull_request) Successful in 2s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m17s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 1m48s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 1m34s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m9s
E2E Chat / E2E Chat (pull_request) Successful in 2m29s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 43s
Lint schedule budget / Zero-cron ratchet (pull_request) Successful in 1m48s
lint-context-set-shrink / lint-context-set-shrink (pull_request) Successful in 2m37s
sdk-route-milestone-contract-drift / SDK route + milestone derive-gates (pull_request) Successful in 1m39s
lint-env-coupling-dismissal / lint-env-coupling-dismissal (pull_request) Successful in 5m5s
CI / Detect changes (pull_request) Successful in 7m45s
E2E Ephemeral CP Happy Path / detect (pull_request) Successful in 7m46s
CI / Canvas (Next.js) (pull_request) Successful in 4m44s
CI / Canvas Deploy Status (pull_request) Successful in 2s
CI / Platform (Go) (pull_request) Successful in 5m55s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 6m30s
CI / all-required (pull_request) Successful in 12s
E2E Ephemeral CP Happy Path / E2E Ephemeral CP Happy Path (pull_request) Successful in 24m10s
Three things, all follow-ups to #5157/#5159. 1. CHECK B WAS A ZERO-CHECK, NOT A PLAUSIBILITY CHECK. All three TOTAL routes were closed -- unresolvable ref, no base workflow dir, every base file unparseable -- but base_lanes = 1, 2, 5 or 22 against an expected 45 all returned OK while Check B was most of the way blind, and the numbers do not look wrong on their own. The fix is ATTRIBUTION, not a ratio, and that choice is the substance of it. A floor like `len(base_lanes) >= 0.8 * len(expected)` reds a PR that legitimately adds a lot of workflows, and a gate that reds honest PRs is worse than the gap it closes. So derive_expected now returns lane -> SOURCE FILE, and for every lane the head expects that the base does not produce, decide() asks which file produced it. A PR can only legitimately introduce a lane by touching that lane's workflow file -- adding the file, adding a job, or widening a trigger -- and all three appear in the PR's own diff. A lane sourced from a file the PR never touched cannot be new, so the base read came back short. Both directions are pinned, and the second is the one that matters: base has one lane and head expects two, with the ONLY difference between the tests being whether `b.yml` is in the diff. A ratio could not tell those apart. A third test covers a job added to an existing file, which a naive "is this a NEW file" check would red. Not reachable today -- fetch-depth: 0 keeps origin/main current and a bad BASE_REF dies in merge-base first -- but the workflow's `git fetch ... || true` is exactly the tolerate-silently shape that produced the incident this gate exists for. 2. THE .strip() EQUIVALENCE ARGUMENT COVERED ONE CALLER OF TWO. The two implementations are genuinely distinct functions -- they diverge on ' A / a', '\tA / a', 'A / a\xa0' -- so the equivalence is a property of the INPUT DOMAIN and has to be argued per entry point. The docstring argued Gitea's emission and silently omitted parse_waivers, which also feeds split_event. Now both are argued: _WAIVER_LINE_RE captures the subject as \S.*? with a trailing \s*$, so '42\t A / a \t' arrives already stripped at both ends. 3. THE GATE CAN NOW DETECT ITS OWN REMOVAL (core#5158). Until now: delete lint-context-set-shrink.yml and it does not schedule at head, Check B never executes, and ['*'] requires nothing of a context that never posts. The last hole in the design, and the same shape the gate exists to close. `lint-context-set-shrink / lint-context-set-shrink` is now listed above the first `# pending-` marker, so enforced_file_contexts_green scores it `missing` when absent -> not green -> the queue blocks. A red already blocked under the wildcard; listing adds the PRESENCE half. This could not be done in #5157 because Guard F resolves enforced entries against the live Actions API, which did not know the workflow until it landed on main (75 vs 76); the API now reports 76 with this workflow `active`. The job directive moves from bp-exempt to `bp-required: pending #5158`, matching local-provision-e2e.yml's precedent for a context enforced by file but not named literally in BP. 34 mutants, 34 killed, 0 survived, 0 stillborn; 81 tests. Field trial unchanged:23e5960e6-> missing=2,4849b27ed-> OK, 0/24 red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
148 lines
7.5 KiB
YAML
148 lines
7.5 KiB
YAML
name: lint-context-set-shrink
|
|
|
|
# Every context this PR SHOULD post, must post.
|
|
#
|
|
# WHY THIS EXISTS
|
|
# ---------------
|
|
# core#5153 added a duplicate `env:` key to local-provision-e2e.yml. Gitea's
|
|
# workflow parser rejects a duplicate mapping key, so it never SCHEDULED the
|
|
# workflow — both of its jobs stopped posting any status, including
|
|
# `Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub)`, which
|
|
# .gitea/required-contexts.txt names. Every other workflow ran, so head
|
|
# 23e5960e6 read 43/43 success and was fully mergeable.
|
|
#
|
|
# The lane did not fail. It ceased to exist.
|
|
#
|
|
# Branch protection on main is status_check_contexts=['*'] — "every POSTED
|
|
# context must be green". A wildcard constrains statuses that EXIST; it cannot
|
|
# require one that never arrives. Five workflow lints all posted `success` on
|
|
# that exact file, because each checks a PROPERTY of the YAML and none checks
|
|
# whether CI actually ran.
|
|
#
|
|
# A duplicate-key rule now ships separately (core#5156). This gate covers the
|
|
# CLASS rather than that one cause: it DERIVES the contexts this PR's head
|
|
# should post — from each workflow's `on:`/`types:`/`branches:`/`paths:`
|
|
# evaluated against the PR's real changed files — and requires every one of them
|
|
# to appear. Parse error, narrowed trigger, narrowed paths, renamed job: all of
|
|
# them land as a derived context that never posted.
|
|
#
|
|
# The rationale for deriving rather than diffing against another commit, and the
|
|
# measured accuracy of the derivation (24/24 real heads exact, zero over- and
|
|
# zero under-prediction), are argued in full at the top of
|
|
# .gitea/scripts/lint_context_set_shrink.py.
|
|
#
|
|
# NO `paths:` FILTER — DELIBERATE, AND LOAD-BEARING
|
|
# -------------------------------------------------
|
|
# This gate must fire on EVERY pull request, not only workflow-touching ones.
|
|
# Most of what it catches is not an edit to .gitea/workflows at all: a `paths:`
|
|
# filter that stops matching because the PR's file set moved, a workflow that
|
|
# stops being scheduled for any other reason. Narrowing the trigger would make
|
|
# the gate vanish for exactly the changes it exists to notice — its own failure
|
|
# mode, applied to itself.
|
|
#
|
|
# ENFORCED VIA required-contexts.txt (core#5158)
|
|
# --------------------------------------------------------
|
|
# THE HOLE THIS CLOSES, stated plainly: delete this workflow file and it does not
|
|
# schedule at head, so Check B never executes, and branch protection ['*'] requires
|
|
# nothing of a context that never posts. The gate could not detect its own removal.
|
|
# That is the same shape it exists to close, pointed at itself.
|
|
#
|
|
# `lint-context-set-shrink / lint-context-set-shrink` is now listed ABOVE the first
|
|
# `# pending-` marker in .gitea/required-contexts.txt, so
|
|
# gitea-merge-queue.py::enforced_file_contexts_green scores it `missing` when it is
|
|
# absent -> not green -> the queue blocks. A red already blocked under the ['*']
|
|
# wildcard; listing adds the PRESENCE half.
|
|
#
|
|
# It could not be listed in #5157, the PR that introduced this workflow:
|
|
# lint-phantom-gate Guard F resolves enforced entries against the LIVE Actions API,
|
|
# which did not know the workflow until it landed on the default branch (75 vs 76).
|
|
#
|
|
# Auth: the auto-injected GITHUB_TOKEN only. Reading combined commit statuses
|
|
# needs nothing more, and an earlier draft of this gate that also called
|
|
# /actions/workflows (which 401s on the auto token, so it needed DRIFT_BOT_TOKEN
|
|
# from Infisical) was dropped along with the check that wanted it. Fewer
|
|
# credentials in a job that runs PR-authored code is worth more than the check
|
|
# it bought.
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
|
|
env:
|
|
GITHUB_SERVER_URL: https://git.moleculesai.app
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
# Per-PR, cancel-in-progress: a superseded head's verdict is worthless — the
|
|
# head it measured is no longer the head that would merge.
|
|
group: lint-context-set-shrink-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# bp-required: pending #5158
|
|
#
|
|
# This context IS enforced: it is listed above the `# pending-` marker in
|
|
# .gitea/required-contexts.txt, so the merge queue presence-requires it — it must
|
|
# post AND be green on the PR head. The directive stays `pending` rather than
|
|
# `yes` for the same reason local-provision-e2e.yml's does: branch protection on
|
|
# main is status_check_contexts=['*'] and does not name any context literally,
|
|
# while `bp-required: yes` makes lint-required-context-exists-in-bp demand
|
|
# literal membership. #5158 tracks that repo-wide gap; it is not specific to this
|
|
# workflow.
|
|
lint-context-set-shrink:
|
|
name: lint-context-set-shrink
|
|
runs-on: ubuntu-latest
|
|
# Bounded by CSG_MAX_SETTLE_SECONDS (300s) plus checkout and setup. A typical
|
|
# run settles in ~50s; the ceiling exists so that an unsettled head reports
|
|
# UNSETTLED and passes rather than being killed mid-poll with no verdict.
|
|
timeout-minutes: 12
|
|
# Born `false`, and it must STAY false. Flipping true->false later would wedge
|
|
# lint-pre-flip-continue-on-error permanently: it accepts only a `(push)`
|
|
# context as flip evidence for a workflow without a `paths:` filter, this
|
|
# workflow is pull_request-only and never emits one, and its zero-run branch
|
|
# fails closed with no remediation available inside the PR.
|
|
continue-on-error: false
|
|
steps:
|
|
- name: Check out PR head with full history
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# The derivation needs the PR's real changed-file list, which means
|
|
# `git merge-base` against the base branch and a diff from it.
|
|
# Shallow=1 has neither.
|
|
fetch-depth: 0
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.12'
|
|
- name: Install PyYAML
|
|
# Same pin as ci-required-drift.yml and lint-pre-flip-continue-on-error.yml
|
|
# so the Gitea runner cache hits across all of them.
|
|
run: python -m pip install --quiet 'PyYAML==6.0.2'
|
|
- name: Ensure the base ref is reachable locally
|
|
# merge-base needs the base branch present locally. Cheap insurance
|
|
# against runner-version differences in what checkout fetches (mirrors
|
|
# check-migration-collisions.yml).
|
|
run: |
|
|
git fetch origin "${{ github.event.pull_request.base.ref }}" || true
|
|
- name: Run lint-context-set-shrink
|
|
env:
|
|
# Auto-injected by Gitea Actions. Read-only is enough: the only API
|
|
# this gate calls is the combined commit-status endpoint.
|
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITEA_HOST: git.moleculesai.app
|
|
REPO: ${{ github.repository }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
BASE_REF: origin/${{ github.event.pull_request.base.ref }}
|
|
BASE_BRANCH: ${{ github.event.pull_request.base.ref }}
|
|
run: python3 .gitea/scripts/lint_context_set_shrink.py
|
|
- name: Run lint-context-set-shrink unit tests
|
|
# Also collected wholesale by test-ops-scripts.yml (`pytest
|
|
# .gitea/scripts/tests`). Running them here too means the gate's own tests
|
|
# execute on the PR that changes the gate, in the same job, rather than
|
|
# only in a sibling workflow whose `paths:` filter might not match.
|
|
run: |
|
|
python -m pip install --quiet 'pytest==9.0.2'
|
|
python3 -m pytest .gitea/scripts/tests/test_lint_context_set_shrink.py -v
|