Files
molecule-core/.gitea/workflows/lint-context-set-shrink.yml
T
Hongming WangandClaude Opus 5 50f39f4106
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
fix(ci): close Check B's PARTIAL blindness, and enforce the gate's own presence
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>
2026-08-10 09:01:57 -07:00

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