Design a GitHub <=> Forgejo command hub #823

Closed
opened 2026-07-09 17:39:40 +00:00 by coilyco-ops · 3 comments
Member

Today Forgejo has a guarded command surface through ward ops forgejo, but GitHub <=> Forgejo coordination is split across several places:

  • agentic-os has a mirror workflow for its GitHub mirror.
  • infrastructure has host-side mirror scripts for broader repository mirroring.
  • ward can dispatch GitHub-targeted warded github.com/owner/repo#N runs with GitHub App tokens.
  • ward docs describe GitHub as the external front door and Forgejo as canonical, with accepted GitHub PRs replayed onto Forgejo.

There is no single command hub that answers bridge questions like:

  • Which Forgejo repo maps to which GitHub mirror?
  • Is a mirror stale or divergent?
  • Which GitHub issue or PR maps to a Forgejo issue?
  • What accepted GitHub PR needs to be carried onto Forgejo main?
  • Which release or tag exists on one side but not the other?

Design a command hub for this bridge. Likely shape:

  • Add a guarded GitHub ops surface, probably ward ops github ..., backed by GitHub App installation tokens where possible.
  • Add higher-level bridge verbs, for example ward ops bridge status, ward ops bridge mirror-status, ward ops bridge carry-pr, and ward ops bridge map-issue.
  • Keep Forgejo canonical and GitHub as the external front door.
  • Default bridge commands to read-only status. Make writes explicit because GitHub comments, PR state changes, branch pushes, and release publishes are externally visible.
  • Do not make force-sync the default. Divergence needs a deliberate gate.
  • Decide whether this belongs wholly in ward-kdl specs, partly in ward roles, or partly in infrastructure host scripts.

Acceptance:

  • A design comment or doc names the command surface, auth model, write gates, and source-of-truth rules.
  • Follow-up implementation issues are filed for the first safe slice.
Today Forgejo has a guarded command surface through `ward ops forgejo`, but GitHub <=> Forgejo coordination is split across several places: * `agentic-os` has a mirror workflow for its GitHub mirror. * infrastructure has host-side mirror scripts for broader repository mirroring. * ward can dispatch GitHub-targeted `warded github.com/owner/repo#N` runs with GitHub App tokens. * ward docs describe GitHub as the external front door and Forgejo as canonical, with accepted GitHub PRs replayed onto Forgejo. There is no single command hub that answers bridge questions like: * Which Forgejo repo maps to which GitHub mirror? * Is a mirror stale or divergent? * Which GitHub issue or PR maps to a Forgejo issue? * What accepted GitHub PR needs to be carried onto Forgejo main? * Which release or tag exists on one side but not the other? Design a command hub for this bridge. Likely shape: * Add a guarded GitHub ops surface, probably `ward ops github ...`, backed by GitHub App installation tokens where possible. * Add higher-level bridge verbs, for example `ward ops bridge status`, `ward ops bridge mirror-status`, `ward ops bridge carry-pr`, and `ward ops bridge map-issue`. * Keep Forgejo canonical and GitHub as the external front door. * Default bridge commands to read-only status. Make writes explicit because GitHub comments, PR state changes, branch pushes, and release publishes are externally visible. * Do not make force-sync the default. Divergence needs a deliberate gate. * Decide whether this belongs wholly in ward-kdl specs, partly in ward roles, or partly in infrastructure host scripts. Acceptance: * A design comment or doc names the command surface, auth model, write gates, and source-of-truth rules. * Follow-up implementation issues are filed for the first safe slice.
Author
Member

🔎 ward agent advisor

ward agent advisor --harness codex ran a one-shot standard research pass on this question:

Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue.


Recommendation

Build the bridge as two layers in ward, with infrastructure retained only for background reconcile/scheduling:

  1. ward ops github ... as the low-level guarded GitHub surface.
  2. ward ops bridge ... as the high-level Forgejo<=>GitHub command hub.

