Skip to content

CI resilience (per-PR): detect Actions-policy startup_failure instead of mis-attributing it #15

Description

@hyperpolymath

Context — the estate-wide "Actions-policy CI outage"

Two failure modes recur across the estate: mode-1 (allowed_actions=selected + empty patterns_allowed → external uses: rejected → startup_failure) and mode-2 (sha_pinning_required=true + tag-pinned workflow files → github-owned actions fail the pin rule). The squabbler is the per-PR / per-repo reactive lane for a single stuck gate — but today it mis-attributes this class:

  • a governance-reusable startup_failure → AssignGateOwner{OwnedUpstream:standards} (wrong: the reusable is fine; the local Actions policy blocks it);
  • a tag-pinned codeql.yml startup_failure → EscalateToExpert{Security} (wrong: a scan won't fix a tag-vs-SHA rejection).

The symptom already lands in the Gate (fetch.rs::parse_rollup maps STARTUP_FAILURE → CheckRun::Failed); what's missing is the why-probe. This is a real added rule inside the existing diagnose→classify→move pipeline. Fleet-scale remediation stays out of scope (escalate to ExpertGroup::HypatiaFleet → gitbot-fleet).

Decision of record

Estate default = allowed_actions=all + sha_pinning_required=true. The squabbler's settings-move should propose that posture (or an explicit patterns_allowed superset) while keeping sha_pinning_required=true — never disable pinning. squabble ≠ bypass still holds: these moves only let a required check start; they drop no required context and weaken no check.

Changes

  1. crates/squabble-cli/src/fetch.rs — add an Actions-policy why-probe. When any required context resolves to STARTUP_FAILURE (already handled at parse_rollup, ~L171–184), additionally call gh api repos/{o}/{r}/actions/permissions + .../selected-actions and thread {allowed_actions, sha_pinning_required, patterns_allowed} into the fight as classification input (it already fetches the ruleset via gh api .../rules/branches/{b}).

  2. crates/squabble-fight/src/workflows.rs — extend WorkflowInfo with tag_pinned_uses: Vec<String> and external_uses: Vec<String> (parse in parse_workflow alongside reusable_repo()). In classify(), when the emitting workflow uses an external reusable/action absent from a selected allowlist (mode-1) or a tag-pinned uses: under sha_pinning_required (mode-2), return the NEW move instead of the current OwnedUpstream / Escalate-Security mis-attribution.

  3. crates/squabble-core/src/moves.rs — add Move variants: PinWorkflowActions { workflow, refs } (self-win, SHA-pin at source; twin of the existing RepinReusableWorkflow) and ReconcileActionsPolicy { blocked_ref, add_patterns } / SetActionsAllowedAll (a legitimate non-bypass settings move). Add them to every_move_is_legitimate_by_construction and describe() coverage so the SPARK invariant still holds.

  4. crates/squabble-core/src/lib.rs — no structural change; diagnose_with_hints is the existing plug seam — feed it the new hints from the host.

  5. crates/squabble-fight/src/lib.rs::structural_scan() — add a probe surfacing "allowed_actions=selected with empty/insufficient patterns_allowed while an external reusable/action is used" and "tag-pinned uses: under sha_pinning_required" even before the check has (yet) startup_failure'd, mirroring the existing well-known/path-filter/AGPL probes.

  6. examples/actions-policy-deadlock.json — new offline gate fixture (mirroring examples/gate-deadlock.json) with a required check as missing/startup_failure for regression coverage. The squabbler's own repo currently shows a Secret Scanner startup_failure — an in-repo dogfood target for .github/workflows/dogfood-gate.yml.

  7. docs/CHARTER.adoc — add the "Actions-policy startup_failure deadlock" class to the v0.1 fight playbook, stating the tradeoff explicitly (prefer allowed_actions=all while KEEPING sha_pinning_required=true, plus SHA-pin the workflow refs).

DO-NOT pointer: .github/settings.yml (probot/settings) manages branch protection/labels/merge only and cannot set Actions permissions — the fix goes via the REST actions/permissions + .../selected-actions endpoints (single-repo from the squabbler, fleet-wide from gitbot-fleet), never via settings.yml.

Acceptance criteria

  • A required-context STARTUP_FAILURE triggers the why-probe; the fight receives {allowed_actions, sha_pinning_required, patterns_allowed}.
  • classify() returns PinWorkflowActions for a tag-pinned-under-sha-pinning gate and ReconcileActionsPolicy/SetActionsAllowedAll for an empty-allowlist gate — not OwnedUpstream{standards} / Escalate{Security}.
  • New Move variants pass every_move_is_legitimate_by_construction (SPARK invariant intact) and have describe() text.
  • examples/actions-policy-deadlock.json is diagnosed to the new move in a unit test.

Model tier

Opus — Rust across three crates plus the SPARK-legitimacy invariant; the classification changes are subtle (distinguishing "policy blocks the check from starting" from genuine upstream/out-of-lane reds).

Related

Fleet-scale detection/remediation belong to hyperpolymath/hypatia + hyperpolymath/gitbot-fleet (the squabbler escalates the fleet dimension to ExpertGroup::HypatiaFleet); source-of-truth allowlist in hyperpolymath/standards.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions