feat(mirrors): force-push every mirror from one verb, then verify #968

Merged
coilysiren merged 1 commit from aos/claude/wz68-mirror-push into main 2026-08-27 10:33:47 +00:00
Owner

Forgejo's web UI syncs one repository at a time, so forcing the whole set meant three visits or three API calls by hand.

just mirror-sync-push                 # every configured mirror
just mirror-sync-push --only umbra    # one of them
just mirror-sync-push --no-verify     # push and stop

It verifies rather than trusting the push

A 200 means Forgejo accepted the request, not that the far side kept the refs. That distinction is the entire argument of coilysiren/inbox#441, so the push would be dishonest without it:

$ just mirror-sync-push
push ok umbra
push ok mcp-beaver
push ok agent-compose

ok   umbra                    match       8e46ec6c5676
ok   mcp-beaver               match       eebbe24e40c0
ok   agent-compose            match       3e8976f3732e

3/3 mirrors in sync

It reuses check_mirror_sync.evaluate, so the verification is the same code the scheduled watch runs, not a second opinion that could drift from it.

Why a separate script rather than a flag

check_mirror_sync.py needs no credential: both forges are public and it only reads. Pushing needs the Forgejo admin token. Adding a --push flag would have dragged that requirement into the module the scheduled workflow imports, and the credential-free property of that job is worth protecting deliberately rather than by accident.

Both read the same scripts/mirror-targets.json, so the pusher and the watcher can never cover different sets.

Behaviour worth knowing

  • An unknown --only name is refused outright rather than silently pushing nothing.
  • A source URL that does not split cleanly into owner and repo raises rather than POSTing a sync at a guessed repository.
  • Forgejo answers 500 with a useful body when the remote rejects the push, so that body is surfaced instead of the bare status.
  • Exit is 1 if any push is rejected or if any mirror still diverges afterwards.

Tests

8 new, covering URL parsing in both directions and each upstream answer shape. Nothing reaches the network or the token store. Existing 12 still green.

Exercised live before landing: --only umbra, all three, and the unknown-name refusal.

Forgejo's web UI syncs one repository at a time, so forcing the whole set meant three visits or three API calls by hand. ``` just mirror-sync-push # every configured mirror just mirror-sync-push --only umbra # one of them just mirror-sync-push --no-verify # push and stop ``` ## It verifies rather than trusting the push A `200` means Forgejo accepted the request, not that the far side kept the refs. That distinction is the entire argument of coilysiren/inbox#441, so the push would be dishonest without it: ``` $ just mirror-sync-push push ok umbra push ok mcp-beaver push ok agent-compose ok umbra match 8e46ec6c5676 ok mcp-beaver match eebbe24e40c0 ok agent-compose match 3e8976f3732e 3/3 mirrors in sync ``` It reuses `check_mirror_sync.evaluate`, so the verification is the same code the scheduled watch runs, not a second opinion that could drift from it. ## Why a separate script rather than a flag `check_mirror_sync.py` needs no credential: both forges are public and it only reads. Pushing needs the Forgejo admin token. Adding a `--push` flag would have dragged that requirement into the module the scheduled workflow imports, and the credential-free property of that job is worth protecting deliberately rather than by accident. Both read the same `scripts/mirror-targets.json`, so the pusher and the watcher can never cover different sets. ## Behaviour worth knowing * An unknown `--only` name is refused outright rather than silently pushing nothing. * A source URL that does not split cleanly into owner and repo raises rather than POSTing a sync at a guessed repository. * Forgejo answers `500` with a useful body when the remote rejects the push, so that body is surfaced instead of the bare status. * Exit is 1 if any push is rejected **or** if any mirror still diverges afterwards. ## Tests 8 new, covering URL parsing in both directions and each upstream answer shape. Nothing reaches the network or the token store. Existing 12 still green. Exercised live before landing: `--only umbra`, all three, and the unknown-name refusal.
feat(mirrors): force-push every mirror from one verb, then verify
All checks were successful
TruffleHog / Scan for secrets (pull_request) Successful in 5s
CI / lint (pull_request) Successful in 46s
b0c37336c9
Forgejo's web UI syncs one repository at a time, so forcing the set meant
three visits or three API calls by hand. `just mirror-sync-push` does the
configured set, and `--only <name>` narrows it.

It verifies afterwards rather than trusting the push. A 200 means Forgejo
accepted the request, not that the far side kept the refs, and the whole
argument of coilysiren/inbox#441 is that the sending side cannot answer
that question. So the push reuses the receiving-side comparison and
reports both, and `--no-verify` opts out for the impatient case.

Kept as its own script rather than a flag on check_mirror_sync.py, because
pushing needs the Forgejo admin token and checking needs no credential at
all. The scheduled watch is worth keeping credential-free, and a shared
flag would have dragged the token requirement into it. Both read the same
mirror-targets.json, so the two can never cover different sets.

Refs coilysiren/inbox#440, coilysiren/inbox#441

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: sysadmin
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
coilyco-flight-deck/infrastructure!968
No description provided.