That split matches how ward already works today:

  • ward ops forgejo is the audited low-level Forgejo surface generated from ward-kdl.
  • Higher-order behavior in ward already lives in hand-written Go when it spans multiple calls or forges.
  • cmd/ward/github_ops.go and cmd/ward/github_app.go already prove ward can talk to GitHub and mint repo-scoped GitHub App tokens.
  • /substrate/infrastructure/scripts/coilysiren-mirror-reconcile.sh already contains the current safe mirror policy, but it is a host/workflow asset, not a user-facing command hub.

Why this shape

The bridge questions in this issue are not one thing. They split cleanly into:

  • resource access: read repo, issue, PR, tag, release, and comment state from GitHub and Forgejo
  • bridge reasoning: decide whether mirrors are stale, whether a GitHub PR was already carried, whether an issue has a counterpart, and what is safe to write

ward-kdl is a good fit for the first half. Hand-written ward code is the right fit for the second half. Putting all of this in infrastructure scripts would keep the policy fragmented and host-bound. Putting all of it in ward-kdl would overfit a multi-system workflow into a low-level REST surface.

Proposed command surface

ward ops github

Start with a read tier that mirrors the Forgejo style:

  • ward ops github repo get <owner> <repo>
  • ward ops github issue get <owner> <repo> <n>
  • ward ops github issue-comment list <owner> <repo> <n>
  • ward ops github pr get <owner> <repo> <n>
  • ward ops github pr list <owner> <repo>
  • ward ops github release list <owner> <repo>
  • ward ops github tag list <owner> <repo>

Then add an explicit write tier only for actions the bridge truly needs later, such as commenting, labeling, or PR state changes.

ward ops bridge

This is the operator-facing hub. Default every verb here to read-only unless the name itself says otherwise.

Recommended first verbs:

  • ward ops bridge map repo <owner> <repo>
  • ward ops bridge map issue <forge-ref>
  • ward ops bridge mirror-status [<owner> <repo>]
  • ward ops bridge release-status [<owner> <repo>]
  • ward ops bridge pr-status [<owner> <repo>]
  • ward ops bridge status [<owner> <repo>]

Recommended later write verbs:

  • ward ops bridge carry-pr <owner> <repo> <pr>
  • ward ops bridge link-issue <forge-ref> <github-ref>
  • ward ops bridge sync-release <owner> <repo> <tag>

Source-of-truth rules

The bridge needs explicit source-of-truth rules or it will become another pile of heuristics.

Recommended rules:

  • Forgejo is canonical for git history, default branch, tags, releases, and internal issue closure.
  • GitHub is the public intake and PR surface.
  • Mirror mapping defaults to same owner/name slug, with explicit overrides for exceptions.
  • Bridge mapping metadata is explicit configuration, not inferred from whichever script ran last.
  • Live status is discovered from APIs and git refs, not cached state.

Recommended config placement:

  • Put the bridge repo-map and policy defaults in a ward config bundle, not infrastructure scripts.
  • A small dialect-2/fleet-style KDL block is the best fit if this stays operator/fleet config.
  • Infrastructure should own only the scheduled execution of reconcile jobs, secret wiring, and rollout.

Auth model

Recommended auth split:

  • Forgejo: keep the existing ward ops forgejo token path and broker model.
  • GitHub read path: prefer the same GitHub App installation-token model already implemented in cmd/ward/github_app.go.
  • GitHub write path: use explicit write-tier commands with GitHub App installation tokens where possible.
  • Keep gh CLI support for agent-dispatch compatibility, but do not make gh the long-term operator-surface dependency for the bridge hub.

Reasoning:

  • gh is already useful for warded GitHub runs.
  • The command hub should be auditable, repo-scoped, and least-privilege by default.
  • GitHub App installation tokens fit that model better than long-lived PATs.

Write gates

The issue is right that writes must be explicit. Recommended gate rules:

  • status, map-*, *-status, and list-* are read-only.
  • Any externally visible mutation lives behind a plainly mutating verb name.
  • carry-pr must never imply force-sync.
  • Divergence should report as a state to resolve, not an auto-repair default.
  • If a command can both plan and apply, split it into separate verbs or require an explicit --apply.

Carrying GitHub PRs

This needs one important decision.

Recommended rule for v1:

  • Treat "ready to carry" as a merged GitHub PR whose head commit is not yet reachable from Forgejo's default branch.

