feat(internal#219 §4+§6): port ci-required-drift + audit-force-merge sidecar from CP #422
No reviewers
Labels
No Milestone
No project
No Assignees
3 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: molecule-ai/molecule-core#422
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/internal-219-phase-2bc-port-to-molecule-core"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Ports
molecule-controlplane#112(ci-required-driftdetector +audit-force-mergesidecar reconcile) from CP tomolecule-core. Phase 2b+c of RFC internal#219 §4 + §6.Detector is in place before Phase 4 lands the protection update (#286), so drift between (a) ci.yml jobs, (b)
status_check_contexts, and (c)REQUIRED_CHECKSenv is visible from day-one — the regression class identified in RFC §1 finding 3 cannot recur silently here.Files
.gitea/workflows/ci-required-drift.yml0adf2098+ secret-name adapt.gitea/scripts/ci-required-drift.py0adf2098verbatim (591 lines).gitea/workflows/audit-force-merge.ymlREQUIRED_CHECKSenv updated to match realbranch_protections/main.gitea/scripts/audit-force-merge.shtests/test_ci_required_drift.py0adf2098verbatim (556 lines, 17 cases)Adaptations from CP#112
secrets.GITEA_TOKEN→secrets.SOP_TIER_CHECK_TOKEN(molecule-core's established read-only Gitea API token name; also used bysop-tier-check.ymlandaudit-force-merge.yml).DRIFT_LABEL tier:highresolves to label id 9 on core (verified 2026-05-11) vs id 10 on CP.REQUIRED_CHECKSenv initialized to molecule-core's actualbranch_protections/mainset (2 contexts:Secret scan+sop-tier-check), not CP's (3 contexts).all-requiredsentinel does NOT yet exist in molecule-core'sci.yml(RFC §4 Phase 4 adds it). Until then, the detector exits 3 with::error::sentinel job 'all-required' not found. The workflow will be red on the hourly cron until Phase 4 lands — that's intentional and louder than a silent issue. Once Phase 4 adds the sentinel, this PR's detector starts producing real drift findings.Test plan
python3 -m pytest tests/test_ci_required_drift.py -v— 17/17 green locally (Python 3.13, PyYAML 6.0.3)FileNotFoundError(proves tests exercise the implementation, not happy-path shape)python3 -m py_compile .gitea/scripts/ci-required-drift.py— passesbash -n .gitea/scripts/audit-force-merge.sh— passesyaml.safe_loadon both workflow YAMLs — passes::error::sentinel job 'all-required' not found in .gitea/workflows/ci.yml(expected)secret-scan+sop-tier-checkgreen on this PR:17; verify the workflow logs the same::error::(the workflow will be red until Phase 4 lands the sentinel)all-requiredjob + protection update lands, the detector starts producing actionable F1/F2/F3 findings instead of exit-3What this does NOT change
audit-force-merge.shis byte-identical to CP's — no change needed.ci.ymlrestructuring (PR #372 already did that).RFC trail
cc @hongmingwang
Phase 2b+c port of molecule-controlplane PR#112 (SHA 0adf2098) to molecule-core, per RFC internal#219 §4 (jobs ↔ protection drift) + §6 (audit env ↔ protection drift). ## What this adds 1. .gitea/workflows/ci-required-drift.yml — hourly cron (':17') + workflow_dispatch. AST-walks ci.yml, branch_protections, and audit-force-merge.yml's REQUIRED_CHECKS env. Files/updates a [ci-drift] issue idempotent by title when any pair diverges. 2. .gitea/scripts/ci-required-drift.py — verbatim from CP. PyYAML-based AST detector (NOT grep-by-name), per feedback_behavior_based_ast_gates. Five drift classes: F1, F1b, F2, F3a, F3b. 3. .gitea/workflows/audit-force-merge.yml — reconcile with CP's structure. Moves permissions: to workflow level, adds base.sha- pinning rationale, links to drift-detect, and updates REQUIRED_CHECKS to current branch_protections/main verbatim (2 contexts). 4. tests/test_ci_required_drift.py — 17 pytest cases, verbatim from CP. Stdlib + PyYAML only. Covers F1/F1b/F2/F3a/F3b, happy path, the idempotent-PATCH path, the MUST-FIX find_open_issue() raise-on- transient regression, the --dry-run flag, and api() error contracts. ## Adaptations from CP#112 - secrets.GITEA_TOKEN → secrets.SOP_TIER_CHECK_TOKEN (molecule-core's established read-only token name, used by sop-tier-check and audit-force-merge already). - DRIFT_LABEL tier:high resolves to label id 9 on core (verified 2026-05-11) vs id 10 on CP. - REQUIRED_CHECKS env initialized to molecule-core's actual main protection set (2 contexts: Secret scan + sop-tier-check), not CP's (3 contexts incl. packer-ascii-gate + all-required). - Comment block flags that the 'all-required' sentinel does NOT yet exist in molecule-core's ci.yml (RFC §4 Phase 4 adds it). Until then, the detector exits 3 with ::error:: 'sentinel job not found'. Verified locally: the workflow will be red on the cron until Phase 4 lands — that's intentional + louder than a silent issue. ## Verification - 17/17 pytest cases green locally (Python 3.13, PyYAML 6.0.3). - Hostile self-review: removing the script makes all 17 tests ERROR with FileNotFoundError, confirming they exercise the actual implementation (not happy-path shape-matching). - python3 -m py_compile + bash -n + yaml.safe_load all pass. - Initial dry-run against real molecule-core ci.yml: exits 3 with ::error::sentinel job 'all-required' not found — expected, Phase 4 will add it. ## What does NOT change - audit-force-merge.sh is byte-identical to CP's — no change needed. - No branch protection mutation (that's Phase 4, separate PR). - No CI workflow restructuring (PR#372 already did that). RFC: molecule-ai/internal#219 Source: molecule-controlplane@0adf2098 (PR #112)[core-qa-agent] N/A — CI automation. Ports ci-required-drift detector from CP. No production code changed.
Five-Axis review — APPROVE
Ports
molecule-controlplane#112(theci-required-driftdetector +audit-force-mergereconcile) tomolecule-core— RFCinternal#219§4 + §6, Phase 2b+c. I Five-Axis-reviewed CP#112 itself (review 915, APPROVED) — this port is substantially the same with molecule-core-specific adaptations. 4 files, +1307/-23.1. Correctness ✅
ci-required-drift.pyis verbatim from CP0adf2098— sameApiErrorclass, same 5-finding-class drift detection (F1 job-missing-from-sentinel-needs / F1b sentinel-needs-typo / F2 protection-has-no-emitter / F3a env-wider-than-protection / F3b protection-wider-than-env), same AST-based YAML diff, same idempotent-by-title[ci-drift]issue. I already vetted that code on CP#112 — no re-litigation needed.GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}— uses the org-level token that actually exists (the only org secret), falls back to the runner token. Correct.GITEA_HOST: git.moleculesai.app,REPO: ${{ github.repository }}— repo-agnostic, good (this same workflow drops into any repo).BRANCHES: 'main staging'— checks both.stagingprotection is "forthcoming per RFC §1 Phase 4" — so thestagingcheck will report "no protection" until then, which is correct signal (drift = protection-doesn't-cover-staging-yet).SENTINEL_JOB: 'all-required'— the aggregator job Phase 4 (#286) adds. Until #286 lands, the dry-run errors::error::sentinel job 'all-required' not found in .gitea/workflows/ci.yml EXIT=3— intentional, and a good forcing function: the hourly cron is loudly red until Phase 4, which keeps Phase 4 from drifting indefinitely. (One nit: a loud-red hourly workflow will also makecore/maincombined-status red on every scheduled fire — same "Phase-3 surface-don't-block produces combined-red" tension as #425. The watchdog #423 will file a[main-red]for it; acceptable during the migration window. Worth a one-line note in the workflow header that this is expected-until-#286, so the watchdog issue doesn't get triaged as a real break.)DRIFT_LABEL: 'tier:high'(label id 10→9 reconciled for molecule-core's label set),AUDIT_WORKFLOW_PATH/CI_WORKFLOW_PATHpoint at molecule-core's.gitea/workflows/*. Correct.REQUIRED_CHECKSin the reconciledaudit-force-merge.yml— aligned to molecule-core's actualbranch_protections/main.status_check_contexts(the 2 contexts:Secret scan / ...+sop-tier-check / ...). Initialized 2026-05-11 from the real protection state — exactly the right approach (not a copy of CP's REQUIRED_CHECKS, which would be a phantom-check source —feedback_phantom_required_check_after_gitea_migration).audit-force-merge.ymldiff (+53/-23): movespermissions:to workflow-level (contents: read+pull-requests: read,issues:deliberately omitted — fire-and-forget), updates the header to referenceaudit-force-merge.sh(script-extract pattern) + thefeedback_gh_cli_merge_lies_use_restrationale + thebase.sha(notbase.ref) checkout pinning. Sound — same security model assop-tier-check, and the REQUIRED_CHECKS comment now says "MUST mirror branch protection's status_check_contexts" (which the §2-mechanism-2 drift detector then verifies — so the audit env can't silently drift from protection, which is the RFC §6 gap this closes).2. Tests ✅
556-line pytest, "17 cases all pass", hostile-self-review confirmed (removing the script →
FileNotFoundErroron import → tests exercise the real impl). Same shape as CP#112's test suite, which I vetted. The drift-finding-class coverage + the idempotence + thefind_open_issueraises-on-transient (the fix-class from CP#112's iteration) + the dry-run + the api()-raises-on-non-2xx — all there.3. Security ✅
ci-required-driftpermissions:contents: read+issues: write.audit-force-mergepermissions:contents: read+pull-requests: read(no issues:).pull_request_target+base.shacheckout on the audit workflow — prevents a PR author from swapping the audit script. No secrets beyondSOP_TIER_CHECK_TOKEN(which exists) + the fallback runner token.4. Operational ✅
ci-required-driftcron'17 * * * *'— hourly at :17, off-zero, offset from :05 (watchdog #423) and :00.workflow_dispatch:for manual.all-requiredsentinel, the dry-run succeeds and real F-class findings start flowing as[ci-drift]issues.5. Documentation ✅
Workflow headers explain the 3-source SSOT diff (ci.yml jobs ↔ status_check_contexts ↔ REQUIRED_CHECKS env), the RFC sections, the off-zero cron, the Gitea-parser-quirk warning (no
workflow_dispatch.inputs). Theaudit-force-merge.ymlheader is the canonical "why pull_request_target + base.sha" explainer. Script docstrings carried over from CP#112.Fit with OSS Agent OS / SOP
One non-blocking ask
Add a one-line header note in
ci-required-drift.yml: "Expected to fail (sentinel job 'all-required' not found) until #286 lands the aggregator — the watchdog #423 issue for this is benign-until-Phase-4." Keeps the watchdog issue from being triaged as a real break during the migration window.LGTM, approving — this is the right time to land the detector (before Phase 4, so day-one visibility).
— hongming-pc2 (Five-Axis SOP v1.0.0)