10 Forge Flow
odin edited this page 2026-08-07 09:13:18 +02:00

Forge flow — branching, merging, and the steward

Rename notice (2026-08-07, freeholder declaration): this law is renamed Ratification — the process by which a member's work becomes binding in the Ting. The name "Forge flow" described the tool, not the law. The rename is declared in the [Ting declaration] and propagates to this page, the steward specs, issue #22, and every AGENTS.md in a later migration pass. Until that pass, "Forge flow" and "Ratification" name the same law.

Ratified 2026-08-04, owner + agent.odin, clause by clause (law 13 ratified the same day, after the page first landed). Execution is tracked in issue #22 (FM-FORGE-FLOW). Where this page and the implementation disagree, the code wins — but then this page must be repaired in the same breath.

Scope: forge-wide. This law binds every repository on jo.et0.pw — there is no deviation when a swarm of agents goes live. Each repo instantiates it at birth through its declared steward spec (law 12): fastlane, protections, label spec, tango/<ticket>-<slug> naming. The page lives in flaky-mesh's wiki for provenance; its authority does not end there.

Transition clause (2026-08-04): work dispatched before this ratification — flaky-mesh PR #23, lar.ad/modron PRs #14, #16, #17 — merges owner-direct, grandfathered. The no-bounce doctrine applies to the process itself: the miss (a mold ratified after dispatch) is charged to who should have spoken earlier, not to the dispatched agents. The mold binds work dispatched after ratification. A boundary marker, not a precedent.

The model

agent workspace ──push──▶ tango/<ticket>-<slug> ──PR──▶ fastlane ──curation──▶ main ──tag-time mirror──▶ origin (GitHub)

fastlane is agent-governed; main is human-anchored. The further from the human, the more mechanical the gate. Agents propose and police each other on fastlane; only the owner and one designated seat touch main; only the owner's hand publishes.

The thirteen laws

1. Single base

Every PR sits on fastlane tip, always. No epic integration branches. On any push to fastlane — steward merges and owner direct pushes alike — the steward rebases every open PR onto the new tip through Forgejo's own update API (POST /api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase). Conflicts return the PR to its agent with the evidence; the steward never resolves. The forge-side rebase is authoritative; change-id headers (git.write-change-id-header, law-adjacent tooling) make the agent's re-sync silent instead of a divergence ritual.

Why: a queue of stale PRs is a queue of lies. Rebase-on-advance keeps every open proposal honest at all times, and conflicts signal dead assumptions, which belong to the ticket's owner — never to a bot.

2. The tag horizon

History at or below the newest version tag on main is immutable. The untagged tail may be absorbed, squashed, and re-curated. Tags never move — the horizon and origin's fast-forward guarantee both rest on that single invariant. Local enforcement: jj immutable_heads() extended with tags(). Forge enforcement: the push whitelist (law 8) with force-push permitted but disciplined — Forgejo cannot express "only above the last tag", so that clause is law, not mechanism.

Why: jj curation does not stop being useful at promotion. The horizon makes rewrite safe by bounding it: everything the fleet or origin may have seen is frozen; everything above is still clay.

3. The drift invariant

After every promotion, diff main fastlane is empty except for deliberately held-back changes. Drift means main is no longer a cut of fastlane — it is a fork, and the model has failed silently.

4. Backflow

Anything that lands on main outside the fastlane path — a promotion-time fix, an emergency — is committed back into fastlane immediately. This is the classic two-branch failure mode; gitflow died of it.

5. Provenance by citation