Do not automate around softer concepts like "approved" or "accepted" first. Those are policy-laden and easy to get wrong. Merged-on-GitHub is objective and observable.

For later, carry-pr should:

  • resolve the mapped Forgejo repo
  • inspect whether the PR commits are already present on Forgejo
  • refuse if the histories diverged in a way that needs a human decision
  • carry via a normal merge/cherry-pick flow that preserves provenance
  • optionally comment back on GitHub and Forgejo, but only as an explicit write path

Issue mapping

Issue mapping also needs an explicit canonical mechanism.

Recommended rule for v1:

  • A cross-forge issue pair is canonical only when there is an explicit bridge marker or backlink.
  • Heuristic matches by title/body may be shown as possible matches, never as canonical.

Practical shape:

  • when a Forgejo issue is created from a GitHub intake, add a machine-readable backlink on both sides
  • ward ops bridge map-issue reads those markers first
  • if no marker exists, it can optionally emit a best-effort guess marked as inference

That avoids fragile title matching becoming policy.

Mirror and release status

The bridge hub should answer three distinct status questions, not flatten them into one noisy report:

  • mirror-status: default-branch reachability and divergence
  • pr-status: merged GitHub PRs not yet present on Forgejo, or open PRs that appear already carried
  • release-status: tag/release presence mismatch by tag name and target commit

For releases and tags, compare at least:

  • tag name
  • target commit SHA
  • release presence on each forge

Comparing only names is too weak.

Placement decision

Recommended placement by responsibility:

  • ward-kdl: low-level ops github REST surface, read tier first, write tier later
  • ward proper: all ops bridge verbs and any multi-forge reasoning
  • infrastructure: scheduled reconcile workflow, secrets, rollout, host automation

Do not make the bridge hub an infrastructure-only feature. Operators need one stable user-facing command surface, and this repo is where that surface belongs.

Existing assets to reuse

This design can reuse real pieces already present:

  • cmd/ward/github_ops.go for GitHub issue-thread semantics and attribution model
  • cmd/ward/github_app.go for App installation-token minting
  • cmd/ward/ops.go / ward ops forgejo as the model for a generated audited API surface
  • /substrate/infrastructure/scripts/coilysiren-mirror-reconcile.sh as the current safe reconcile policy reference
  • /substrate/infrastructure/.forgejo/workflows/mirror-sync.yml as the current staged executor for background reconcile

Risks

The main risks are architectural, not coding difficulty:

  • policy split risk: if mirror logic stays in infra while bridge logic grows in ward, the two will drift
  • mapping ambiguity risk: without explicit bridge markers, issue and PR mapping becomes heuristic and brittle
  • auth sprawl risk: mixing PATs, gh, App tokens, and Forgejo bot tokens without a clear boundary will make the surface hard to reason about
  • unsafe repair risk: any default that tries to auto-heal divergence will eventually overwrite history or carry the wrong commit
  • scope creep risk: if v1 tries to solve write flows, replay policy, release publish, and background scheduling together, it will stall

Open questions to resolve in the design doc

These need explicit answers before implementation starts:

  • Where exactly does the repo mapping live? A new bridge block in ward config is the strongest answer.
  • What is the canonical machine-readable backlink format for issue/PR mapping?
  • Should carry-pr operate only on merged GitHub PRs in v1, or also on approved-but-unmerged PRs? Recommendation: merged only.
  • Should bridge writes comment back on GitHub/Forgejo automatically, or should commentary be a separate explicit step?
  • Does release sync mean only reporting mismatch, or eventually publishing missing releases/assets too?

First safe slice

The first implementation slice should stay entirely on the read path:

  1. Add ward ops github read-tier verbs.
  2. Add ward ops bridge map repo, map issue, mirror-status, and status.
  3. Define the bridge mapping config and explicit backlink format.
  4. Document source-of-truth and write-gate rules.

That slice is safe because it creates one command hub immediately without taking on irreversible actions.

Suggested follow-up issues

