ci-queue-wait.sh --purpose merge exits 3 (ASSERTED_NOT_READY state=no-status) on any repository whose commits carry zero status contexts, and pr-merge.sh calls the guard with --purpose merge before merging. A repository with no CI configured therefore has no wrapper merge path. Push already treats no-status as queue-clear (upstream d339e8fd, #1129); merge deliberately stayed fail-closed because no-status at merge time can also mean "CI has not reported yet". There was no sanctioned way to merge a repository that genuinely has no CI.
Change
ci-queue-wait.sh gains --no-ci-expected: for --purpose merge on a head with zero status contexts, the guard returns queue-clear instead of exit 3 — but only under an explicit, admin-gated, audit-visible assertion.
Admin gate: the elevation check reads the repository object's permissions.admin (Gitea GET /repos/{owner}/{repo}; GitHub gh api repos/{o}/{r}). The branch and combined-status objects the guard already fetches carry no permissions field (measured on the live forge), so the flag adds exactly one network call, and only inside the flag's merge/no-status branch — never on the default path.
Non-admin (or field absent): ASSERTION_REFUSED, distinct error text, exit 77 (not 3), with an ASSERTION_REFUSED JSONL audit record.
Unattributable caller (MOSAIC_GIT_IDENTITY unset or empty) is refused before queue-clear and before the permission lookup: ASSERTION_UNATTRIBUTABLE, exit 78, with its own audit record. asserted_by can never print unknown on the pass path.
Pass prints a distinguishable audit line ([ci-queue-wait] queue-clear state=no-status purpose=merge asserted-by=<identity> reason=no-ci-expected branch=<branch>) and writes a NO_CI_ASSERTED JSONL record. An unauditable pass is refused (exit 70).
--no-ci-expected combined with --require-status is a contradiction: usage error, exit 1, before any network I/O.
No effect when any status context exists: a pending or failed context still holds or fails exactly as today; the flag only reclassifies the zero-context case; push behavior unchanged.
pr-merge.sh gains only a pass-through of the flag to the guard invocation. No other behavior change; the break-glass override is untouched.
Usage text documents the flag in one sentence per script; TOOLS-REFERENCE.md gains one clause in the guard paragraph.
PowerShell twins deliberately unchanged: no existing test exercises their guard path, so a flag there would ship untested by the current harness.
Tests
New test-ci-queue-wait-no-ci-expected.sh, 9 cases: admin pass with audit line + JSONL record; no-flag merge on no-status still exit 3 unchanged; non-admin refusal exit 77; missing-permissions-field refusal exit 77; flag + --require-status usage error exit 1; one pending context still holds; push + no-status unchanged; admin-lookup-unreachable degrades to CANNOT_ASSERT exit 75; unattributable identity refused exit 78 with zero permission lookups (identity check precedes the elevation lookup).
New test-pr-merge-no-ci-expected.sh: the flag reaches the guard invocation, and its absence does not.
Both follow the existing stub-provider harness conventions and are enumerated in test:framework-shell.
The full existing test-ci-queue-wait-* / test-pr-merge-* set re-run: per-file rc 0 across all 11 suites (test-pr-merge-gitea-empty-uid.sh requires MOSAIC_GIT_IDENTITY unset in environments that export it — pre-existing, identical on pristine origin/next).
## Problem
`ci-queue-wait.sh --purpose merge` exits 3 (`ASSERTED_NOT_READY state=no-status`) on any repository whose commits carry zero status contexts, and `pr-merge.sh` calls the guard with `--purpose merge` before merging. A repository with no CI configured therefore has no wrapper merge path. Push already treats `no-status` as queue-clear (upstream d339e8fd, #1129); merge deliberately stayed fail-closed because `no-status` at merge time can also mean "CI has not reported yet". There was no sanctioned way to merge a repository that genuinely has no CI.
## Change
`ci-queue-wait.sh` gains `--no-ci-expected`: for `--purpose merge` on a head with zero status contexts, the guard returns queue-clear instead of exit 3 — but only under an explicit, admin-gated, audit-visible assertion.
- Admin gate: the elevation check reads the repository object's `permissions.admin` (Gitea `GET /repos/{owner}/{repo}`; GitHub `gh api repos/{o}/{r}`). The branch and combined-status objects the guard already fetches carry no permissions field (measured on the live forge), so the flag adds exactly one network call, and only inside the flag's merge/no-status branch — never on the default path.
- Non-admin (or field absent): `ASSERTION_REFUSED`, distinct error text, exit 77 (not 3), with an `ASSERTION_REFUSED` JSONL audit record.
- Unattributable caller (`MOSAIC_GIT_IDENTITY` unset or empty) is refused before queue-clear and before the permission lookup: `ASSERTION_UNATTRIBUTABLE`, exit 78, with its own audit record. `asserted_by` can never print `unknown` on the pass path.
- Pass prints a distinguishable audit line (`[ci-queue-wait] queue-clear state=no-status purpose=merge asserted-by=<identity> reason=no-ci-expected branch=<branch>`) and writes a `NO_CI_ASSERTED` JSONL record. An unauditable pass is refused (exit 70).
- `--no-ci-expected` combined with `--require-status` is a contradiction: usage error, exit 1, before any network I/O.
- No effect when any status context exists: a pending or failed context still holds or fails exactly as today; the flag only reclassifies the zero-context case; push behavior unchanged.
- `pr-merge.sh` gains only a pass-through of the flag to the guard invocation. No other behavior change; the break-glass override is untouched.
- Usage text documents the flag in one sentence per script; `TOOLS-REFERENCE.md` gains one clause in the guard paragraph.
- PowerShell twins deliberately unchanged: no existing test exercises their guard path, so a flag there would ship untested by the current harness.
## Tests
- New `test-ci-queue-wait-no-ci-expected.sh`, 9 cases: admin pass with audit line + JSONL record; no-flag merge on no-status still exit 3 unchanged; non-admin refusal exit 77; missing-permissions-field refusal exit 77; flag + `--require-status` usage error exit 1; one pending context still holds; push + no-status unchanged; admin-lookup-unreachable degrades to `CANNOT_ASSERT` exit 75; unattributable identity refused exit 78 with zero permission lookups (identity check precedes the elevation lookup).
- New `test-pr-merge-no-ci-expected.sh`: the flag reaches the guard invocation, and its absence does not.
- Both follow the existing stub-provider harness conventions and are enumerated in `test:framework-shell`.
- The full existing `test-ci-queue-wait-*` / `test-pr-merge-*` set re-run: per-file rc 0 across all 11 suites (`test-pr-merge-gitea-empty-uid.sh` requires `MOSAIC_GIT_IDENTITY` unset in environments that export it — pre-existing, identical on pristine `origin/next`).
- `bash -n` clean on all edited shell scripts.
Closes #1372
A repository with no CI configured has no sanctioned wrapper merge
path: the queue guard fails closed on zero status contexts for
purpose=merge (exit 3). That fail-closed default stays correct, because
at merge time no-status may also mean CI has not reported yet; but it
left CI-less repos unmergeable without the break-glass override, which
makes the override a gap rather than an exception.
--no-ci-expected reclassifies only the zero-context case for merge as
queue-clear, and only when the acting token holds repository admin.
The elevation check reads the repository object's permissions.admin:
the branch-head and combined-status responses the guard already
fetches carry no permissions object at all. The assertion prints its
own audit line (purpose, branch, asserting identity, reason) and
writes a NO_CI_ASSERTED JSONL record to the existing audit sink; an
unauditable pass is refused (exit 70). A non-admin caller is refused
with exit 77 (ASSERTION_REFUSED, own text, audited), kept distinct
from ASSERTED_NOT_READY's exit 3; an unavailable permissions lookup
holds as CANNOT_ASSERT exit 75. --require-status contradicts the flag
and is a usage error; a pending or failed context still holds or
fails exactly as before; push behavior is unchanged.
pr-merge.sh gains a pass-through --no-ci-expected that only forwards
the flag to the guard invocation. PowerShell twins are unchanged: no
existing test exercises their guard path (pr-merge.ps1 only runs with
-SkipQueueGuard; ci-queue-wait.ps1 has no test).
Closes#1372
The --no-ci-expected admin-success path accepted an asserted-by=unknown
pass when MOSAIC_GIT_IDENTITY was unset or empty, so the queue-clear
assertion and its NO_CI_ASSERTED audit record named no one. Refuse such
callers with a distinct ASSERTION_UNATTRIBUTABLE outcome (exit 78, audit
recorded) before queue-clear and before the permission lookup, so an
unattributable caller never triggers that network call. Document exit 78
in the usage text and the tools reference; pin the path with a
regression case (admin stub + env -u MOSAIC_GIT_IDENTITY).
rev-code-02
approved these changes 2026-08-23 18:52:39 +00:00
Independent review PASS at head 187d70be (fleet review T29R2). Re-ran test-ci-queue-wait-no-ci-expected.sh and test-pr-merge-no-ci-expected.sh plus the existing guard/merge suites; F1 (ASSERTION_UNATTRIBUTABLE exit 78, identity before permission lookup) closed.
Independent review PASS at head 187d70be (fleet review T29R2). Re-ran test-ci-queue-wait-no-ci-expected.sh and test-pr-merge-no-ci-expected.sh plus the existing guard/merge suites; F1 (ASSERTION_UNATTRIBUTABLE exit 78, identity before permission lookup) closed.
orch-01
merged commit 143f925fd8 into next2026-08-23 18:54:50 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
ci-queue-wait.sh --purpose mergeexits 3 (ASSERTED_NOT_READY state=no-status) on any repository whose commits carry zero status contexts, andpr-merge.shcalls the guard with--purpose mergebefore merging. A repository with no CI configured therefore has no wrapper merge path. Push already treatsno-statusas queue-clear (upstreamd339e8fd, #1129); merge deliberately stayed fail-closed becauseno-statusat merge time can also mean "CI has not reported yet". There was no sanctioned way to merge a repository that genuinely has no CI.Change
ci-queue-wait.shgains--no-ci-expected: for--purpose mergeon a head with zero status contexts, the guard returns queue-clear instead of exit 3 — but only under an explicit, admin-gated, audit-visible assertion.permissions.admin(GiteaGET /repos/{owner}/{repo}; GitHubgh api repos/{o}/{r}). The branch and combined-status objects the guard already fetches carry no permissions field (measured on the live forge), so the flag adds exactly one network call, and only inside the flag's merge/no-status branch — never on the default path.ASSERTION_REFUSED, distinct error text, exit 77 (not 3), with anASSERTION_REFUSEDJSONL audit record.MOSAIC_GIT_IDENTITYunset or empty) is refused before queue-clear and before the permission lookup:ASSERTION_UNATTRIBUTABLE, exit 78, with its own audit record.asserted_bycan never printunknownon the pass path.[ci-queue-wait] queue-clear state=no-status purpose=merge asserted-by=<identity> reason=no-ci-expected branch=<branch>) and writes aNO_CI_ASSERTEDJSONL record. An unauditable pass is refused (exit 70).--no-ci-expectedcombined with--require-statusis a contradiction: usage error, exit 1, before any network I/O.pr-merge.shgains only a pass-through of the flag to the guard invocation. No other behavior change; the break-glass override is untouched.TOOLS-REFERENCE.mdgains one clause in the guard paragraph.Tests
test-ci-queue-wait-no-ci-expected.sh, 9 cases: admin pass with audit line + JSONL record; no-flag merge on no-status still exit 3 unchanged; non-admin refusal exit 77; missing-permissions-field refusal exit 77; flag +--require-statususage error exit 1; one pending context still holds; push + no-status unchanged; admin-lookup-unreachable degrades toCANNOT_ASSERTexit 75; unattributable identity refused exit 78 with zero permission lookups (identity check precedes the elevation lookup).test-pr-merge-no-ci-expected.sh: the flag reaches the guard invocation, and its absence does not.test:framework-shell.test-ci-queue-wait-*/test-pr-merge-*set re-run: per-file rc 0 across all 11 suites (test-pr-merge-gitea-empty-uid.shrequiresMOSAIC_GIT_IDENTITYunset in environments that export it — pre-existing, identical on pristineorigin/next).bash -nclean on all edited shell scripts.Closes #1372
Independent review PASS at head
187d70be(fleet review T29R2). Re-ran test-ci-queue-wait-no-ci-expected.sh and test-pr-merge-no-ci-expected.sh plus the existing guard/merge suites; F1 (ASSERTION_UNATTRIBUTABLE exit 78, identity before permission lookup) closed.