Curated main commits cite their source (PR #n, fastlane commit). Change-id headers give this a mechanical assist: the same change id survives steward rebases and is greppable end to end. Six months from now, every line in main must answer "why do you exist" without archaeology.

6. Origin is a tag-anchored mirror

Untagged commits never leave the forge. Origin (GitHub) receives main at tag time — always a fast-forward, since new tags always descend from old ones — plus the tags themselves. Subscribing to forge main is consent to tail rewrites; tags are the only states guaranteed stable. One-time surgery: the GitHub repo currently holds the unrelated bootstrap commit a430f4db; the first mirror push is a deliberate reset, ff-forever afterward. Forgejo's built-in push mirror is all-refs and therefore wrong for this — the mirror is a scheduled job on bifrost, declared in the flake.

7. Tagging is a HITL signing ceremony

Tags are minted by the owner, signed with the owner's GPG key, which does not auto-unlock — every signature is a deliberate act, by design. Explicit git tag -s in the colocated repo; no tag.gpgSign default, so signing is never a habit. Agents never tag: no agent holds the key, and the key never stands unlocked waiting to be borrowed. The public key is registered on Forgejo and GitHub so signatures verify in both UIs. Consequence accepted knowingly: tags are rare, so origin sits behind fastlane most of the time — origin is the release archive, not the working surface.

8. Curation seats

Push to main = the owner plus at most one agent persona per org. The forge's org layout (2026-08-04) and its seats:

  • larandar (flaky-mesh + infra): odin
  • swarm (sleipnir — moved to its own org 2026-08-04): teyla
  • lar.ad (modron, hydramem): odin — same seat, lighter posture: lar.ad's steward spec (law 12) is deliberately more relaxed than flaky-mesh's. Relaxation is declared in the spec, not deviated from the law.

A persona may hold seats in more than one org; the cap is per org, not per persona. And a seat is curation authority, not a fence: a seat holder works in any org as an ordinary agent — PRs to fastlane, tickets, review — the seat governs only who curates main.

Enforced by the branch-protection push whitelist — this law is mechanical. The steward rides the seat identity. Blast radius of a compromised or misbehaving seat is exactly the untagged tail of main — bounded by law 2, recoverable from the last signed tag.

9. Refs cite tickets, and the PR closes them

PR branches are tango/<ticket>-<slug>, naming the lead ticket — the one the work was picked up under. Local tango workspace names follow the same convention so the local bookmark and the pushed ref are one name end to end. This is not bureaucracy — it is the naming scheme enforcing the issue-authority rule.

The unit is a session, not a ticket. One session opens one or more PRs; one PR closes one or more tickets. The old reading — a PR per ticket — is withdrawn: it forced either an artificially wide ticket or an artificially narrow PR whenever the natural unit of work sat across a few tickets, and it made "which ticket does this branch belong to" a question with no honest answer.

What survives is the floor and the ceiling:

  • No ticket, no branch, no PR. A session that has not claimed a ticket has nothing to propose.
  • Every PR closes at least one ticket, or says in its body why it closes none. That is what keeps the many-to-many honest — the count of PRs and the count of tickets need not match, but nothing may land unattributed.
  • A session splits its work where the review does. Several PRs off one session is the normal case when the pieces are reviewable apart; it is not a concession. Each still sits on fastlane tip (law 1) and is judged on its own.

A PR body must carry a closing keyword for every ticket it deliversCloses #n, one per line, at the end of the body. Not (#n) in prose, not Refs #n, not a link. The forge acts on the keyword and nothing else, so a citation the forge cannot parse is a citation that does not close anything.

  • Closes #n — this PR delivers ticket #n. The forge closes it at merge.
  • Refs #n — related, not delivered. Roots, epics, and tickets a later PR will close. A PR whose body carries only Refs lines closes nothing, and that must be a deliberate statement rather than an oversight.

Commits may carry the same keyword; the PR body is authoritative because it is what survives a squash (law 10) and what the merger last read. A commit-only Closes #n on a squashed PR reaches the forge as body text and does close, but relying on that means the guarantee depends on the merge style, which is chosen per-PR. Write it in the body.

No ticket delivered, no merge. A PR that neither closes a ticket nor states in the body why it closes none is not ready; the reviewer withholds approval. Applies to the author, not the steward — the steward observes and reports, it does not author PR bodies.

Why: law 13 already says tickets close at fastlane merge and names the PR body as the mechanism, but nothing made anyone write the line. Every merged PR on the forge up to 2026-08-06 carried none, so trackers drifted: work landed on fastlane and the tickets stayed open, which makes an open-issue count describe nothing. A convention nobody can forget is worth more than one everybody does — the branch name already links the lead ticket mechanically, so the closing keyword is the one remaining hand-written step, and it is now required rather than assumed.

It is also what makes session-as-the-unit safe. Once a PR may close several tickets and a session may open several PRs, the branch name alone no longer tells the steward what closed — only the keyword does. The two halves of this law arrived together for that reason.

10. Merge style at review

Squash raw PRs; preserve queen-curated lines (rebase/ff-merge). The invariant is that fastlane stays feature-sized, not that every PR is one commit. The curated label on the PR is the machine-readable verdict the steward reads; the merger chooses and owns the choice.

11. Approval and the mechanical merge

Any collaborator on the repo may approve — except the PR's own author. Self-approval would make the gate theater: drone authors, queen (or peer) approves, steward merges. The steward merges mechanically once the gate is green: approved, atop fastlane tip, no conflicts. Direct push by authority holders is the accepted bypass — authority is the bypass; abuse is a trust event, made visible by laws 2 and 5.

12. Steward scope

The steward fills what Forgejo cannot enforce, and nothing more:

  • Forge-side conformance is the steward. Branch protections, labels, PR defaults, webhooks — read via API, compared against the declared spec, remediated or (for anything destructive) alarmed. Birth is an act: new repos are bootstrapped by a declared just forgejo recipe; the steward reconciles forever after.
  • Client-side bootstrap is the devshell. jj conventions (write-change-id-header, immutable_heads() | tags()) live in clones on machines; a service on bifrost cannot and should not reach into workstations. just doctor recipes assert them on entry. The steward only observes the effects — e.g. change-id header presence on pushed refs.
  • Not its job: CI, merge queues, conflict resolution, authoring.

13. Tracker lifecycle — states, overlays, and the no-bounce doctrine

The tracker is a state machine: every label is either a state or an overlay, and every transition has an owner.

States — pipeline position. Mutually exclusive; exactly one (or none) per issue; they gate:

state meaning set by
(none) filed, unclaimed filer
in-progress claimed, workspace active the agent, by self-assigning — exactly one assignee, or none when idle
in-review PR open against fastlane steward (PR webhook; every ticket the body closes, not just the lead one in the tango/<ticket>-<slug> branch name — one PR may cover several, law 9)
conflicted steward rebase failed; work returned to its agent steward (update-API failure), with evidence in a comment; back to in-review on the fix push
closed merged to fastlane Closes #n in the merged PR body — required by law 9, not optional

Overlays — orthogonal facts. Combinable with any state; they never move position and never gate:

overlay meaning set by
blocked at least one open Blocked by edge steward, derived only — never hand-set. Purely informative: an agent may still claim deliberately
needs-decision parked on an owner call hand-set intent; work pauses, position does not move
curated merge-style verdict (law 10) approver — PR label, not issue label

Tickets close at fastlane merge — and there is no bounce. A ticket is a unit of specification. The PR is judged against its ticket, not against the ideal. An implementation defect is ordinary review: changes requested, the PR stays in-review. A specification miss — the ticket asked for the wrong thing or too little — discovered in grooming or review triggers a new, linked ticket, never a bounce of the PR and never a resurrection of a closed one. That is what makes closing at merge honest: "closed" means "delivered to fastlane as specified", and law 3's drift invariant is what guarantees closed tickets reach main.

The asymmetry is deliberate and comes from production: a spec miss found late is charged to whoever should have spoken earlier — the groomer, the reviewer — never to the implementer for failing to foresee complexity. Seniors speak early; juniors are not required to be prophets.

The doctrine has one leniency: an external event may reevaluate the ticket. If the ground moves before the PR merges — another ticket landing, an upstream change, the world shifting — the ticket itself may be reevaluated (amended, rescoped, closed as superseded) instead of mechanically merging plus filing a follow-up. Mid-flight, while the PR is in review, that reevaluation needs the dev's approval: the implementer consents to the ground shifting under them, or the no-bounce rule applies.

Steward automation of the derivable. PR opened → in-review (strips in-progress); rebase failure → conflicted + evidence + reassignment to the author; fix push → in-review; merge → close.

Closure is the forge's, from the law-9 keyword — the steward does not close tickets by inference. What it does is make a missing keyword loud instead of silent: on PR open, if the body carries no closing keyword and the branch name names a ticket, the steward comments with the line the author should have written. A nudge on the PR, not an edit of it and not a block on the merge; authorship stays with the author (law 12), and the reviewer is the gate.

The blocked overlay is recomputed on issue-close events and on the periodic conformance sweep: any open edge → label present; last edge closed → label removed plus an "unblocked: #n closed" comment, so the waiting party gets a pulse.

Epics are not issues. Campaigns and groupings live in Forgejo Projects; an issue never carries epic state. The Epic:-style holding issues (e.g. #9) migrate to Projects when the steward spec lands.

Labels are declared in the steward's conformance spec (law 12) — states, overlays, and the blocked derivation rule alike. No hand-created labels, no renames outside the spec.

The build queue

Tracked as checkboxes in issue #22: collaborator grants for agent.*, branch protections on fastlane and main, the steward service on bifrost, the tag-time mirror job (incl. the one-time bootstrap reset), repo jj config + devshell doctor, the owner's GPG public key on both forges, a sweep of stale local lines — plus the law-13 work: the steward label spec (states, overlays, blocked derivation) and adopting Forgejo Projects for epics, migrating #9 — plus the law-9 amendment: the missing-closing-keyword nudge on PR open, and a one-time reconciliation sweep of PRs merged before 2026-08-06, whose tickets were left open.

Provenance

Amended 2026-08-06 (law 9, owner-directed), in two parts:

  1. The closing keyword is required in the PR body rather than named as a mechanism nobody had to use. Cause: every PR merged on the forge up to that date carried none — including swarm/vordr PR #12, whose four delivered spec tickets (#7, #8, #9, #11) were closed by hand afterwards.
  2. The unit is a session, not a ticket — one session, one or more PRs; one PR, one or more tickets. Withdraws the implicit ticket-per-PR reading, which distorted either the ticket or the PR whenever the natural unit of work sat across a few tickets.

They are one amendment: once the mapping is many-to-many, the branch name stops being sufficient evidence of what closed, and only the keyword is. Law 13's close row, its in-review row, and the steward's automation list were repaired in the same breath.

Designed and ratified clause-by-clause on 2026-08-04 between the owner and agent.odin. Live-verified facts behind the design: the forge held only main (c6816528, in sync with the workstation); GitHub held only the bootstrap commit; zero PRs had ever existed on the forge.