Suggested issue sequence after this design lands:

  • ward - Add ward ops github read-tier surface backed by GitHub App installation tokens.
  • ward - Add ward ops bridge read-only status verbs (map repo, map issue, mirror-status, status).
  • ward - Define and document bridge mapping config plus machine-readable backlink format.
  • ward - Design carry-pr apply semantics after the status layer proves the model.
  • infrastructure - Repoint scheduled mirror/reconcile automation to consume the stabilized ward bridge policy, or explicitly document why it remains separate.

Bottom line

The best design is not "add some GitHub commands" and not "move the scripts into ward". It is:

  • ward ops github for low-level guarded GitHub access
  • ward ops bridge for cross-forge reasoning and operator answers
  • explicit source-of-truth and mapping metadata
  • read-only first, writes later and named plainly
  • infrastructure kept as the scheduler/executor, not the command hub

That gives ward one coherent bridge surface without weakening the current Forgejo-canonical model.


Researched and posted automatically by ward agent advisor --harness codex (ward#179). This is one-shot research, not a carried change - verify before acting on it.

— Codex, via ward agent

### 🔎 ward agent advisor `ward agent advisor --harness codex` ran a one-shot **standard** research pass on this question: > Read the issue title, body, and comment thread below as the research brief. Identify the decisions, options, open questions, and risks, then give a clear advisory recommendation that can be posted back on the issue. --- ## Recommendation Build the bridge as **two layers in `ward`**, with **infrastructure retained only for background reconcile/scheduling**: 1. **`ward ops github ...`** as the low-level guarded GitHub surface. 2. **`ward ops bridge ...`** as the high-level Forgejo<=>GitHub command hub. That split matches how `ward` already works today: - `ward ops forgejo` is the audited low-level Forgejo surface generated from ward-kdl. - Higher-order behavior in `ward` already lives in hand-written Go when it spans multiple calls or forges. - `cmd/ward/github_ops.go` and `cmd/ward/github_app.go` already prove ward can talk to GitHub and mint repo-scoped GitHub App tokens. - `/substrate/infrastructure/scripts/coilysiren-mirror-reconcile.sh` already contains the current safe mirror policy, but it is a **host/workflow** asset, not a user-facing command hub. ## Why this shape The bridge questions in this issue are not one thing. They split cleanly into: - **resource access**: read repo, issue, PR, tag, release, and comment state from GitHub and Forgejo - **bridge reasoning**: decide whether mirrors are stale, whether a GitHub PR was already carried, whether an issue has a counterpart, and what is safe to write `ward-kdl` is a good fit for the first half. Hand-written `ward` code is the right fit for the second half. Putting all of this in infrastructure scripts would keep the policy fragmented and host-bound. Putting all of it in ward-kdl would overfit a multi-system workflow into a low-level REST surface. ## Proposed command surface ### `ward ops github` Start with a **read tier** that mirrors the Forgejo style: - `ward ops github repo get <owner> <repo>` - `ward ops github issue get <owner> <repo> <n>` - `ward ops github issue-comment list <owner> <repo> <n>` - `ward ops github pr get <owner> <repo> <n>` - `ward ops github pr list <owner> <repo>` - `ward ops github release list <owner> <repo>` - `ward ops github tag list <owner> <repo>` Then add an explicit **write tier** only for actions the bridge truly needs later, such as commenting, labeling, or PR state changes. ### `ward ops bridge` This is the operator-facing hub. Default every verb here to **read-only** unless the name itself says otherwise. Recommended first verbs: - `ward ops bridge map repo <owner> <repo>` - `ward ops bridge map issue <forge-ref>` - `ward ops bridge mirror-status [<owner> <repo>]` - `ward ops bridge release-status [<owner> <repo>]` - `ward ops bridge pr-status [<owner> <repo>]` - `ward ops bridge status [<owner> <repo>]` Recommended later write verbs: - `ward ops bridge carry-pr <owner> <repo> <pr>` - `ward ops bridge link-issue <forge-ref> <github-ref>` - `ward ops bridge sync-release <owner> <repo> <tag>` ## Source-of-truth rules The bridge needs explicit source-of-truth rules or it will become another pile of heuristics. Recommended rules: - **Forgejo is canonical for git history, default branch, tags, releases, and internal issue closure.** - **GitHub is the public intake and PR surface.** - **Mirror mapping defaults to same `owner/name` slug**, with explicit overrides for exceptions. - **Bridge mapping metadata is explicit configuration**, not inferred from whichever script ran last. - **Live status is discovered from APIs and git refs**, not cached state. Recommended config placement: - Put the **bridge repo-map and policy defaults** in a ward config bundle, not infrastructure scripts. - A small dialect-2/fleet-style KDL block is the best fit if this stays operator/fleet config. - Infrastructure should own only the **scheduled execution** of reconcile jobs, secret wiring, and rollout. ## Auth model Recommended auth split: - **Forgejo**: keep the existing `ward ops forgejo` token path and broker model. - **GitHub read path**: prefer the same GitHub App installation-token model already implemented in `cmd/ward/github_app.go`. - **GitHub write path**: use explicit write-tier commands with GitHub App installation tokens where possible. - Keep `gh` CLI support for agent-dispatch compatibility, but do **not** make `gh` the long-term operator-surface dependency for the bridge hub. Reasoning: - `gh` is already useful for `warded` GitHub runs. - The command hub should be **auditable, repo-scoped, and least-privilege by default**. - GitHub App installation tokens fit that model better than long-lived PATs. ## Write gates The issue is right that writes must be explicit. Recommended gate rules: - `status`, `map-*`, `*-status`, and `list-*` are read-only. - Any externally visible mutation lives behind a plainly mutating verb name. - `carry-pr` must never imply force-sync. - Divergence should report as a state to resolve, not an auto-repair default. - If a command can both plan and apply, split it into separate verbs or require an explicit `--apply`. ## Carrying GitHub PRs This needs one important decision. Recommended rule for v1: - Treat **"ready to carry"** as **a merged GitHub PR whose head commit is not yet reachable from Forgejo's default branch**. Do **not** automate around softer concepts like "approved" or "accepted" first. Those are policy-laden and easy to get wrong. Merged-on-GitHub is objective and observable. For later, `carry-pr` should: - resolve the mapped Forgejo repo - inspect whether the PR commits are already present on Forgejo - refuse if the histories diverged in a way that needs a human decision - carry via a normal merge/cherry-pick flow that preserves provenance - optionally comment back on GitHub and Forgejo, but only as an explicit write path ## Issue mapping Issue mapping also needs an explicit canonical mechanism. Recommended rule for v1: - A cross-forge issue pair is canonical only when there is an explicit bridge marker or backlink. - Heuristic matches by title/body may be shown as **possible matches**, never as canonical. Practical shape: - when a Forgejo issue is created from a GitHub intake, add a machine-readable backlink on both sides - `ward ops bridge map-issue` reads those markers first - if no marker exists, it can optionally emit a best-effort guess marked as inference That avoids fragile title matching becoming policy. ## Mirror and release status The bridge hub should answer three distinct status questions, not flatten them into one noisy report: - **mirror-status**: default-branch reachability and divergence - **pr-status**: merged GitHub PRs not yet present on Forgejo, or open PRs that appear already carried - **release-status**: tag/release presence mismatch by tag name and target commit For releases and tags, compare at least: - tag name - target commit SHA - release presence on each forge Comparing only names is too weak. ## Placement decision Recommended placement by responsibility: - **ward-kdl**: low-level `ops github` REST surface, read tier first, write tier later - **ward proper**: all `ops bridge` verbs and any multi-forge reasoning - **infrastructure**: scheduled reconcile workflow, secrets, rollout, host automation Do **not** make the bridge hub an infrastructure-only feature. Operators need one stable user-facing command surface, and this repo is where that surface belongs. ## Existing assets to reuse This design can reuse real pieces already present: - `cmd/ward/github_ops.go` for GitHub issue-thread semantics and attribution model - `cmd/ward/github_app.go` for App installation-token minting - `cmd/ward/ops.go` / `ward ops forgejo` as the model for a generated audited API surface - `/substrate/infrastructure/scripts/coilysiren-mirror-reconcile.sh` as the current safe reconcile policy reference - `/substrate/infrastructure/.forgejo/workflows/mirror-sync.yml` as the current staged executor for background reconcile ## Risks The main risks are architectural, not coding difficulty: - **policy split risk**: if mirror logic stays in infra while bridge logic grows in ward, the two will drift - **mapping ambiguity risk**: without explicit bridge markers, issue and PR mapping becomes heuristic and brittle - **auth sprawl risk**: mixing PATs, `gh`, App tokens, and Forgejo bot tokens without a clear boundary will make the surface hard to reason about - **unsafe repair risk**: any default that tries to auto-heal divergence will eventually overwrite history or carry the wrong commit - **scope creep risk**: if v1 tries to solve write flows, replay policy, release publish, and background scheduling together, it will stall ## Open questions to resolve in the design doc These need explicit answers before implementation starts: - Where exactly does the repo mapping live? A new bridge block in ward config is the strongest answer. - What is the canonical machine-readable backlink format for issue/PR mapping? - Should `carry-pr` operate only on merged GitHub PRs in v1, or also on approved-but-unmerged PRs? Recommendation: merged only. - Should bridge writes comment back on GitHub/Forgejo automatically, or should commentary be a separate explicit step? - Does release sync mean only reporting mismatch, or eventually publishing missing releases/assets too? ## First safe slice The first implementation slice should stay entirely on the **read path**: 1. Add **`ward ops github` read-tier verbs**. 2. Add **`ward ops bridge map repo`**, **`map issue`**, **`mirror-status`**, and **`status`**. 3. Define the bridge mapping config and explicit backlink format. 4. Document source-of-truth and write-gate rules. That slice is safe because it creates one command hub immediately without taking on irreversible actions. ## Suggested follow-up issues Suggested issue sequence after this design lands: - **ward** - Add `ward ops github` read-tier surface backed by GitHub App installation tokens. - **ward** - Add `ward ops bridge` read-only status verbs (`map repo`, `map issue`, `mirror-status`, `status`). - **ward** - Define and document bridge mapping config plus machine-readable backlink format. - **ward** - Design `carry-pr` apply semantics after the status layer proves the model. - **infrastructure** - Repoint scheduled mirror/reconcile automation to consume the stabilized ward bridge policy, or explicitly document why it remains separate. ## Bottom line The best design is **not** "add some GitHub commands" and **not** "move the scripts into ward". It is: - **`ward ops github` for low-level guarded GitHub access** - **`ward ops bridge` for cross-forge reasoning and operator answers** - **explicit source-of-truth and mapping metadata** - **read-only first, writes later and named plainly** - **infrastructure kept as the scheduler/executor, not the command hub** That gives ward one coherent bridge surface without weakening the current Forgejo-canonical model. --- Researched and posted automatically by `ward agent advisor --harness codex` (ward#179). This is one-shot research, not a carried change - verify before acting on it. <!-- ward-agent-reply --> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

Kai's current direction is that infrastructure should be the GitHub <=> Forgejo coordination hub. Filed coilyco-flight-deck/infrastructure#489 to own the fleet/stateful side: mirror units, credentials, token helpers, divergence checks, scheduled sync, and hub runbooks. Ward should stay the reusable command surface and dispatch engine rather than owning fleet-specific coordination state.

Kai's current direction is that infrastructure should be the GitHub <=> Forgejo coordination hub. Filed coilyco-flight-deck/infrastructure#489 to own the fleet/stateful side: mirror units, credentials, token helpers, divergence checks, scheduled sync, and hub runbooks. Ward should stay the reusable command surface and dispatch engine rather than owning fleet-specific coordination state.
Author
Member

Consult interrogation: closing this ward-side design shell. Kai's current direction moved the GitHub <=> Forgejo coordination hub to infrastructure, tracked as coilyco-flight-deck/infrastructure#489. Ward remains the reusable command surface and dispatch engine, but this issue should not sit as ward headless implementation work.

Consult interrogation: closing this ward-side design shell. Kai's current direction moved the GitHub <=> Forgejo coordination hub to infrastructure, tracked as coilyco-flight-deck/infrastructure#489. Ward remains the reusable command surface and dispatch engine, but this issue should not sit as ward headless implementation work.
Sign in to join this conversation.
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/ward#823
No description provided.