docs(agents): record what main's protection depends on in ci.yml #21

Merged
jlxq0 merged 1 commit from chore/record-trigger-dependency into main 2026-08-27 02:56:35 +00:00
Owner

Closes the hole in yesterday's acceptance and records what main's protection actually
depends on, which is in .forgejo/workflows/ci.yml and not in the protection settings.

The acceptance recorded yesterday proved less than it claimed

It recorded a refused push to main and nothing beside it. A refusal in isolation is
equally well explained by a dead token, a wrong remote, a mangled refspec or a
non-fast-forward
refused by git before the hook is ever reached. Re-run properly, in the
armed state, one commit 8e9246a7f6a0ff7aa6784f64f76376688456a2f5 to two destinations:

git push origin "${probe}:refs/heads/probe/armed-control"   exit 0   * [new branch]
git push origin "${probe}:refs/heads/main"                  exit 1   ! [remote rejected]

The remote's own lines, which is what settles it rather than the exit codes:

remote: Create a new pull request for 'probe/armed-control':
 * [new branch]      8e9246a7... -> probe/armed-control

remote: Forgejo: Not allowed to push to protected branch main
 ! [remote rejected] 8e9246a7... -> main (pre-receive hook declined)

Same commit, same token, same remote, same invocation, differing in the destination ref
alone. git merge-base --is-ancestor origin/main HEAD asserted true first, so neither push
could be refused as a non-fast-forward before reaching the hook. Refspecs braced, because
unbraced zsh reads "$probe:refs/..." as the :r modifier and pushes something else.
Probe branch deleted, HTTP 204.

Two things in ci.yml the rule silently depends on

Neither is visible from GET /branch_protections, and sampled statuses cannot establish
either: statuses say the contexts have been produced, the workflow says they must be.

cargo carries no needs: and no job-level if:. It always runs, so its status is
always the real result. That is precisely what makes it safe to require and docker,
which carries needs: cargo, unsafe. Checkable from the file rather than from history.

pull_request: is bare. No paths:, paths-ignore:, branches: or types::

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: "17 3 * * *"

Adding a filter there would make a filtered-out PR produce no required context at all, so
the gate becomes permanently unsatisfiable with nothing in the protection settings having
changed
: a merge blocked forever by a line in a workflow file nobody connects to it. The
branches: [main] filter present in that block sits under push:, where it cannot affect
a PR head.

One coverage note, not a gate problem

cargo has two step-level if: github.event_name != 'pull_request' conditions, so the
OfficeMaster checkout and scripts/sync-templates.sh --check do not run on a PR. The job
still reports its real result, so the gate stays satisfiable. But CI / cargo (pull_request) is a weaker check than CI / cargo (push), and template drift against
canonical OfficeMaster is not covered by the required status
. That is deliberate in the
workflow, since a PR gets no read token for the upstream repository. Recorded because the
required status does not cover everything its name suggests.

jlxq0/mantis#141.

Closes the hole in yesterday's acceptance and records what `main`'s protection actually depends on, which is in `.forgejo/workflows/ci.yml` and not in the protection settings. ## The acceptance recorded yesterday proved less than it claimed It recorded a refused push to `main` and nothing beside it. **A refusal in isolation is equally well explained by a dead token, a wrong remote, a mangled refspec or a non-fast-forward** refused by git before the hook is ever reached. Re-run properly, in the armed state, one commit `8e9246a7f6a0ff7aa6784f64f76376688456a2f5` to two destinations: ``` git push origin "${probe}:refs/heads/probe/armed-control" exit 0 * [new branch] git push origin "${probe}:refs/heads/main" exit 1 ! [remote rejected] ``` The remote's own lines, which is what settles it rather than the exit codes: ``` remote: Create a new pull request for 'probe/armed-control': * [new branch] 8e9246a7... -> probe/armed-control remote: Forgejo: Not allowed to push to protected branch main ! [remote rejected] 8e9246a7... -> main (pre-receive hook declined) ``` Same commit, same token, same remote, same invocation, differing in the destination ref alone. `git merge-base --is-ancestor origin/main HEAD` asserted true first, so neither push could be refused as a non-fast-forward before reaching the hook. Refspecs braced, because unbraced zsh reads `"$probe:refs/..."` as the `:r` modifier and pushes something else. Probe branch deleted, `HTTP 204`. ## Two things in ci.yml the rule silently depends on Neither is visible from `GET /branch_protections`, and sampled statuses cannot establish either: statuses say the contexts *have been* produced, the workflow says they *must be*. **`cargo` carries no `needs:` and no job-level `if:`.** It always runs, so its status is always the real result. That is precisely what makes it safe to require and `docker`, which carries `needs: cargo`, unsafe. Checkable from the file rather than from history. **`pull_request:` is bare.** No `paths:`, `paths-ignore:`, `branches:` or `types:`: ```yaml on: push: branches: [main] tags: ["v*"] pull_request: workflow_dispatch: schedule: - cron: "17 3 * * *" ``` Adding a filter there would make a filtered-out PR produce no required context at all, so the gate becomes permanently unsatisfiable **with nothing in the protection settings having changed**: a merge blocked forever by a line in a workflow file nobody connects to it. The `branches: [main]` filter present in that block sits under `push:`, where it cannot affect a PR head. ## One coverage note, not a gate problem `cargo` has two step-level `if: github.event_name != 'pull_request'` conditions, so the OfficeMaster checkout and `scripts/sync-templates.sh --check` do not run on a PR. The job still reports its real result, so the gate stays satisfiable. But `CI / cargo (pull_request)` is a weaker check than `CI / cargo (push)`, and **template drift against canonical OfficeMaster is not covered by the required status**. That is deliberate in the workflow, since a PR gets no read token for the upstream repository. Recorded because the required status does not cover everything its name suggests. `jlxq0/mantis#141`.
docs(agents): record what main's protection depends on in ci.yml
All checks were successful
CI / cargo (pull_request) Successful in 1m58s
CI / docker (pull_request) Successful in 21s
5964669716
The rule requires `CI / cargo*` and depends on two properties of the workflow
file, neither visible from the protection settings.

`cargo` carries no `needs:` and no job-level `if:`, so it always runs and its
status is always the real result. That is what makes it safe to require and
`docker`, which carries `needs: cargo`, unsafe.

`pull_request:` is bare: no paths, paths-ignore, branches or types filter, so
every PR produces the required context. Adding one would make a filtered-out PR
produce no required context at all and the gate permanently unsatisfiable, with
nothing in the protection settings having changed. The `branches: [main]` filter
in that block is under `push:` and cannot affect a PR head.

Sampled statuses cannot establish either property. They say the contexts have
been produced; the on: block and the absence of needs: say they must be.

Acceptance is recorded as one commit pushed to two destinations in the armed
state, control accepted and treatment refused, after asserting the push was a
fast-forward. A refusal without an accepted push beside it is equally well
explained by a dead token, a wrong remote or a network fault, which is the hole
in what was recorded yesterday.

Also notes that two step-level `if: github.event_name != 'pull_request'`
conditions leave template drift outside the required context on a PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CXx4uyuDhqwungt4SMN4S
jlxq0 merged commit f53a1bdfd4 into main 2026-08-27 02:56:35 +00:00
jlxq0 deleted branch chore/record-trigger-dependency 2026-08-27 02:56:36 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/typst-mcp!21
No description provided.