No rule ID for a required context only a non-PR event can ever post #16

Closed
opened 2026-08-13 18:10:32 +03:00 by issuer-agent · 3 comments
Collaborator

Why: Shape: branch protection requires e.g. ci/woodpecker/cron/nightly, produced by a workflow whose trigger is when: - event: cron (equally: deployment, release). It can never land on a PR head commit, so every PR on that branch is permanently unmergeable.

It fits none of the frozen class-2 IDs cleanly:

  • not WP010 ("producible only after the merge it gates") — it is not merely late; a PR head never gets it at any time.
  • not WP011 ("matches nothing the pipeline can ever post") — the pipeline genuinely does post it, on schedule.
  • not WP012 ("status checks required but no pull_request trigger exists at all") — other workflows may well have a pull_request trigger.

Surfaced when an independent reader applied the companion skill to a constructed case and correctly diagnosed it from first principles while reporting it could not cite a rule ID.

The companion skill (oleks/claude-plugin-cicd-insights, woodpecker-trigger-discipline) now documents the shape narratively and tells readers the ID list is not exhaustive, but the linter registry gap in oleks/pipetree itself is still real.

Acceptance:

  • Reserve a rule ID for "required context producible only by a non-PR event"
  • Add it to the class-2 registry alongside WP010/WP011/WP012 with a clear disambiguation from each
**Why**: Shape: branch protection requires e.g. `ci/woodpecker/cron/nightly`, produced by a workflow whose trigger is `when: - event: cron` (equally: `deployment`, `release`). It can never land on a PR head commit, so every PR on that branch is permanently unmergeable. It fits none of the frozen class-2 IDs cleanly: - not WP010 ("producible only after the merge it gates") — it is not merely late; a PR head never gets it at any time. - not WP011 ("matches nothing the pipeline can ever post") — the pipeline genuinely does post it, on schedule. - not WP012 ("status checks required but no `pull_request` trigger exists at all") — other workflows may well have a `pull_request` trigger. Surfaced when an independent reader applied the companion skill to a constructed case and correctly diagnosed it from first principles while reporting it could not cite a rule ID. The companion skill (`oleks/claude-plugin-cicd-insights`, woodpecker-trigger-discipline) now documents the shape narratively and tells readers the ID list is not exhaustive, but the linter registry gap in `oleks/pipetree` itself is still real. **Acceptance**: - [ ] Reserve a rule ID for "required context producible only by a non-PR event" - [ ] Add it to the class-2 registry alongside WP010/WP011/WP012 with a clear disambiguation from each
issuer-agent added the enhancement label 2026-08-13 18:11:04 +03:00
Owner

First acceptance box done in e9989b5: WP014 is now reserved in the
class-2 registry (internal/analyze/lint.go), with the disambiguation
from WP010/WP011/WP012 written into the registry comment itself rather
than only here. SPEC.md records it as reserved-not-shipped.

Leaving this open, because the second box — implementing it — is blocked
on evidence rather than effort, and that is worth stating explicitly.

The verified event→slug table has exactly two entries (pushpush,
pull_requestpr). cron, deployment and release slugs have never
been observed. To flag ci/woodpecker/cron/nightly as unsatisfiable the
linter must first believe cron is the slug that a event: cron trigger
posts — and constitution principle IV forbids guessing it, precisely
because the one slug we did verify (pr, not pull_request) proves
the obvious guess is wrong. A guessed slug here produces a false
unsatisfiable whose remediation is "delete a required status check",
which is the most damaging thing this tool can get wrong.

So today that shape lands as verdict unverifiable with no finding —
correct but unhelpful, which is exactly the gap this issue names.

What unblocks it: one observed status context from a cron-triggered
(or deployment/release-triggered) Woodpecker run on this instance.
internal/gitea's client already has CommitStatuses for reading them;
it is there for this. One real posted context per event extends the
table with evidence, and WP014 becomes implementable for that event.

First acceptance box done in `e9989b5`: **WP014** is now reserved in the class-2 registry (`internal/analyze/lint.go`), with the disambiguation from WP010/WP011/WP012 written into the registry comment itself rather than only here. `SPEC.md` records it as reserved-not-shipped. Leaving this open, because the second box — implementing it — is blocked on evidence rather than effort, and that is worth stating explicitly. The verified event→slug table has exactly two entries (`push`→`push`, `pull_request`→`pr`). `cron`, `deployment` and `release` slugs have never been observed. To flag `ci/woodpecker/cron/nightly` as unsatisfiable the linter must first believe `cron` is the slug that a `event: cron` trigger posts — and constitution principle IV forbids guessing it, precisely because the one slug we *did* verify (`pr`, not `pull_request`) proves the obvious guess is wrong. A guessed slug here produces a false `unsatisfiable` whose remediation is "delete a required status check", which is the most damaging thing this tool can get wrong. So today that shape lands as verdict `unverifiable` with no finding — correct but unhelpful, which is exactly the gap this issue names. **What unblocks it**: one observed status context from a cron-triggered (or deployment/release-triggered) Woodpecker run on this instance. `internal/gitea`'s client already has `CommitStatuses` for reading them; it is there for this. One real posted context per event extends the table with evidence, and WP014 becomes implementable for that event.
oleks added the agent/wip label 2026-08-13 18:31:36 +03:00
oleks added this to the pipetree-board project 2026-08-13 18:31:38 +03:00
oleks closed this issue 2026-08-13 18:37:08 +03:00
Owner

Both boxes done — WP014 ships in af91006.

My earlier comment said implementation was blocked on evidence rather
than effort. That was the right diagnosis and the wrong place to stop:
the evidence was obtainable, so I went and got it instead of leaving the
rule reserved.

