You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(fight): enact the path-filter self-win under squabble fight --apply (#22)
## Summary
`squabble fight` could *plan* a fight but never *enact* one. This PR
lands the first appliable self-win move, so the squabbler starts paying
rent instead of only diagnosing. It is the repo's own STATE `#1`
next-action.
`squabble fight --apply` strips an `on.*.paths` trigger filter that
strands a required check — the documented estate fix (boj-server "CI /
Required Status Checks") for a path-filtered required workflow that
reports "Expected" forever and leaves a PR `mergeable_state: blocked`.
It writes the workflow file and **stops**: no commit, no push, no CI
re-run.
**Why this is not a bypass (squabble ≠ bypass):** removing an inclusion
filter makes the required check run on *more* PRs, never fewer — a
strict coverage increase. It cannot turn a `Failed` run into `Passed`
and cannot drop a required context, so `Gate::evaluate` is untouched and
the SPARK invariant (Green iff every required check ran and passed)
needs no re-proof. **The `spark/` tree is unchanged.**
**Honesty about scope (no overclaim):** applying a config edit does not
make the gate green — only a CI re-run can — so the `Outcome` stays
`Red`, now carrying a new `applied` evidence section recording exactly
what changed on disk. Only `InjectPathFilterPassThrough` is appliable in
v0.1 (a pure local edit). Required-context reconcile and reusable re-pin
need the GitHub ruleset API / a target SHA and stay propose-only; the
webhook leg remains honestly absent.
## Changes
- **squabble-core**: add `AppliedChange` + `Report.applied`
(`#[serde(default)]`, back-compatible with older reports).
- **squabble-fight**: new `apply` module — `remove_on_paths` (tolerant
line-based YAML strip, trigger-block-scoped, block + inline forms,
`paths-ignore` left alone) and `apply_moves` (fail-closed, idempotent,
per-move). Classify a **Missing** + path-filtered required check as the
appliable `InjectPathFilterPassThrough` (a `Failed` check *did* trigger,
so the filter isn't its cause → stays an escalation). Dedupe self-won
path-filter traps out of the structural owner-assignment scan.
- **squabble-cli**: `squabble fight --apply` (propose remains the
default); an "applied to the working tree (no commit/push)" section in
the human report.
- **squabble-app**: unchanged — the HTTP backend stays propose-only (no
server-side mutation).
- **docs**: README status line +
`.machine_readable/descriptiles/STATE.a2ml` updated.
## RSR Quality Checklist
### Required
- [x] Tests pass (`cargo test --workspace` + `cargo test -p squabble-cli
--features boj`)
- [x] Code is formatted (`cargo fmt --all --check` clean)
- [x] Linter is clean (`cargo clippy` clean, default + `boj` feature)
- [x] No banned language patterns (Rust only; no new deps — `Cargo.lock`
unchanged)
- [x] No `unsafe` blocks without `// SAFETY:` comments (none added)
- [x] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`,
`Admitted`, `sorry`)
- [x] SPDX license headers present on all new/modified source files
(`apply.rs` carries `MPL-2.0`)
- [x] No secrets, credentials, or `.env` files included
### As Applicable
- [x] `.machine_readable/descriptiles/STATE.a2ml` updated (apply
milestone + next-actions)
- [ ] `.machine_readable/descriptiles/ECOSYSTEM.a2ml` updated (no
integration change)
- [ ] `.machine_readable/descriptiles/META.a2ml` updated (no new ADR —
this is inside ANCHOR's "downstream-may add legitimate moves")
- [x] Documentation updated for user-facing changes (README `--apply`)
- [ ] `TOPOLOGY.md` updated (no architecture change)
- [ ] `CHANGELOG` or release notes updated
- [x] New dependencies reviewed (none added — `apply` uses only `std`)
- [ ] ABI/FFI changes validated (none)
## Testing
- New unit tests in `apply.rs`: block-form strip keeps siblings;
inline-form strip; bare-trigger left valid when `paths:` was the only
sub-key; `paths:` inside `jobs:` untouched; idempotency; no-filter
no-op; temp-file end-to-end write + recorded no-op on second apply;
**fail-closed** on a missing file; non-appliable moves ignored.
- `workflows.rs`: a `Missing` + path-filtered required check classifies
as `InjectPathFilterPassThrough`; a `Failed` one stays
`GroundTruthCheckNames`.
- `lib.rs`: the same trap is a self-win **and not** also owner-assigned.
- `fight.rs`: `--apply` flag parsing (default off).
- gnatprove runs in CI, not this environment; the `spark/` files are
untouched by this change, so the proved invariant is unaffected.
## Screenshots
Live `--apply` on a fixture (`Missing` check emitted by a path-filtered
workflow):
```
self-win moves (squabbler's lane):
- inject path-filter pass-through for `wellknown` in `wellknown.yml`
applied to the working tree (no commit/push — operator's step):
- .github/workflows/wellknown.yml: removed the `on.*.paths` trigger filter so the
required check is always created (it now runs on every PR — a strict coverage
increase, never a bypass); layering an always-run `changes` detector … is a follow-up
```
Before → after (the `paths:` block is stripped; `branches:`,
`pull_request:`, `permissions:`, `jobs:`, and the SPDX header all
survive):
```diff
on:
pull_request:
- paths:
- - '.well-known/**'
branches: [main]
```
---
Note: commits on this branch are **unsigned** — this environment has
only the public half of the signing key, so doctrine #9 (`status:G`)
cannot be satisfied here. CI/owner must sign (or re-sign on
squash-merge) before merge.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01SUURnQUnzTXYftzWFs5pfZ
---
_Generated by [Claude
Code](https://claude.ai/code/session_01SUURnQUnzTXYftzWFs5pfZ)_
Copy file name to clipboardExpand all lines: .machine_readable/descriptiles/STATE.a2ml
+5-3Lines changed: 5 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -6,13 +6,13 @@
6
6
[metadata]
7
7
project = "cicd-squabbler"
8
8
version = "0.1.0"
9
-
last-updated = "2026-07-03"
9
+
last-updated = "2026-07-17"
10
10
status = "active" # active | paused | archived
11
11
12
12
[project-context]
13
13
name = "cicd-squabbler"
14
14
purpose = "CI/CD gate fighter: drives a stuck branch-protection gate to a legitimately-satisfied green by satisfying its required checks — never by admin override, enforce_admins toggle, or removing/renaming a required context. Rust fight engine + SPARK-proved gate invariant. Detachable; no hypatia/estate dependency."
{ name = "squabble-app loopback HTTP backend (axum, 127.0.0.1:7741): /health, /api/v1/diagnose, /api/v1/fight — the process the cicd-squabbler-mcp cartridge proxies to. The GitHub App *webhook* leg (check_run.requested_action + octocrab) remains honestly absent (no webhook route registered).", completion = 100 },
33
33
{ name = "boj-server expert client (feature `boj`, ureq; default build gains zero deps): `squabble fight --summon` maps each EscalateToExpert to its cartridge (Security→panic-attack-mcp, Proof→echidna-llm-mcp, Hypatia*→hypatia-mcp; dispatch-fix recorded as assessed+tracked, actuation external — no fixer cartridge exists). Fail-closed on unreachable experts; loud refusal when built without the feature.", completion = 100 },
34
34
{ name = "cicd-squabbler-mcp cartridge (boj-server-cartridges cartridges/domains/ci-cd/): squabble_fight + squabble_diagnose tools, mod.js proxy to SQUABBLE_BACKEND_URL, no ffi block (JS dispatch path)", completion = 100 },
35
+
{ name = "`squabble fight --apply` — enacts the first appliable self-win: strips an on.*.paths trigger filter that strands a required check (Missing-only classification), writing the workflow file. Fail-closed + idempotent + no commit/push/re-run; Outcome stays Red with an `applied` evidence section (no overclaim). Provably gate-strengthening (more coverage, never fewer) so the SPARK invariant is untouched. Other self-wins (context reconcile, reusable re-pin) stay propose-only pending the GitHub ruleset API.", completion = 100 },
"Implement `squabble fight --apply` (and the squabble-app GitHub App webhook leg: check_run.requested_action + octocrab) so self-win moves are enacted + re-run, not only planned.",
45
+
"Enact the remaining self-wins under --apply: required-context reconcile and reusable-workflow re-pin both need the GitHub ruleset API / a target SHA (network), so they stay propose-only until that host capability exists. (`--apply` already enacts the path-filter strip, which is a pure local file edit.)",
46
+
"Add the squabble-app GitHub App webhook leg (check_run.requested_action + octocrab — the \"Squabble!\" button) so a fight can be triggered from a PR, not only from the CLI; still honestly absent (no webhook route registered).",
45
47
"Live-fire --summon against a running boj-server with the expert backends up (hypatia :7701, panic-attack, echidna) — today's verification proves the wiring + fail-closed degradation; the experts themselves are not yet deployed.",
46
48
"Extend the SPARK invariant to cover the licence-policy gate class (v0.2; v0.1 moves are Rust-only for that class).",
47
49
"Register the CLADE (uuid 38bec2ed-bb12-44ee-9fb4-08872370c27b) into hyperpolymath/gv-clade-index (owner action).",
Copy file name to clipboardExpand all lines: README.adoc
+9-5Lines changed: 9 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,13 +15,17 @@ A standalone, per-repo CI/CD fighter that drives a stuck pipeline to a
15
15
*legitimate* green — or improves red→yellow, or fails and hands the human a
16
16
structured report. It *wins* the gate; it never cheats past it.
17
17
18
-
NOTE: Status — *v0.1 in active development (updated 2026-07-03).* The engine and
18
+
NOTE: Status — *v0.1 in active development (updated 2026-07-17).* The engine and
19
19
its formal invariant are real and verified (see below); live gate fetch via `gh`
20
20
landed in #6; `squabble fight` (classify each red as self-win | escalate |
21
21
assign-owner), the `squabble-app` loopback HTTP backend, and the boj-server
22
-
expert summoning (`--summon`, feature `boj`) landed in #13. Still open: the
23
-
`squabble apply` plumbing (moves are proposed, not yet executed) and the GitHub
24
-
App *webhook* leg (pending the owner's hosting decision). See
22
+
expert summoning (`--summon`, feature `boj`) landed in #13. `squabble fight
23
+
--apply` now *enacts* the first appliable self-win — stripping an `on.*.paths`
24
+
trigger filter that strands a required check — by writing the workflow file
25
+
(propose remains the default; `--apply` never commits, pushes, or re-runs CI).
26
+
Still open: applying the other self-wins (required-context reconcile,
27
+
reusable-workflow re-pin — these need the GitHub ruleset API / a target SHA) and
28
+
the GitHub App *webhook* leg (pending the owner's hosting decision). See
25
29
`docs/CHARTER.adoc` for the design and open owner decisions.
26
30
27
31
== Core principle: squabble ≠ bypass
@@ -42,7 +46,7 @@ check away, or any override are provably *not* paths to green.
42
46
|===
43
47
| `crates/squabble-core` | The fight engine. Pure, detachable, no hypatia/estate dependency — `diagnose → propose move → apply → verify-satisfied`. The valuable reusable part.
44
48
| `crates/squabble-fight`| The shared host brain: a2ml ecosystem-context reader + workflow ground-truth + the fight planner. One planner for the CLI and the App, so they cannot drift.
45
-
| `crates/squabble-cli` | `squabble` — runs locally or as a CI step. `fetch`, `diagnose`, and `fight` (with `--summon` under the `boj` feature to put expert verdicts on the report); apply plumbing next.
49
+
| `crates/squabble-cli` | `squabble` — runs locally or as a CI step. `fetch`, `diagnose`, and `fight` (with `--summon` under the `boj` feature to put expert verdicts on the report, and `--apply` to enact the path-filter self-win by editing the workflow — no commit/push).
46
50
| `crates/squabble-app` | The loopback HTTP backend (axum, `127.0.0.1:7741`): serves the shared planner to the boj-server `cicd-squabbler-mcp` cartridge. The GitHub App *webhook* leg (trigger button + octocrab) is still to come — no webhook route is registered.
47
51
| `spark/` | The formal heart. `Gate_Machine` + the proved theorem `green ⇔ (non-empty ∧ all passed)`.
0 commit comments