Skip to content

promote: closure-router fix, CODEOWNERS, and actionlint to main - #70

Merged
LukasWodka merged 4 commits into
mainfrom
develop
Jul 26, 2026
Merged

promote: closure-router fix, CODEOWNERS, and actionlint to main#70
LukasWodka merged 4 commits into
mainfrom
develop

Conversation

@LukasWodka

@LukasWodkaLukasWodka commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Promotes the three fixes merged to develop today onto main.

This is the step that makes them real. All 96 caller workflows across 14 repos pin
tracebloc/.github/.github/workflows/*.yml@main, so until this lands the closure-router fix is not
running, CODEOWNERS is not enforced, and actionlint is not linting anything.

PRWhat it does
#67A manual issue close no longer invents Status=Prod. The *) default was inverted, so every hand-closed issue was being marked shipped — and because fr-gate compares by rank, Prod (10) satisfies every promotion gate.
#69CODEOWNERS. require_code_owner_reviews: true has been set on main with no file behind it, so it resolved to nothing on the repo that gates every other repo.
#66actionlint on this repo's own workflows, plus the 29 pre-existing findings cleared. This repo has never had a required status check; once merged, make actionlint required on main.

develop is 4 commits ahead of main and 0 behind — a clean fast-forward's worth of history, no
conflicts.

After merging

  1. Make actionlint a required check on main. It is the first one this repo will ever have.
  2. Verify the closure router: hand-close an issue and confirm its Status is left alone rather than
    set to Prod.

Part of RFC-BACKEND-0008 (tracebloc/backend#1262) D11/D18/D22, Phase 0.


Note

High Risk
Changes land on the org enforcement root at @main and alter kanban Status semantics that directly affect required FR promotion gates; misconfiguration could block merges or leave incorrect board state until reconcile runs.

Overview
Promotes org-wide tracebloc/.github hardening to main, where every other repo pins reusable workflows.

Governance: New CODEOWNERS makes require_code_owner_reviews on main real for workflows and scripts. New actionlint workflow runs on every PR (no paths filter), installs a version- and digest-pinned binary, requires shellcheck, and fails on any workflow/shell finding.

Security / process fix:kanban-closure-router no longer sets Status=Prod when an issue is closed as completed with no closing PR; it leaves kanban Status unchanged so manual closes cannot satisfy fr-gate. Routing logic is refactored to a single STATUS output; issue lookup uses ISSUE_NUMBER in the step env.

Correctness / lint cleanup:advance-deploy-env passes git log ranges as a bash array so the zero-before first-push path gets separate --max-count and SHA arguments. Several workflows add targeted # shellcheck disable=SC2016 on embedded GraphQL, plus small shell fixes (grouped GITHUB_OUTPUT, : > truncation in wip-limit-check, extra read field in kanban-reconcile apply step).

Reviewed by Cursor Bugbot for commit 151cc3f. Bugbot is set up for automated code reviews on this repo. Configure here.

LukasWodkaand others added 4 commits July 25, 2026 11:36
An issue closed by hand as completed was routed to Prod. The closing-PR
lookup returns an empty base when no PR closed the issue, and the case
statement's `*)` arm mapped that empty string to "Prod". This was an
explicit default, not an accident of fallthrough, and it also swallowed
the case where the GraphQL lookup simply failed.
Observed live: backend#1171-#1176 were ticked off by hand on 2026-07-25
and all six were moved to Prod, despite their code having been merged
only to develop. The run logs show "Routing decision: status=Prod" while
closedByPullRequestsReferences returned an empty list for each.
This matters beyond tidiness. fr-gate.yml is a required status check that
lets a promotion PR merge only when every contained item sits in Ready
for staging or Ready for prod. An item falsely parked in Prod satisfies
that gate with nobody having reviewed it, so this bug can let unreviewed
work through the very gate designed to stop it.
Fix: separate "no closing PR" from "closing PR with an unrecognised
base". With no closing PR the step now emits an empty status, and the
update step (already guarded on a non-empty status) skips entirely,
leaving the card in whatever column it was already in. Closing an issue
by hand tells us nothing about where the code is deployed, so the honest
answer is to change nothing. not_planned and a missing state_reason still
route to Cancelled, and every PR-merge route is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(closure-router): a manual issue close must not invent a deploy state
`main` has had `require_code_owner_reviews: true` since branch protection was
applied, but this repository has never contained a CODEOWNERS file — so the
requirement resolved to nothing and any two of the seven org members could
change the gates themselves with one approval and no CI.
That matters more here than anywhere else. 96 caller workflows across 14 repos
pin `tracebloc/.github/.github/workflows/*.yml@main`, and all of them pass
`secrets: inherit`, so a single merge to this repo takes effect org-wide on the
next workflow run and reaches every secret the calling repos hold.
Adds @LukasWodka as owner of the workflows, the scripts they call, and the
repository by default.
This does not change the self-merge path: as sole owner he cannot approve his
own PRs and still relies on `enforce_admins: false`. The control added here is
over the other six members' changes to the enforcement layer.
…sting findings (#66)
* fix(workflows): clear all 29 actionlint findings
actionlint (with its native shellcheck pass) reported 29 findings across 8
of this repo's reusable workflows. None had ever been checked by CI. Fixed
the real ones, suppressed the correct-code ones at the line.
Real fixes:
- advance-deploy-env.yml (SC2086): the git log range was an unquoted
$RANGE. Rewritten as an array, NOT simply quoted -- on the first push to
a branch the range is two argv entries (--max-count=50 and the SHA), so
"$RANGE" would have handed git one bogus argument and broken PR
discovery on that path entirely. Verified both push shapes produce
identical PR lists before and after.
- wip-limit-check.yml (SC2034): REPO_NAME was assigned and never used --
a copy-paste leftover from the workflows that do use it. Removed.
- wip-limit-check.yml (SC2188): `> /tmp/items.txt` is a redirection with
no command. Now `: > /tmp/items.txt`, matching the form already used in
kanban-reconcile.yml. Byte-identical truncate.
- kanban-reconcile.yml + fr-pass-comment.yml (SC2129): consecutive
redirects to $GITHUB_OUTPUT grouped into a single `{ ... } >>` block.
Verified byte-identical output.
Suppressed as correct code (23x SC2016): single-quoted GraphQL documents
where $org / $num / $p / $i / $f / $o are GraphQL variables interpolated
server-side. Expansion is exactly what must NOT happen. Each gets a
per-line `# shellcheck disable=SC2016` with the reason -- no file-wide
directive and no actionlint config exclusion, so future code is not
silently covered.
kanban-reconcile.yml (SC2034, `label`): NOT dead code. It is the 4th
`read` variable and exists to stop `read` folding field 4 of moves.tsv
into $reason -- removing it would append "<TAB><category>" to every
[OK]/[FAIL] log line. Kept, documented, suppressed.
Behaviour is otherwise unchanged: the remaining diff is comments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci(actionlint): lint this repo's own workflows on every PR
This repo publishes 16 reusable workflows that every other repo consumes
at `@main`, and it had no CI of its own -- nothing validated a workflow
file before it merged and went org-wide. This closes that gap.
actionlint parses each workflow, type-checks every ${{ }} expression,
validates the runs-on / uses / needs wiring, and runs shellcheck over
every `run:` block.
A hard gate from day one (any finding fails the job), which is only
affordable because the preceding commit cleared the backlog -- the tree
is at zero findings, so the job is green and can be marked a required
status check immediately.
Notes:
- actionlint is pinned by version AND verified against a pinned SHA-256
of the release tarball, rather than via a wrapper action that downloads
it for us. actions/checkout is pinned to a full commit SHA.
- A step asserts shellcheck is present: actionlint silently skips every
shell check when the binary is missing, which would leave the gate
green while checking far less.
- Includes a concurrency group so a re-push supersedes the previous run.
- Requests only `contents: read`.
- The `paths:` filter means the job does not run on PRs touching no
workflow file; the header documents what that implies before marking
the check required.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci(actionlint): drop the paths filter so the check can be required
A required status check with a `paths:` filter never runs on a PR that
touches nothing matching it, and GitHub then waits forever for a status
that cannot arrive -- so the PR is stuck. The job takes ~7s, which is far
cheaper than that failure mode.
Raised on the PR as the one thing to settle before marking it required.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@LukasWodkaLukasWodka self-assigned this Jul 26, 2026
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

👋 Heads-up — Code review queue is at 41 / 30

Above the WIP limit. The team convention is to review existing PRs before opening new work.

Open PRs currently in Code review (oldest first):

Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.)

@LukasWodkaLukasWodka added the skip-fr-gate Emergency override of the FR gate. Requires a written reason on the PR. label Jul 26, 2026
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

The gate is right. The ladder is broken.

gate / gate failed with:

Items not yet at 'Ready for prod' (or later): #66(Ready for staging) #67(Ready for staging) #69(Ready for staging)

That is correct behaviour, and there is no legal way to satisfy it in this repo:

  1. The three items sit at Ready for stagingrank 7. Merging into main requires rank 9.
  2. Rank 7 → 8 happens on a push to staging. tracebloc/.githubhas no staging branch.
  3. /fr-pass cannot help: fr-pass-comment.yml handles only FR on dev and FR on staging. An item
    at Ready for staging falls to the *) arm and is told "wrong column".

So the item is stuck at rank 7 against a required rank 9, permanently. This is
RFC-BACKEND-0008 D28the promotion ladder is unsatisfiable in 11 of 17 repos, so the process
forces the bypass it exists to prevent
— reproducing live, on the PR that carries the fixes.

skip-fr-gate did not exist in this repo either, so until a moment ago the only remaining exit was
dragging three cards by hand, which is the silent bypass (D27-L3) and leaves no trace. The label has
now been created here and in every other repo that was missing it, so the override is at least
visible.

Why I am using the override rather than dragging cards

Both get the same merge. The difference is that this one is on the PR, in the timeline, with a
reason attached — and the review's finding was precisely that overrides were happening invisibly.

What this PR unblocks

Nothing here is live yet: all 96 caller workflows pin @main, and main is still on 54b1817a
(18 June). Until this merges, the closure router is still marking every hand-closed issue as
Prod, CODEOWNERS is still vacuous, and actionlint is not running.

The real fix

backend#1274 (D8/D28) gives every repo a staging branch so this state stops existing. Until that
lands, every promotion in the 12 repos without one will hit this same wall.

@LukasWodka
LukasWodka merged commit 2cf8f2f into mainJul 26, 2026
6 of 8 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-fr-gateEmergency override of the FR gate. Requires a written reason on the PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LukasWodka