Commit graph

7 commits

Author SHA1 Message Date
adf907c963 fix(198): the action fails closed, the caller decides scheduling, the guard decides the forge (#198)
All checks were successful
CI / test (pull_request) Successful in 3m2s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 46s
@codex-reviewer-andresmgsl's second review, both points taken.

The refs action goes back to `forge_preflight || exit 1`. 97e63ac had it exit 0
with a notice so the PR check would not be red, and that conflated two
different questions: "this action cannot produce a verdict" is the ACTION's
contract and must stay a refusal, while "this check should not block the
board" is the CALLER's decision. The caller now carries it —
refs-guard.yml skips unless github.server_url is github.com, mirroring
forge_detect positively. A skipped check is a green head; an action that
reports success it did not earn is not. The leaked preflight_err temp file
goes with the revert.

The workflow guard asked the wrong question. `command -v gh` alone passes the
moment a Forgejo runner image happens to ship gh, and then dispatches against
a forge that cannot serve it — the client/forge mismatch forge_preflight
exists to prevent. It decides the FORGE first now, mirroring forge_detect
positively, and the binary second. The source guard splits to match: a
declaration guarded only by binary presence is reported, with a fixture that
fails on exactly that shape.

The warning text was also wrong on the facts, as noted: issue-event sweeps ARE
this caller's event-driven wakes, so they are precisely what is lost. It now
says the hourly scheduled sweep survives and every event-driven wake through
this caller does not, until #205.

Point 1 of that review — jq 1.6 accepting an empty payload — was already fixed
in 728102a, pushed before the review landed.

Verified under the runner's jq 1.6 as well as 1.7: 28 test files, 0 failed
both ways. shellcheck 0.10.0 (CI's pin), actionlint, self-ref, marker,
vendored, changelog-armed all clean with every file tracked.

Refs #198
2026-08-05 12:30:28 +00:00
728102a3ba fix(issueflow): issue_payload_valid refuses an empty payload on jq 1.6 too (#198)
All checks were successful
CI / test (pull_request) Successful in 3m3s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 45s
`CI / test` was red at 97e63ac on a case that passes on this box: upstream's
own "an empty payload is refused". The cause is not the test.

`jq -e` disagrees with itself across versions on EMPTY input. jq 1.7 exits 4 —
no valid result was ever produced. jq 1.6 exits 0. Measured both ways today
against the same filter. This instance's runner image
(ghcr.io/catthehacker/ubuntu:act-22.04) carries jq 1.6.

So on this forge the guard #247 D3 added specifically to refuse an unreadable
read was ACCEPTING one: an empty body read as a valid issue payload, and the
sweep would have reconciled an issue from a payload it never received. The
test is upstream's, it is correct, and it passes on a GitHub runner — which is
why upstream never saw this.

The fix does not depend on jq's exit code for an input it never receives: the
payload is read, emptiness is decided in the shell, and jq judges only a
non-empty body.

Verified under BOTH jq versions, not just the one on this box: empty refused
and healthy accepted on 1.6 and 1.7, and the whole suite green under jq 1.6 —
28 test files, 0 failed — as well as under 1.7.

Refs #198
2026-08-05 12:22:20 +00:00
06f05aebec fix(198): the workflow declares and refuses instead of being exempted by name (#198)
Some checks failed
CI / test (pull_request) Failing after 3m3s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 5s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 46s
@codex-reviewer-andresmgsl's blocker 1 is right and the filename exemption was
the wrong shape. It exempted the whole FILE — any later `gh` call anywhere in
labels.yml would have ridden in free — and it let the merge ship a step that
dies with `command not found` on every sweep on this forge, which #197's bar
does not permit.

The declaration mechanism already existed; a workflow simply could not reach
it. It can: `CEREMONY_FORGE_CLIENT: gh` in the step's env is the same
declaration actions/refs-not-closing carries, and the refusal that a script
gets from forge_preflight is inline here because a workflow has no shell to
call it from. The dispatch now warns by name, cites #205, and exits 0 rather
than reddening every sweep for a known gap.

So the guard needs no exemption list at all. It now requires the pair —
declared AND refusing — and reports a declaration that carries no refusal,
which is a permission slip for `command not found`.

That predicate was wrong on its first write, and its mutation test caught it:
`refuses_when_unavailable` matched the word `forge_preflight` inside
labels.yml's own comment explaining that it has NO forge_preflight to call. A
guard reading prose as evidence is the blind sweep again, in the guard written
to forbid it. Comments are stripped now, as gh_calls already stripped them.

Blocker 4: the nudge strips a trailing slash from the server URL. Reverting the
strip reds two cases.

Blockers 2 and 3 were already fixed in 97e63ac, before either review landed.

test/run.sh 28 files 0 failed under CI's env; shellcheck 0.10.0 (CI's pin),
actionlint, self-ref, marker, vendored and changelog-armed all clean, with
every file tracked this time.

Refs #198
2026-08-05 12:16:53 +00:00
97e63acef0 fix(refs-not-closing): report and skip on a forge it cannot speak, rather than reddening every PR (#198)
Some checks failed
CI / test (pull_request) Failing after 3m2s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 45s
The first head's `Refs guard` failed on this PR, correctly: spec 4's
CEREMONY_FORGE_CLIENT=gh declaration made forge_preflight refuse by name on
this forge. But that workflow runs on every pull request here, so the
declaration as first written turns every future PR red until #199 lands —
blocking the board for a gap that already has its own issue.

Refusing and scheduling are different questions. This action must never
produce a verdict from a graph it did not read, and it does not: on a forge it
cannot speak it now says so by name, cites #199, states that no verdict was
produced, and reaches the forge zero times. A preflight failure for any other
reason stays fatal, and on a forge it CAN speak nothing changes.

Also: five SC2016 findings in test/no-runtime-gh.test.sh. They were invisible
locally because shellcheck-all.sh lints TRACKED files and the guard was still
untracked when I ran it — a new file is exactly the case that check cannot
see. Verified this time against CI's pinned shellcheck 0.10.0 with the file
committed.

test/run.sh: 28 test files, 0 failed, under CI's CEREMONY_REQUIRE_* env.
shellcheck, actionlint, self-ref, marker and vendored guards all clean.

Refs #198
2026-08-05 12:09:05 +00:00
e035130f65 merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198)
Some checks failed
CI / test (pull_request) Failing after 33s
CI / release-exercise (pull_request) Successful in 12s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Failing after 5s
labels / labels (pull_request) Successful in 43s
`git merge` of upstream `8c3a4d1` onto `dad99dd`, common ancestor `84bb1a4`.
18 hunks in 10 files; `lib/forge.sh`, `lib/forge-github.sh` and
`lib/forge-forgejo.sh` conflict in none and come out byte-identical.

The resolutions the issue decided: VERSION and both CEREMONY_SELF_REF
carriers take upstream's numbers; `.github/labels.conf` and `drills/0.4.1.md`
keep this forge's; CHANGELOG keeps both sides and names the upstream commit
this tree carries.

The part the hunks did not contain. Upstream's 0.5.0/0.6.0 work added whole
functions to files this tree already owned, so `git merge` took its side
without raising a conflict — and with them, EIGHT runtime `gh` call sites
that #188 had removed. Seven are ported onto the shim: two reads and four
comment writes in issueflow-reconcile, and labels-reconcile's HEAD_COMMIT_AT
read. The eighth is `gh workflow run` in labels.yml, which a workflow cannot
declare a client for and whose Forgejo equivalent this instance answers with
500 rather than a 4xx — named with its reason rather than ported on a guess.

test/no-runtime-gh.test.sh makes the rule mechanical, because reviewing the
diff could not: four reviewers reading it each found a different subset, and
the contract suite stubs `gh`, so a reintroduced call site passes it.

Three seams the resolution decides are silent when resolved wrongly, and each
now has a case that fails on the wrong one: the merged record's `merged_at`
third column (without it every sort key ties and the highest PR number comes
back), the open gather's one-BODY-row-per-line feed (a whole decoded body as
one record loses every declaration including the first), and the whole-board
read whose COLLISION_FLAGS/WINDOW_FLAGS consumers auto-merged.

The open gather carries CLOSING rows as well as BODY rows. `Refs` alone would
drop every `Closes #N` link on the open side and reclaim a claim the PR was
holding — the existing base64 round-trip case is red without it.

actions/refs-not-closing declares CEREMONY_FORGE_CLIENT=gh: its only gather
is GraphQL, which Forgejo does not serve at all. #199 ports it.

test/run.sh: 28 test files, 0 failed. shellcheck and actionlint clean.

Refs #198
2026-08-05 11:56:23 +00:00
codex-bot-andresmgsl
7c755bcd40 release: stamp 0.4.0 candidate 2026-07-29 10:51:51 +00:00
codex-bot-andresmgsl
6fe549cdb7 docs: make BUILDER the single PR flow contract 2026-07-28 22:31:55 +00:00