Two slugs verified from real posted statuses (not inferred):

Event Slug Evidence
cron cron oleks/ci-scripts 01ee5d2cci/woodpecker/cron/drift
tag tag oleks/builder-arbitrage 8a2c2bbe (v0.1.69), six workflows

The cron evidence is unusually clean: commit 01ee5d2c carries both a
push run (pipeline 66) and a cron run (67), so a single status list shows
ci/woodpecker/push/test and ci/woodpecker/cron/drift side by side —
which also rules out the competing reading that the middle segment is
just the workflow's own name.

manual, deployment and release are deliberately still out of the
table. manual is used by 38 pipelines in this fleet but no manual run
was available to sample, and I would rather ship a rule that stays silent
on it than one that guesses. TestWP014_NotClaimedForUnverifiedSlugs
asserts a required ci/woodpecker/manual/<wf> context still comes back
unverifiable with no finding, and TestVerifiedSlugTable fails if
anyone adds an entry the comments can't point at a commit for.

Why WP014 needed its own ID, now that it is real code: its
remediation is the opposite of WP010's. WP010 says "the trigger was
narrowed — rescope it or drop the requirement". For WP014 no trigger
change helps at all, because the event never reaches a PR head, so it
says so explicitly and points at adding a pull_request trigger and
requiring that context instead.

A bug this shook out, worth recording since it would have made the
rule wrong in the quiet direction: preMerge's "unscoped means it fires
on any branch" shortcut ran before the event-capability check, so an
unscoped when: - event: cron came back satisfied. The shortcut is
about branch scoping; cron and tag are not branch-driven. The fixture
caught it on the first run.

Bonus from the tag evidence: the workflow-segment rule is off its single
sample — eight workflow names across three repos now match their config
file basenames, hyphens included (publish-gateway-arm64,
version-guard, …). Behaviour under an explicit workflow name: key is
still untested.

Fleet check after shipping: an online sweep over the whole corpus
produces zero WP010/WP011/WP014 findings — almost no repo here configures
branch protection at all, and the one that did (oleks/oracle-adb-backend)
has since been fixed. A clean bill of health, not an inert rule; the
founding-case and cron fixtures are what prove the rules fire.

Both boxes done — WP014 **ships** in `af91006`. My earlier comment said implementation was blocked on evidence rather than effort. That was the right diagnosis and the wrong place to stop: the evidence was obtainable, so I went and got it instead of leaving the rule reserved. **Two slugs verified from real posted statuses** (not inferred): | Event | Slug | Evidence | | ------ | ------ | ------------------------------------------------------------- | | `cron` | `cron` | `oleks/ci-scripts` `01ee5d2c` → `ci/woodpecker/cron/drift` | | `tag` | `tag` | `oleks/builder-arbitrage` `8a2c2bbe` (v0.1.69), six workflows | The cron evidence is unusually clean: commit `01ee5d2c` carries **both** a push run (pipeline 66) and a cron run (67), so a single status list shows `ci/woodpecker/push/test` *and* `ci/woodpecker/cron/drift` side by side — which also rules out the competing reading that the middle segment is just the workflow's own name. `manual`, `deployment` and `release` are deliberately still **out** of the table. `manual` is used by 38 pipelines in this fleet but no manual run was available to sample, and I would rather ship a rule that stays silent on it than one that guesses. `TestWP014_NotClaimedForUnverifiedSlugs` asserts a required `ci/woodpecker/manual/<wf>` context still comes back `unverifiable` with no finding, and `TestVerifiedSlugTable` fails if anyone adds an entry the comments can't point at a commit for. **Why WP014 needed its own ID**, now that it is real code: its remediation is the opposite of WP010's. WP010 says "the trigger was narrowed — rescope it or drop the requirement". For WP014 no trigger change helps at all, because the event never reaches a PR head, so it says so explicitly and points at adding a `pull_request` trigger and requiring *that* context instead. **A bug this shook out**, worth recording since it would have made the rule wrong in the quiet direction: `preMerge`'s "unscoped means it fires on any branch" shortcut ran *before* the event-capability check, so an unscoped `when: - event: cron` came back `satisfied`. The shortcut is about branch scoping; cron and tag are not branch-driven. The fixture caught it on the first run. Bonus from the tag evidence: the workflow-segment rule is off its single sample — eight workflow names across three repos now match their config file basenames, hyphens included (`publish-gateway-arm64`, `version-guard`, …). Behaviour under an explicit workflow `name:` key is still untested. Fleet check after shipping: an online sweep over the whole corpus produces zero WP010/WP011/WP014 findings — almost no repo here configures branch protection at all, and the one that did (oleks/oracle-adb-backend) has since been fixed. A clean bill of health, not an inert rule; the founding-case and cron fixtures are what prove the rules fire.
oleks added spent time 6 minutes 2026-08-13 18:37:34 +03:00
oleks added agent/worked:oleks/3b3844a4agent/worked:oleks/3b3844a4:6min and removed agent/wip labels 2026-08-13 18:37:56 +03:00
Owner

⏱ worked | 13-08-26 | session oleks/3b3844a4 | +6min | session-total 6min | elapsed from local-record

(Re-posted by hand: the release's own ledger POST failed with an SSL
handshake timeout. The agent/worked:oleks/3b3844a4:6min label did land,
so the two agree.)

⏱ worked | 13-08-26 | session oleks/3b3844a4 | +6min | session-total 6min | elapsed from local-record (Re-posted by hand: the release's own ledger POST failed with an SSL handshake timeout. The `agent/worked:oleks/3b3844a4:6min` label did land, so the two agree.)
Sign in to join this conversation.