Declare the repos the estate wants, and what holds on them #16
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Meta/Campaign
Meta/Epic
Meta/Session
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Curated
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Scope/Campaign
Status/Abandoned
Status/Blocked
Status/Conflicted
Status/In Progress
Status/In Review
Status/Need Grooming
Status/Need More Info
Status/Ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Jostoph!16
Loading…
Reference in a new issue
No description provided.
Delete branch "agent.teyla/repo-conformance"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #3. Closes #5.
Both tickets ask for the same thing in different clothes: per-repo forge settings that must hold. #3 wants a push mirror
lar.ad/<repo> main → midgard/<repo>; #5 wants the merge-style allow-set that keeps pre-PR commit curation (Ting/Ting#4) from being discarded by a merge-commit. One declaration innix/forgejo.nix, beside the webhook, in Forgejo's own API field names so a reconciler can compare field-for-field.nix build .#forgejo-reposrenders it: the instance defaults, eight repos across two orgs, four public faces.Desired state, not an inventory
Repos are declared by org, and the declaration says what the estate wants, not what the forge already has. A repository named there that does not exist is a repository to create; a mirror whose target does not exist is a target to create. Existence is a field like any other, and reconciling it is the provider's job — not a precondition this file waits for.
midgard/modronis declared for exactly that reason: #3 names modron as midgard-bound,midgard/modrondoes not exist, and that is the point of declaring it.mirroris a bare boolean rather than a path, so the public face always carries the same name in the public org and a typo cannot invent a second naming convention.The converse deliberately does not hold: a repository on the forge and absent from the declaration is a finding for a person, never a deletion. Add-and-repair, the posture #3 asks for on mirrors. Nothing here should be able to destroy a repository as a side effect of an eval — which is also why
midgard/fastlane(superseded per #6) is named here as an observation and not removed.The instance defaults, upstreamed from flake-ops
modules/impls/forge/forgejo.nixsetsDEFAULT_BRANCH = "fastlane",DEFAULT_MERGE_STYLE = "rebase"andDEFAULT_DELETE_BRANCH_AFTER_MERGE = truein app.ini. Declared here too — the declaration, not a second implementation. They are the same policy as the per-repo settings, split across two mechanisms by Forgejo rather than by intent, and kept in separate files they can disagree silently.The property that makes the per-repo half necessary, measured rather than assumed:
So the per-repo half re-states it, both are rendered into one document, and
nix flake checkcompares them.default_merge_styleis derived from the instance value in Nix, so they cannot drift by construction; the check guards a future edit that hardcodes it separately.DEFAULT_BRANCHneeded care rather than copying — it is per-repo truth, not a constant.Ting/Ting(law repo, PRs land on main),Ting/Jostoph(no fastlane, per this repo's #8) andlar.ad/jostoph-rs(main-only by design) each say so with a reason. The rendered result matches the live branch reality on all eight.Both ends of a mirror
A
midgardrepo is where the estate publishes, not where it develops — it has shed the fastlane flow:mainonly.fastlanethere would name a branch the mirror'sbranch_filterexists to keep out.use_sshmeans the forge generates a key pair and exposes only the public half (PushMirror.public_key); the target must carry that half withread_only: false, because a read-only key cannot be pushed with and the mirror would fail every sync. Both ends of one credential, neither value written down here.Public faces are derived from the same (canonical, public) pairs as the mirrors, so a mirror cannot exist without its face or the reverse — and the public org cannot be declared into by hand, which is an eval-time error.
#3's open boundary question, answered
#3 said to make the provider-vs-steward call deliberately before building. It is already ruled at the top of
nix/forgejo.nix(owner, 2026-08-05), and a push mirror is the webhook case for the same mechanical reason, read off the live forge: a mirror is addressed byremote_name, whichCreatePushMirrorOptionhas no field for — the forge invents it (remote_mirror_c7JGztIibo-). Identity you did not choose is state, and state is the provider'srequireStatecase.#3's "designation needs a source of truth" is answered as a declaration here rather than a repo topic: a topic makes the forge the authority on what the forge ought to be, so drift and the record of intent share one mutable home.
Five real problems this names
Measured against the live forge:
lar.ad/dendritehas no push mirror at all, yetmidgard/dendriteexists and is diverged — canonical290f34b7, public528821af. Exactly #3's predicted failure.lar.ad/jostoph-rs's mirror has an emptybranch_filter— every branch, wherenixops4-providerscorrectly hasmain.default_merge_style = "merge", and none retires its branch at merge.midgard/modrondoes not exist, though modron is meant to have a public face.On (2) I am the evidence: I briefly created a
fastlanebranch onlar.ad/jostoph-rsearlier today (a mistake, corrected on #11). The empty filter published it tomidgard/jostoph-rswithin the hour, and deleting it from the canonical did not retract it — I removed the public copy by hand.midgard/jostoph-rsis back tomainonly at029a102, so the locks in #13 and Vedanta#4 are unaffected.Out of scope
checks.module-evaluatesfails the build if the generated unit grows acurlorFORGEJO_TOKEN. A reconciler here would mean deleting that check.lar.ad/nixops4-providers— the other half of both tickets, a separate repo.nixops-forgejoalready reconciles orgs as a resource.Boundary
This file declares and never reaches the network; the change keeps that —
writeTextand nothing else.The declaration deliberately states policy that is currently false nearly everywhere (all five findings). That is the point: a declaration nothing has reconciled yet describes intent, it does not claim the world already matches.
The list is hand-maintained, which is the honest cost: a repo missing from it is invisible to conformance. Same gap as a repo missing a topic, but in one reviewed place rather than across N settings pages.
Acceptance
nix build .#forgejo-repos— instance, 8 repos in 2 orgs, 4 public faces;default_branchmatches live branch reality on all eight. ✅nix flake check— green, 10 checks. ✅allow_merge_commits = true→does not declare the fleet merge policy✅branch_filter = ""(the real-world bug) →does not restrict itself to main✅rebase→ the policy-literal check ✅disagrees with the instance DEFAULT_MERGE_STYLE✅does not retire its branch at merge✅fastlanedefault →not main-only, PR-free and writable(three mutations) ✅midgard holds public faces, which are derived, not declared here✅Two corrections made during review, both mine: an earlier revision pinned
default_merge_styleto the literal"rebase"and compared it to the instance, so the agreement check could not fail independently — the literal now lives in one place. And an earlier revision excludedlar.ad/modronon the grounds thatmidgard/modrondid not exist, which contradicted this PR's own claim that a declaration states intent rather than describing the forge.Verified with
--override-input jostoph-rs path:…because this branch is offmain, whose input still pins the private canonical; #13 fixes that independently.aae801539btof1d4ccce17Declare the repo settings that must holdto Declare the repo settings that must hold, both endsf1d4ccce17tobbf39e7b5cDeclare the repo settings that must hold, both endsto Declare the repos the estate wants, and what holds on them