613 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 9daeeb756e |
Merge pull request 'actions/refs-not-closing — gather over REST, so the guard produces verdicts on this forge (#199)' (#214) from build/199-refs-not-closing-rest into main
All checks were successful
CI / test (push) Successful in 3m14s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 6s
CI / action-exercise (push) Successful in 5s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 7s
Reviewed-on: #214 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
|
|
3bde48f24c |
test(labels): unset GITHUB_API_URL in the no-api case — env preserves it
All checks were successful
CI / test (pull_request) Successful in 3m13s
CI / release-exercise (pull_request) Successful in 10s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
Plain `env` preserves the parent environment, and on the runner every step arrives with GITHUB_API_URL set — the premise of the fix under test — so the unset-refusal case inherited it and never exercised the refusal. It passed only in a dev shell that lacks the variable: the environment distance UPSTREAM-SYNC.md step 7 warns about, in the test written the same day (@kimi-reviewer-andresmgsl, run 468). Refs #205 |
||
|
|
0c2db9d85a |
test(labels): remove the stale never-silenced check, superseded behaviourally
Some checks failed
CI / test (pull_request) Failing after 3m14s
CI / release-exercise (pull_request) Successful in 10s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
The check grepped the gh workflow run line #205 removes, so it passed on every REST implementation including one that swallows a failed POST — a green assertion whose name claimed an invariant its implementation could not observe. Its replacement lives in test/labels-dispatch.test.sh: a transport failure must fail the extracted step, plus a code-aware no-|| true guard (@codex-reviewer-andresmgsl, !213 review round 2). Refs #205 |
||
|
|
396744618f |
Merge remote-tracking branch 'origin/main' into build/199-refs-not-closing-rest
All checks were successful
CI / test (pull_request) Successful in 3m13s
CI / release-exercise (pull_request) Successful in 10s
CI / self-guards (pull_request) Successful in 8s
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 6s
labels / labels (pull_request) Successful in 8s
|
||
|
|
768d54d1db |
Merge remote-tracking branch 'origin/main' into build/205-dispatch-rest
Some checks failed
CI / test (pull_request) Failing after 3m13s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
|
||
| 94d5b81964 |
Merge pull request 'drills/README.md — the standing runner-probe venue, and why the drill disposal rule does not apply to it (#202)' (#207) from build/202-runner-probe-venue into main
All checks were successful
CI / test (push) Successful in 3m12s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 7s
CI / action-exercise (push) Successful in 6s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 6s
Reviewed-on: #207 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: glm-reviewer-andresmgsl <andres+5@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
|
|
37e31ffd85 |
fix(labels): refuse an unset API root, name transport failures, update the docs
Some checks failed
CI / test (pull_request) Failing after 3m13s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
Three corrections from @codex-reviewer-andresmgsl's review of
|
||
|
|
4e28d437d6 |
fix(refs-not-closing): gather over REST, so the guard produces verdicts here
All checks were successful
CI / test (pull_request) Successful in 3m15s
CI / release-exercise (pull_request) Successful in 12s
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 6s
labels / labels (pull_request) Successful in 8s
The action's entire gather was one GraphQL query asking GitHub for its own parse of the closing keywords. Forgejo serves no GraphQL at all — /api/graphql 404s here and a forgejo-runner job arrives with GITHUB_GRAPHQL_URL empty — so there was nothing to translate it to. It is re-expressed, as #188 re-expressed its own two GraphQL sites, over two reads both backends serve plus this repo's own parser. The graph was called authoritative for including "closing keywords and sidebar links". Those halves resolve differently here: Forgejo has no sidebar-link concept, so nothing is lost there, but it DOES honour closing keywords in commit messages. A body-only port would miss a PR that closes an issue from a commit subject — exactly the contradiction this action exists to catch — so the closing set unions the body and every commit message. The hasNextPage refusal is relocated, not dropped: --paginate carries the forgejo backend's x-total-count completeness proof, and a short gather refuses rather than returning a partial verdict. lib/issue_references.sh extracts the LOCAL/CROSS classifier from issueflow-reconcile's executable. closes_references.sh's header recorded that dependency in prose; a composite action cannot source a reconciler to borrow one function, because sourcing a reconciler runs one. refs-guard.yml's github-only gate is removed in the same change. A portable action behind that gate is a guard that passes by never running. The contract test drives the boundary on BOTH backends with stubs at the transport. Mutations: body-only parse reds 4 cases, dropping --paginate reds the partial-gather case, ignoring a failed read reds 9. Refs #199 |
||
|
|
935a813d75 |
fix(labels): wake the sweep over REST, so board events reconcile in seconds
All checks were successful
CI / test (pull_request) Successful in 3m13s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
`.github/workflows/labels.yml` dispatched the sweep with `gh workflow run`, the eighth runtime gh call site the 0.6.0 merge reintroduced and the only one !204 did not port. On this forge the runner carries neither gh nor a GitHub API, so the step refused and the entire event-driven reconcile path ended there — every transition waiting up to an hour for the scheduled sweep. The workflow-dispatch endpoint has the SAME shape on both forges: POST {api}/repos/{owner}/{repo}/actions/workflows/{file}/dispatches {"ref": "<branch>", "inputs": {...}} -> 204, empty body so the step no longer decides a forge at all. The CEREMONY_FORGE_CLIENT=gh declaration and both inline refusals are removed rather than ported, and test/no-runtime-gh.test.sh now asserts their ABSENCE — an opt-out with no gh behind it is a standing permission slip. The ref is supplied explicitly and taken from the repository, never from GITHUB_REF_NAME: on a pull_request_target run that is `<n>/merge`, which is not a branch. A non-204 still fails the job, keeping the misconfiguration alarm the trigger exists to be, and the diagnostic explains Forgejo's empty 500 rather than passing a bare status to a reader who will go looking for an outage that is not there. test/labels-dispatch.test.sh extracts the shipped step and executes it against a recording stub, asserting the method, endpoint, ref and inputs actually sent. Dropping the inputs or ignoring a non-204 both red the suite. Refs #205 |
||
|
|
262705d394 |
Merge remote-tracking branch 'origin/main' into build/202-runner-probe-venue
All checks were successful
CI / test (pull_request) Successful in 3m12s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
|
||
| a13aa6d4b9 |
Merge pull request 'fix(docs-sync): the doctrine mirror is fetched from the forge in play, never a built-in one (#201)' (#203) from build/201-docs-sync-forge-source into main
All checks were successful
CI / test (push) Successful in 3m12s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 8s
CI / action-exercise (push) Successful in 6s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 7s
Reviewed-on: #203 Reviewed-by: glm-reviewer-andresmgsl <andres+5@heavyduty.builders> Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
|
|
65cee3fdf9 |
Merge remote-tracking branch 'origin/main' into build/202-runner-probe-venue
All checks were successful
CI / test (pull_request) Successful in 3m12s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
|
||
|
|
1bf7091d39 |
Merge remote-tracking branch 'origin/main' into build/201-docs-sync-forge-source
All checks were successful
CI / test (pull_request) Successful in 3m12s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
|
||
| 5c924294bf |
Merge pull request 'docs/UPSTREAM-SYNC.md + .upstream-ref + the delta-inventory guard — the recurring sync, written from having just done one (#200)' (#208) from build/200-upstream-sync-doc into main
All checks were successful
CI / test (push) Successful in 3m13s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 6s
CI / action-exercise (push) Successful in 6s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 7s
Reviewed-on: #208 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
| 0cb320b807 |
Merge pull request 'lib/forge-*.sh + labels-reconcile — forge_commit_at, because Forgejo serves a single commit at /git/commits/{sha} (#209)' (#212) from build/209-commit-at into main
Some checks failed
release / release (push) Waiting to run
CI / test (push) Has been cancelled
CI / release-exercise (push) Has been cancelled
CI / self-guards (push) Has been cancelled
CI / action-exercise (push) Has been cancelled
CI / docs-sync-exercise (push) Has been cancelled
Reviewed-on: #212 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
| 03143ff0ed |
Merge pull request 'issueflow-reconcile — the board discriminator is .pull_request == null, not has(), or this forge has no issues (#210)' (#211) from build/210-discriminator into main
Some checks failed
release / release (push) Waiting to run
CI / test (push) Has been cancelled
CI / release-exercise (push) Has been cancelled
CI / self-guards (push) Has been cancelled
CI / action-exercise (push) Has been cancelled
CI / docs-sync-exercise (push) Has been cancelled
Reviewed-on: #211 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
|
|
368621dcea |
docs(runner-probes): bind caller kind to its path class
All checks were successful
CI / test (pull_request) Successful in 3m8s
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 8s
The manifest target-validation added in
|
||
| 6874e76c04 |
docs(runner-probes): the checker binds the manifest to its target, and the snippets lint clean standalone (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl, and he linted the published snippets DIRECTLY, which my "parse, lint clean" claim had never meant. 1. THE CHECKER DID NOT CHECK THE TARGET. It accepted <fork> <code-sha> <armed-sha> and used none of them — SC2034 on all three, which is the same defect the linter and the reviewer found independently. It proved only "tree equals manifest", so a manifest generated with the ARMED sha where the candidate belonged, against a tree rewritten to that same wrong value, passed. Wrong-but-consistent is exactly what this gate exists to reject. Each manifest `want` is now validated against the independently supplied target before the tree is compared to it. 2. ONE ORDER, NOT TWO. Step 2 said "commit the arming AND write the manifest" while the prose below correctly said to generate from the PRE-arming tree. The manifest enumerates the carriers that must CHANGE, so it has to see them before they do — generating afterwards enumerates rewritten rows and loses the canonical internal-checkout ones entirely. The generator's first parameter is <candidate-checkout> now, and says so. 3. THE SNIPPETS LINT CLEAN STANDALONE. SC2016 needed a scoped directive — and the first placement was itself invalid: SC1124, a directive may precede a complete command, not an individual case branch. The checker's mktemp gets a trap. Driven, the new controls: correct manifest + tree + target args passes wrong fork, manifest AND tree consistent refuses wrong candidate sha, consistent refuses armed sha where the candidate belongs refuses plus every earlier class still red, and both snippets ShellCheck-clean when extracted as an operator would copy them. test/run.sh 28/28; repository shellcheck 0.10.0 and changelog-armed clean. Refs #202 |
|||
| fc24fa4b78 |
docs(upstream-sync): the inventory names docs-sync, which #201 makes forge-deciding (#200)
All checks were successful
CI / test (pull_request) Successful in 3m9s
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 8s
Found by combining all five open PRs and running the suite on the result — which is the check this PR's own runbook adds, catching a real break the first time it was applied at scale. !203 (#201) makes actions/docs-sync/docs-sync.sh decide the forge from GITHUB_SERVER_URL, because it was fetching the doctrine mirror from a hard-coded github.com. This PR's guard requires every forge-deciding file to be named in the inventory. Both are individually green; together the tree is red: forge-specific but not in docs/UPSTREAM-SYNC.md: actions/docs-sync/docs-sync.sh The entry belongs here rather than in !203: the inventory is this PR's artifact, and !203 is a bug fix that should not have to know about a guard absent from its base. Adding it early is harmless — the guard checks that deciding files ARE listed, not that listed files decide — and correct the moment both land. Five-way combined tree after this: 28 test files 0 failed under the runner's jq 1.6, shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed all clean. Refs #200 |
|||
| 20f4b287f7 |
docs(runner-probes): the generator survives a one-layer probe, callers carry full coordinates, one domain (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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 8s
@codex-reviewer-andresmgsl drove the published commands again and found three. 1. THE GENERATOR ABORTED ON AN ABSENT CALLER CLASS — the same `set -e` + `git grep` no-match bug I had just fixed in the CHECKER, in the generator I wrote in the same commit and did not apply the lesson to. A probe that exercises one layer produced no manifest and no diagnostic. `|| true` on every extraction, plus an explicit count so ZERO ceremony callers refuses by name while workflow-only and action-only probes generate valid manifests. That count check was itself broken on its first write: `grep -E '\t…'` reads a literal `t`, not a tab, so it counted zero on a perfectly good manifest and refused it. Found by running it. 2. CALLERS CARRY THE COMPLETE COORDINATE. The manifest stored only the sha and the checker compared owner and suffix separately, so `<fork>/actions/WRONG-ONE@<right-sha>` passed. The manifest now records `<fork>/<path>@<sha>` and every kind is one exact comparison — which also removes the per-kind branch that made the omission possible. 3. GENERATOR AND CHECKER SHARE ONE DOMAIN. `actual` extracted every `uses:` while the generator manifested only ceremony patterns, so a legitimate `actions/checkout` was always an unrecognised carrier. Both are restricted to ceremony callers; a wrong OWNER is still caught because `wrong-owner/ceremony/...` is still a ceremony caller. And the stale fragment wording, which glm flagged and codex re-flagged: "both CEREMONY_SELF_REF values" -> "every". DRIVEN, all of it: generator: both / workflow-only / action-only -> valid manifests generator: zero ceremony callers -> refuses by name deletion, role swap x2, wrong owner, wrong sha, wrong path, deleted caller class, extra carrier -> all refuse armed control, third-party actions/checkout present -> passes test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202 |
|||
| a55fbaef15 |
fix(forge): forge_commit_at — Forgejo serves a single commit at /git/commits/{sha} (#209)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
Found by the first post-merge sweep after the 0.6.0 merge — #198's own acceptance probe — not by review. Three PRs in one run: labels: #208: could not read the head commit's date: forge_api: HTTP 404 from 'GET repos/heavy-duty/ceremony/commits/f3a1336…' — blocker:unrequested not judged this pass Measured against this instance: forgejo repos/{o}/{r}/commits/{sha} -> 404 forgejo repos/{o}/{r}/git/commits/{sha} -> 200, date under `.created` github repos/{o}/{r}/commits/{sha} -> 200, date nested A fourth asymmetry, alongside the three lib/forge-forgejo.sh's header already records. #198 ported this call site onto the shim with GitHub's path unchanged — correct against GitHub, and the block it lives in (#236 D2) arrived WITH the merge, so nothing here had ever executed it. So it becomes a verb rather than a path at the call site: the caller wants one timestamp and should not have to know either shape. Cost while it stood was bounded and loud rather than silent — guarded_read refused and the sweep said so — but blocker:unrequested could never be judged on this forge. The tests pin each backend's PATH and FIELD, because a stubbed forge_api cannot catch a wrong path; that is exactly how this shipped and why it took a live sweep to find. Swapping the paths reds the forgejo pair; swapping the fields reds the github one. test/run.sh 28/28 under jq 1.7 and jq 1.6; forge-backends 124/124; shellcheck 0.10.0 and actionlint clean. Refs #209 |
|||
| 745944ec8c |
docs(runner-probes): the arming gate is a manifest comparison, driven against all six failure classes (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's four holes and @glm-reviewer-andresmgsl's prose
staleness. Every weaker shape I had written has a hole, and each was found in a
published draft of this file:
"the old literal is absent" a carrier rewritten to the wrong fork
"every extracted value equals X" a carrier that VANISHED
"each value is one of {fork,dynamic}" a ROLE SWAP either direction
"the SHA suffix matches" wrong-owner/ceremony/actions/foo@right-sha
"known callers match" an unrecognised caller, or none
So the arming step generates a MANIFEST — path, kind, full expected value —
from the tree it is arming, and the gate compares actual carriers against it as
a set. All six become one kind of failure: the sets differ. Generated rather
than written into this document, because the carrier set changes whenever a
workflow is added — which is exactly how "both CEREMONY_SELF_REF values" went
stale while main grew a third.
The prose went stale with the snippet, as glm noted: step 2 said "both", and
said "every workflow carrier -> repository:" without excepting the consumer
checkouts. Both corrected.
DRIVEN, not asserted. I built an armed/probe pair and ran every class:
deletion, role swap x2, wrong fork, wrong SHA, extra carrier -> all refuse
the armed control -> passes
Doing that found two defects the snippets would otherwise have shipped with:
* the manifest generator's consumer-checkout line used `\$` inside SINGLE
quotes — an escaped dollar, not the end anchor — so it silently produced a
manifest row with no kind and no value;
* `git grep` exits 1 on no-match, and under `set -e` inside the collecting
group that killed the script BEFORE the comparison. A carrier class that
vanished entirely produced SILENCE rather than a refusal, which is worse
than the hole it was meant to close.
test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean.
Refs #202
|
|||
| 5b78d29201 |
test(issueflow): isolate the scalar guard — the list row admits, the payload stands down (#210)
All checks were successful
CI / test (pull_request) Successful in 3m10s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl found the subtlety my board fixture could not reach: BOARD_RECORDS filters an object-valued row out of the LIST before the per-issue guard ever sees it, so no board fixture alone can prove the scalar stand-down. My #64 case proved the gather excluded it, not that reconcile_issue_pass did. The fixture that isolates the site is a deliberate mismatch: the LIST row is null-valued, so the board gather admits #65 — and the INDIVIDUAL payload the sweep then fetches is object-valued. Only reconcile_issue_pass's own guard can stand that down. Three rows over the same number, so the guard cannot pass by standing everything down or by admitting everything: payload object-valued -> NOT reconciled payload null-valued -> reconciled payload key absent -> reconciled (the GitHub shape) Mutating ONLY the scalar predicate now reds three BEHAVIOURAL rows plus the pin, where before it red only the pin and a neighbour. pass_disc is gone: it repeated the predicate inside the test helper and never called production — which is the same isolated-expression trap, one layer down, in the fix for it. issueflow 512/512; test/run.sh 28/28; shellcheck 0.10.0 clean. Refs #210 |
|||
| 087ea4a24b |
test(issueflow): each site observable through the real path, not through the expression it contains (#210)
All checks were successful
CI / test (pull_request) Successful in 3m9s
CI / release-exercise (pull_request) Successful in 10s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's three, and items 1 and 2 were still open after
|
|||
| bada4ffff5 |
test(issueflow): each of the three sites is caught by behaviour, not only by the pin (#210)
All checks were successful
CI / test (pull_request) Successful in 3m9s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's two scope items, applied before the first review round rather than after. 1. THE GUARD IS COMMENT-AWARE, WITH CONTROLS. It already stripped comments — it has to, because the #188 warning that explains why has("pull_request") is wrong contains the string. Without controls that was an untested property, and the pressure it creates is real: a raw grep would push a builder into deleting the very warning that prevents recurrence. Two fixtures now prove it: the explanatory comment is allowed, an executable jq filter is rejected. 2. ALL THREE SITES ARE DRIVEN BY BEHAVIOUR. The pin makes any revert red, but a pin proves a string is absent, not that each replacement means the intended thing: BOARD_RECORDS the forgejo-shaped board is not read as empty release_bodies an open `release` issue whose gate holds an open member makes a claimable NON-member draw a window flag — empty carriers, no flag, so the row discriminates the site instead of merely reaching it reconcile_issue_pass the scalar payload: key-present-null is an issue, object-valued is a PR, key-absent is still an issue The release_bodies row did NOT discriminate on its first write — it asserted an issue number that BOARD_RECORDS also produces, so reverting the site left it green. Caught by mutating each site separately rather than trusting the suite total. Mutation, per site: BOARD_RECORDS 3 red, release_bodies 2 red, reconcile_issue_pass 2 red. test/run.sh 28/28; issueflow 510/510; shellcheck 0.10.0 clean. Refs #210 |
|||
| 877e09e015 |
fix(issueflow): the board discriminator is .pull_request == null, not has() (#210)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
issueflow-reconcile has been blind on this forge since the 0.6.0 merge landed. Run 368 — #198's own post-merge acceptance probe — printed: issueflow: no open issues. issueflow: reconciled. over a board of nine. Every Forgejo entry CARRIES the `pull_request` key, valued null on an issue, so `select(has("pull_request") | not)` selects zero rows. Measured again today: #209 (an issue) has the key valued null; #208 and #207 (PRs) have it valued as objects. This is mine. #188 fixed exactly this and the file's own comment at :1113 states the rule, with :1121 already using it correctly. Resolving hunk 4 of the merge I took upstream's board block wholesale and carried the wrong discriminator into three sites — the gather, the release-body gather, and reconcile_issue_pass — in the PR whose stated purpose was to stop blind sweeps reporting success. Cost while it stood: no issue transitions, no claim reclaims, no nudges, no board flags — and no `post-merge` transitions, which is why #192 and #198 both still read `claimed` after their PRs merged, and why #198's own closure criterion could not complete. Two guards, because a comment did not hold: * A GATHER-LEVEL CASE against a Forgejo-shaped fixture — every entry carrying the key. The existing discriminator cases assert jq expressions in isolation and passed throughout this regression; they never ran the gather that uses them, which is precisely how it survived review. * A SOURCE PIN forbidding has("pull_request") on this surface, so a future sync cannot reintroduce it 40 lines below the comment forbidding it. Reverting the board gather reds both. Reverting reconcile_issue_pass reds the pin. test/run.sh 28/28 under jq 1.7 and jq 1.6; issueflow 503/503; shellcheck 0.10.0 and actionlint clean. Refs #210 |
|||
| dc87051c69 |
docs(runner-probes): enumerate the real carriers, spare the consumer checkouts, and make the snippet run (#202)
All checks were successful
CI / test (pull_request) Successful in 3m8s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's three, all verified against current main before
fixing.
1. THERE ARE THREE SELF-REF CARRIERS, NOT TWO — labels-sweep.yml:52,
labels.yml:51, release.yml:132. My `[ "$n" -eq 2 ]` came from the
pre-upstream tree, so it would have REJECTED a correctly armed candidate and
told the operator to rewrite two of three, leaving one workflow pinned to
the tag. The gate enumerates from the tree now, with the derivation commands
beside the table so the list is re-checked rather than trusted.
2. NOT EVERY `repository:` BELONGS TO THE FORK. Three are
`${{ github.repository }}` — labels-sweep.yml:69, labels.yml:92,
release-exercise.yml:72 — and they fetch the CALLER's repository. My loop
required every one to equal the fork, which would have rewritten the
consumer checkouts and quietly changed what the probe exercises. Internal
self-checkouts (four) are asserted to be the fork; consumer checkouts are
asserted to stay dynamic.
3. EACH CHECK IS BOUND TO THE TREE IT IS ABOUT — `git -C "$armed"` for the
carriers, `git -C "$probe"` for the callers, instead of depending on the
operator's current directory. And `mapfile` rather than `git grep | while …
fail`: the loop ran in a pipeline subshell, so `fail` exited the subshell
and the gate carried on. Collect first, validate after, under a declared
`set -euo pipefail`.
And the snippet is now executable rather than illustrative: placeholders became
positional parameters, so it parses, is shellcheck-clean, and runs. Driven
against the unarmed tree it refuses with `CEREMONY_SELF_REF=0.6.0` — a tag
rather than the candidate SHA, which is exactly the case it exists to catch.
Publishing a gate that could not run would have been the same defect one level
up.
Branch updated from merged main (
|
|||
| 8c3c37d412 | Merge remote-tracking branch 'origin/main' into build/202-runner-probe-venue | |||
| a48cc719a4 |
fix(upstream-delta): discovery is git's index, not the filesystem (#200)
All checks were successful
CI / test (pull_request) Successful in 3m9s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl reproduced it again, with one file: scanned_paths()
said "tracked" and used `find`, which walks the working directory and knows
nothing about the index.
Not pedantry — ci.yml extracts shellcheck.tar.xz, actionlint.tar.gz and their
binaries INTO the checkout before the suite runs, and any developer cache sits
there too. Today none happens to carry a matching marker; that is luck, not a
property, and a false red on a downloaded tarball would be indistinguishable
from a real finding.
`git ls-files -z` makes "tracked" executable rather than prose.
The fixtures become tiny git repositories, because a fixture that is only a
directory is invisible to ls-files and every must-fail below it would have
passed vacuously — the same trap as the earlier teeth that never invoked the
guard. Plus the negative case he asked for: an untracked marker-bearing cache
file is ignored, and the moment it is TRACKED the guard sees it.
Reverting discovery to find reds three.
Branch updated from merged main (
|
|||
| f8f318c634 | Merge remote-tracking branch 'origin/main' into build/200-upstream-sync-doc | |||
| e236318647 |
Merge pull request 'lib/forge-forgejo.sh + labels-reconcile — label removal is a full-set PUT, and a write that did not happen fails the sweep (#192)' (#206) from build/192-label-write into main
All checks were successful
CI / test (push) Successful in 3m7s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 7s
CI / action-exercise (push) Successful in 6s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 7s
Reviewed-on: #206 Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> |
|||
| 7e02344672 |
docs(runner-probes): the arming gate asserts what each carrier IS, not that a literal is gone (#202)
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 8s
@codex-reviewer-andresmgsl: absence of the canonical coordinate is not proof of correct arming. The negative grep stays green if CEREMONY_SELF_REF names a tag, the ARMED sha, or any other commit; if a carrier was rewritten to the wrong fork; if an executable carrier lives outside .github; or if a carrier simply disappeared rather than being rewritten. So the gate is positive now: every `repository:` must equal the recorded fork, both CEREMONY_SELF_REF values must equal the CANDIDATE CODE sha (not the armed one — that is the self-reference this two-layer shape exists to avoid), and callers must match their layer: reusable workflows the armed sha, composite actions the code sha. With a COUNT beside the comparison. `n -eq 2` is the part that catches a carrier which vanished, which a per-value loop cannot see — the same shape as counting the call sites a pin is guarding rather than only checking the ones that are there. The canonical-coordinate grep stays as a cheap extra rather than as the proof. Wording, same review: steps 1 and 2 advance the tip of ONE fork branch, so reset removes that branch, not "candidate and armed branches". test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean. Refs #202 |
|||
| f3a1336d42 |
fix(upstream-delta): discovery derives from the tree, not from a glob list (#200)
All checks were successful
CI / test (pull_request) Successful in 3m5s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl did not argue this one, he reproduced it: an `actions/*/action.yml` declaring CEREMONY_FORGE_CLIENT and a workflow written `.yaml` rather than `.yml`, both invisible to the hand-picked globs, guard still 21/21 green. The first is not an edge case — `actions/*/action.yml` is this repository's normal composite structure and a client declaration there IS a forge decision. The second shows `*.yml` was never a complete workflow surface. So discovery walks the tree and EXCLUDES by class rather than enumerating directories, depths and extensions. Excluding is the safer default: a new file type arrives scanned rather than invisible. Out of scope are .git/, test/ (whose harness asserts these tokens by design), changelog.d/ and *.md — prose, including drills/, which stays in the inventory because its records are forge-specific by CONTENT while a record mentioning a selector verb in prose is not a decision. Both of his reproductions are now fixtures driving the real no_unlisted, and restricting discovery back to *.sh reds four cases. The documentation claim is aligned with what the guard does rather than what the table implies: it checks forge DECISIONS in executable and configuration files; it is not a diff against upstream, so drills/ and labels.conf are listed by judgement rather than found by scan. Saying otherwise made labels.conf and drills/ look like evidence of completeness while action.yml was invisible. upstream-delta 24/24; test/run.sh 29/29; shellcheck 0.10.0, actionlint and changelog-armed clean. Refs #200 |
|||
| e27acd8ab9 |
docs(runner-probes): arming is two layers, because a commit cannot contain its own SHA (#202)
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 8s
@codex-reviewer-andresmgsl found that the procedure was not executable as
written, and the reason is structural rather than a wording slip.
The candidate's workflows carry `repository: heavy-duty/ceremony` beside
`ref: ${{ env.CEREMONY_SELF_REF }}`, so arming must rewrite them. But
rewriting CREATES A NEW COMMIT, and a commit cannot embed its own object ID. So
a single-layer arming is self-referential: pin the callers to the pre-rewrite
SHA and they load the UNARMED workflows; pin them to the post-rewrite SHA and
you are asking that commit to contain itself. My step 3 asked for exactly that.
Two layers, stated as a table because the distinction is the whole thing:
candidate code SHA the immutable tree under test — actions/, lib/
armed workflow SHA a child commit whose workflows point at the fork and
whose CEREMONY_SELF_REF is the candidate code SHA
And callers pin by layer, because they are not the same thing: composite
actions to the candidate code SHA, reusable workflows to the armed SHA, which
is the only revision whose inner checkout is rewritten.
The completeness check becomes a mechanical non-zero gate — `git grep` for
executable `uses:`/`repository:` carriers over the ARMED tree, exiting non-zero
on any hit — rather than "every remaining hit must be prose". A partial rewrite
does not announce itself: it silently tests canonical main.
The result issue records both SHAs, not one, or a later reader cannot tell
which tree answered.
test/run.sh 28/28; shellcheck 0.10.0 and changelog-armed clean.
Refs #202
|
|||
| 634e7a3528 |
fix(upstream-delta): the object is mandatory, the scan covers every governed surface, and the teeth drive the real guard (#200)
All checks were successful
CI / test (pull_request) Successful in 3m4s
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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's five points. Three were correctness, and one of
them found that my must-fail cases could not fail.
1. THE OBJECT IS MANDATORY. UNVERIFIABLE-HERE is gone: a missing ref, an absent
object and a non-ancestor are three distinct refusals. The ref is now the
FULL 40-character SHA, and ci.yml fetches exactly that object before the
suite. "Runs offline" means the TEST reads local evidence; it never meant CI
may omit the evidence and pass.
2. THE SCAN COVERS WHAT THE INVENTORY CLAIMS. It walked shell under four globs
and never looked at workflows, .github/labels.conf or drills/ — three
categories the inventory governs. Widened, and it immediately found four
real blind spots on merged main: refs-not-closing's declaration,
labels.yml's inline forge decision, refs-guard.yml's GitHub-only scheduling
and release-exercise.yml's pinned CEREMONY_FORGE. All four are now inventory
entries with the issue that removes them, because a delta location with no
exit is indistinguishable from one nobody noticed. A file that DECLARES a
client is no longer exempt as a "consumer" — only files that merely CALL the
shim are.
3. THE TEETH NOW DRIVE THE GUARD. They asserted the predicates separately and
never invoked no_unlisted, so the guard could have been `return 0` and both
must-fail rows would still have passed. SCAN_ROOT is a parameter now and the
cases build a tree, add an unlisted decider — shell AND workflow, so
coverage cannot regress to the old glob — and assert the real top-level
check fails naming it. Replacing no_unlisted with `return 0` reds five.
4. PATH MATCHING, NOT PREFIX MATCHING. `drills/` accepted `drills-old/x` and
`lib/forge.sh` accepted `lib/forge.sh.backup`. Exact for files, `dir/` for
directories, with both negative boundaries covered.
5. THE IMMUTABLE SHA IS CAPTURED AT FETCH. The runbook now takes
upstream_sha=$(git rev-parse gh/main) once and merges and records that
value. This is not hypothetical: while this PR was in review upstream moved
from
|
|||
| b80767e36c |
docs(runner-probes): rewrite coordinates not only refs, keep result issues, and stop asserting what was not measured (#202)
All checks were successful
CI / test (pull_request) Successful in 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) Has been skipped
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl's three operational corrections. 1. ARMING REWRITES THE COORDINATE. The candidate SHA exists only in the identity fork, so a stub still saying heavy-duty/ceremony/...@<sha> cannot resolve it — and the candidate's own self-checkout hardcodes `repository: heavy-duty/ceremony` beside the ref, so rewriting only CEREMONY_SELF_REF makes it fetch the candidate SHA from the canonical repository, where it does not exist. Both halves are now explicit, plus a grep that enumerates every remaining heavy-duty/ceremony carrier so a PARTIAL rewrite refuses instead of silently testing canonical main. 2. RESULT ISSUES ARE NOT RESET SCOPE. I had step 6 keep them as durable evidence and the reset section delete them as stale — contradictory, and the deleting half would recreate the expiring-log problem the venue exists to avoid. Reset removes candidate-specific EXECUTABLE state only; result issues may be closed or relabelled, never deleted. 3. NO UNMEASURED CLAIMS. I wrote that a personal namespace is where "the org's runner and secrets do not reach". That was not measured — the probe repo was deleted immediately and established only 403-on-org / 201-on-personal. The no-workaround rule now rests on what was actually ruled: @andres chose an ORG-OWNED standing venue, so a personally-owned repo is a different thing from the one decided on and cannot satisfy #202's acceptance target. If runner reach matters, it gets measured once the venue exists. test/run.sh 28/28; shellcheck 0.10.0, changelog-armed clean. Refs #202 |
|||
| f3f7538d15 |
docs(upstream-sync): stale in-flight branches, and auditing post-merge runs by executed steps (#200)
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 8s
@codex-reviewer-andresmgsl's two additions (#5697), both measured in the #198 sync rather than anticipated. Every branch open across a sync is stale afterwards: Forgejo never re-tests an open PR when main moves under it, so #206 and #207's green 22-file suites were about a tree that no longer existed once the 28-file one landed — and #206's fragment was individually green while making the combined tree red under a rule the sync itself introduces. The runbook now says to update each in-flight branch from the newly synced main, or check them in a scratch merge, and that a prior approval is evidence about the tree it was given on. And post-merge runs are audited by executed steps, never by colour: inventory what the sync changed about triggers and jobs, read which job actually ran, and treat a green refusal path as evidence for that path only. Run 326 was green and had reconciled nothing. Both failures happened with the no-runtime-gh guard green and CI green, so the runbook says that too. Refs #200 |
|||
| e61bb91476 |
docs(runner-probes): its own document, an arming procedure, and the evidence boundary made consistent (#202)
All checks were successful
CI / test (pull_request) Successful in 3m2s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 8s
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 8s
@codex-reviewer-andresmgsl's four gaps.
1. BRANCH UPDATED TO CURRENT MAIN. The commit's parent was pre-#204
|
|||
| 2037275a9c | Merge remote-tracking branch 'origin/main' into build/202-runner-probe-venue | |||
| e965b15cbf |
docs: the recurring upstream sync, its standing resolutions, and a guard on where the delta lives (#200)
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 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) Has been skipped
labels / labels (pull_request) Successful in 8s
The third child of #197, written immediately after performing the sync it describes, while the findings are still first-hand. docs/UPSTREAM-SYNC.md carries the procedure and the six standing resolutions, each with the issue that decided it, so they are not re-argued every sync. The parts that are not obvious from the outside, and that the 0.6.0 sync paid to learn: * THE AUDIT STEP. `git merge` takes upstream's side wherever only upstream moved a region, so a function upstream ADDED to a file this tree owns arrives with no conflict and no question. Reviewing the hunks cannot find it — four reviewers read the same diff and each found a different subset. That was eight runtime `gh` call sites in three files and two file types. * THE SAME MECHANIC APPLIES TO STATE. A resolved region can remove a producer whose consumers auto-merged, and those consumers degrade to empty rather than erroring, so nothing goes red. Three such seams in one sync. * VERIFY WHERE IT RUNS. "Green locally" was wrong three times, for three different reasons: shellcheck-all lints TRACKED files so a new file's first lint is meaningless; CI pins shellcheck 0.10.0; and the runner's jq 1.6 exits 0 where 1.7 exits 4 on `jq -e` with empty input — which was not a test problem but a guard accepting an unreadable read. * TEST THE MERGE RESULT. Forgejo tests heads, never what two branches produce together, and two green PRs did produce a red tree in this sync. * AFTER MERGING, CHECK THE SWEEP RECONCILED SOMETHING. The first post-merge run was green and had done nothing. .upstream-ref records the carried commit in machine-readable form beside the CHANGELOG's prose. test/upstream-delta.test.sh asserts every forge-DECIDING file is named in the inventory — offline, comment-aware, and refusing rather than skipping when the ref is missing. Shim CONSUMERS are allowed by name, so a seventh consumer is silent and a seventh decider is not. docs/CONSUMERS.md now states that two ceremonies answer to the same version number and how a consumer says which one it pinned. Must-fail, both from the issue's test plan: scattering a forge_detect branch into an unlisted file reds the guard; blanking .upstream-ref reds it too. test/run.sh 29 files 0 failed under jq 1.7 and jq 1.6; shellcheck 0.10.0, actionlint, self-ref, marker, vendored and changelog-armed all clean. Refs #200 |
|||
| 08714530b3 |
docs(drills): the standing runner-probe venue, and why it is not a drill (#202)
All checks were successful
CI / test (pull_request) Successful in 1m30s
CI / release-exercise (pull_request) Successful in 10s
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
labels / labels (pull_request) Successful in 8s
@andres ruled option A (#5631): one standing never-archived repo. This is the runbook half. The distinction the document exists to make: a drill is disposable by design and ends with the builder archiving it. This venue is the opposite — it exists so that runner-only facts can be measured on demand, and archiving it defeats the purpose. That is not hypothetical: all three drill repos were archived correctly, by the rule, and each then had to be un-archived or replaced. The request came three times in two days across #192 and #198 and never became anything. What the runbook pins, all of it measured rather than asserted: * a probe MUST run as an Actions job under ${{ github.token }} — the same DELETE answers 500 there and 204 under a PAT, so a probe run any other way produces a confident wrong answer; * probe results are written into the forge, not left in a job log, because logs age out and #192's run 701 survived only because it wrote into an issue; * no probe touches ceremony's own board — the venue exists so the live board is not the fixture; * the three probes it already owes (#192's live label lift, #205's dispatch measurement, a 0.6.0 consumer exercise after #198). STANDING THE REPO UP IS THE OPERATOR'S STEP, and this is the part I could not do rather than the part I chose not to. Measured today with this identity: POST /api/v1/orgs/heavy-duty/repos -> 403 not allowed in organization POST /api/v1/user/repos -> 201 personal namespace only Same shape as the drill delete: a deliberate boundary, not a misconfiguration. The runbook says so, says not to retry it, and says not to work around it by using a personal namespace where the org's runner and secrets do not reach. test/run.sh 22/22, shellcheck 0.10.0, actionlint, self-ref all clean. Refs #202 |
|||
| a35a77f752 |
fix(forgejo): a read failure names its verb too, and the tests assert the whole diagnostic (#192)
All checks were successful
CI / test (pull_request) Successful in 1m35s
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
labels / labels (pull_request) Successful in 8s
@codex-reviewer-andresmgsl caught a test that describes evidence it does not collect — mine, and it is the class this PR is about. Two cases were titled "naming the verb, path and status" and asserted only the substring "500". The PUT boundary happened to satisfy the contract because forgejo_write already passes "PUT $endpoint" to forgejo_http_ok. The GET boundary did not: the diagnostic was `HTTP 500 from 'repos/o/r/issues/5'`, with no verb at all — so a caller could not tell a failed READ from a failed WRITE of the same path, and #192's acceptance criterion asks for exactly that distinction. Reads now pass "GET $endpoint" on both non-paginated and paginated paths, and the two tests assert the complete expected diagnostic as one substring rather than a status code that any failure would contain. Reverting the verb reds the GET case. Also, per the same review: the failed GET is asserted to write nothing, and the failed PUT to have attempted exactly one write. forge-backends 117/117 (was 115), test/run.sh 22/22 under jq 1.7 and jq 1.6, shellcheck 0.10.0 and actionlint clean. Refs #192 |
|||
| 790c4d226f |
Merge pull request 'actions/* + lib/* + CHANGELOG — merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198)' (#204) from build/198-upstream-0.6.0 into main
All checks were successful
CI / test (push) Successful in 3m2s
CI / release-exercise (push) Has been skipped
CI / self-guards (push) Successful in 7s
CI / action-exercise (push) Successful in 6s
CI / docs-sync-exercise (push) Successful in 6s
release / release (push) Successful in 7s
Reviewed-on: #204 Reviewed-by: kimi-reviewer-andresmgsl <andres+4@heavyduty.builders> Reviewed-by: codex-reviewer-andresmgsl <andres+2@heavyduty.builders> |
|||
| 062e016a42 |
fix(labels): all four review gaps — preserved ids, zero-write no-op, every mutation counted, no success token (#192)
All checks were successful
CI / test (pull_request) Successful in 1m35s
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
labels / labels (pull_request) Successful in 56s
@codex-reviewer-andresmgsl's four gaps, all real, all taken. 1. PRESERVED IDS COME FROM THE ISSUE. The removal path read only .labels[].name and then re-resolved every preserved label through the repository-wide list — so preservation depended on a paginated read with nothing to do with this issue, and an incomplete one would drop a bystander. It now keeps name<TAB>id from the issue payload, subtracts removals by name, and resolves ONLY added names. Fixture: a bystander on the issue with id 14 that is absent from the repo-list fixture entirely must still survive the PUT. 2. AN ABSENT REMOVAL WRITES NOTHING. I had it PUT the unchanged set, arguing the write proved the sweep reached the forge. The GET already proves that, and replacing a set with itself opens ceremony#128's window for no state change — most calls here are exactly this case, since the reconcilers call --remove-label unconditionally. Short-circuits when the wanted set equals the current one. This was the policy-shaped choice flagged for @andres; the reviewer's reasoning is better than mine was. 3. EVERY LABEL MUTATION REACHES THE TALLY. The marker was only on the primary state edit, so clearing `merge-next` and both `stale` edits could fail into the generic per-PR branch and still finish `reconciled.` and exit 0. All four sites go through one `label_write` helper, so a future call site cannot reopen it by forgetting to mark itself. Probe: a failed NON-primary write (unstale on a blocked PR) must fail the sweep. 4. NO SUCCESS TOKEN IN A FAILURE TAIL. "NOT reconciled." still contains "reconciled.", which a log-tail consumer greps for. The line is now "sweep incomplete", and the test asserts the whole output is free of the token rather than only of the success prefix. Also added the two fault boundaries the acceptance plan named and the fixtures never proved: a failed current-label GET and a failed replacement PUT, each non-zero with the backend's verb/path/status diagnostic. Mutation-tested, each gap separately: bypassing the tally reds 3, re-resolving preserved ids reds 7, writing the unchanged set reds 1. forge-backends 115/115 (was 110), labels-reconcile 175/175 (was 172), test/run.sh 22/22 under jq 1.7 and jq 1.6, shellcheck 0.10.0 and actionlint clean. Refs #192 |
|||
| 018489ac4d |
chore(changelog): the 192 fragment's citation is terminal (#192)
All checks were successful
CI / test (pull_request) Successful in 1m34s
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
labels / labels (pull_request) Successful in 56s
Found by merging this branch onto !204 and running the suite there — not by anything visible on this base. The terminal-citation rule (#262) ARRIVES with the 0.6.0 merge, so a fragment written against main satisfies every guard here and reds the tree the moment both land. '(ceremony#128) (#192)' is two groups; exactly one must end the entry. The reference moves into prose. Refs #192 |
|||
| 0f20f4b6ef |
fix(labels): a label removal that cannot happen fails the sweep, and removal itself now works (#192)
All checks were successful
CI / test (pull_request) Successful in 1m35s
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
labels / labels (pull_request) Successful in 54s
Two defects, one cause, and the second is why the first survived a week.
THE WRITE. Removal was a per-label `DELETE .../labels/{id}` loop. On this
instance that call returns HTTP 500 for every removal under the token the
sweep actually holds — measured inside Actions, probe run 701, where the same
`PUT .../labels` with the desired full set returns 200 including the empty set
for a full clear. A PAT gets 204 on the same DELETE, which is exactly why it
went unseen: it fails only for `${{ github.token }}`.
Net effect before this: on Forgejo the state machine could only ever ADD
labels. Every `state:*` transition needing the previous state cleared and every
`blocker:*` that should lift was inert. Both PRs open right now carry stale
`blocker:*` labels that are false and that nothing can remove.
So the removal path is read-current, compute-wanted, one PUT — the same shape
the assignee branch beside it already used. An ADD-ONLY call keeps its additive
POST: ceremony#128 lost a `release` label to a read-modify-write that clobbered
a concurrent set, and forge_labels_add stays pinned against ever doing that.
The window is accepted here and only here, where the caller asked to REMOVE
and no additive verb can say that. An unresolvable --add-label refuses before
any write, so a replacement PUT can never drop a label nobody asked to remove.
THE REPORTING. `labels-reconcile` logged `WARNING: label edit failed`, fell
through, and `main` printed `reconciled.` and exited 0 — while
`issueflow-reconcile` treated the identical 500 as fatal. One cause, two
contradictory policies, and the wrong one hid the write defect.
A failed write is fatal now, and the tally reaches main's exit code. That
second half is load-bearing: making reconcile_pr fatal alone is not enough,
because the loop swallows a per-PR non-zero into a log line and finishes. The
per-PR tolerance is right and stays — one bad PR must not blind the board — but
it now applies to READS. A sweep that could not write exits non-zero and never
prints `reconciled.`
The diagnostic says what was attempted and that it did not happen. The old text
blamed a missing label and told the operator to bootstrap, when the label was
present and the call returned 500 — #101's rule is report, do not diagnose.
Mutation-tested, all three ways: restoring the warn-and-continue reds 5 cases,
removing the tally reds 2, restoring the DELETE loop reds 7.
test/run.sh 22 files 0 failed under jq 1.7 and jq 1.6; shellcheck 0.10.0 and
actionlint clean.
Refs #192
|
|||
| 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`. |
|||
| 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
|
|||
| 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
|
|||
| 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 |