policy: declare the label the rules read, and answer on one comment #20

Merged
larandar merged 2 commits from tango/curated-and-standing into main 2026-08-11 20:13:36 +00:00
Owner

Three things asked for by the owner on 2026-08-11, and the first two turn out to be one thing. Built on #19, which merged while this was being written.

In scope

  • The curated label is declared, so that it exists. nix/forgejo.nix carries it as org-level desired state beside the repos, mirrors and webhook — in Forgejo's own field names, reconciled by the same thing that reconciles the rest. Org-level rather than per-repo, for the same reason the webhook is namespace-scoped: it covers repositories born later.
  • And the rules can no longer read a label the estate does not create. nix/policy.nix takes curated_label from nix/forgejo.nix instead of spelling it a second time — exactly the coupling mergeStyles already has, and for the same reason. Eval fails if policy names a label the declaration lacks; nix flake check fails if the label is declared without a description, because a bare label tells a reviewer nothing about what asserting it commits them to.
  • Squash when the label is absent is now pinned rather than incidental. An eval assertion: the uncurated style is the one that applies when nobody asserted anything, and a default that drifts is a default nobody chose. Curated stays rebase, still drawn from the repos' own allow-set, so neither can name a style the forge refuses.
  • The steward answers each subject on one comment, kept current. Every issue and pull request it rules on gets one comment carrying a marker (<!-- jostoph:standing -->), and its declaration asks to be upserted: an executor edits the comment carrying the marker, and posts only when there is none. The body is an aggregate over every conclusion about that subject — two missing sections are one comment naming both, not two comments racing to be the last word, which is what this replaces. Sorted and deduplicated, so an unchanged standing writes identical bytes and "nothing changed" is a comparison rather than a guess.
  • The comment also names its repository and index. The old one named neither: as declared it was unpostable.
  • A push is not a subject and gets no comment. There is nowhere to put one, and inventing somewhere is worse than saying nothing.

The aggregate is a second ascent! program, Standing, run after the rules — an aggregate says what it means only over a relation that has stopped growing, and inside the rule set conclusion is still being derived. Same shape as the substrate's own dispatch-then-resolution.

Out of scope

  • The label's name. It stays curated. lar.ad carries the same meaning as Reviewed/Curated; unifying the two across the estate is a decision for whoever owns both, not a side effect of this file.
  • The forge's own default merge style. It stays rebase, still upstreamed from lar.ad/flake-ops's app.ini. If "squash unless curated" should also be what a human gets clicking merge unaided, that is a change to the instance default and belongs in flake-ops — made here it would only disagree with the instance and fail the check that exists to catch exactly that.
  • Performing any of it. Comments, merges and updates are still declared and never executed.
  • Creating the label on the forge. This declares it; reconciling stays the nixops4 provider's, per the ruling at the top of nix/forgejo.nix.

Boundary

nix/forgejo.nix (the label, and rendering it), nix/policy.nix (reads it; two new assertions), flake.nix (one check), src/policy.rs (the standing comment), README.md. No change to standing, protected refs, required sections, the merge styles themselves, mirrors, or the NixOS module.

Acceptance

Verified locally, not argued:

  • nix flake check passes, including the new label check and the module eval.
  • 35 tests pass (was 31 — five added, one replaced), and cargo clippy --all-targets -- -D warnings is clean.
  • nix build .#policy still renders curated_label: "curated", so policy/ting.json is unchanged and the drift check stays quiet.

Depends on lar.ad/jostoph-rs#5

Against the currently pinned substrate nix flake check fails here, correctly:

every_finding_lands_in_one_comment ... FAILED
  left: 2, right: 1

One comment asked for by two findings is one comment only once resolve keeps the carried-effect set a set — which is jostoph-rs#5. With --override-input jostoph-rs path:… on that branch, all checks pass. So: land jostoph-rs#5, let the mirror carry it, nix flake update jostoph-rs, then this.

Writing this is also what surfaced the other half of that PR: an approval from a login the estate does not recognize was denied and had its merge declared anyway.

Authored as agent.odin.

Three things asked for by the owner on 2026-08-11, and the first two turn out to be one thing. Built on #19, which merged while this was being written. ## In scope - **The `curated` label is declared, so that it exists.** `nix/forgejo.nix` carries it as org-level desired state beside the repos, mirrors and webhook — in Forgejo's own field names, reconciled by the same thing that reconciles the rest. Org-level rather than per-repo, for the same reason the webhook is namespace-scoped: it covers repositories born later. - **And the rules can no longer read a label the estate does not create.** `nix/policy.nix` takes `curated_label` from `nix/forgejo.nix` instead of spelling it a second time — exactly the coupling `mergeStyles` already has, and for the same reason. Eval fails if policy names a label the declaration lacks; `nix flake check` fails if the label is declared without a description, because a bare label tells a reviewer nothing about what asserting it commits them to. - **Squash when the label is absent is now pinned rather than incidental.** An eval assertion: the uncurated style is the one that applies when nobody asserted anything, and a default that drifts is a default nobody chose. Curated stays `rebase`, still drawn from the repos' own allow-set, so neither can name a style the forge refuses. - **The steward answers each subject on one comment, kept current.** Every issue and pull request it rules on gets one comment carrying a marker (`<!-- jostoph:standing -->`), and its declaration asks to be **upserted**: an executor edits the comment carrying the marker, and posts only when there is none. The body is an aggregate over *every* conclusion about that subject — two missing sections are one comment naming both, not two comments racing to be the last word, which is what this replaces. Sorted and deduplicated, so an unchanged standing writes identical bytes and "nothing changed" is a comparison rather than a guess. - The comment also names its `repository` and `index`. The old one named neither: as declared it was unpostable. - A push is not a subject and gets no comment. There is nowhere to put one, and inventing somewhere is worse than saying nothing. The aggregate is a second `ascent!` program, `Standing`, run after the rules — an aggregate says what it means only over a relation that has stopped growing, and inside the rule set `conclusion` is still being derived. Same shape as the substrate's own dispatch-then-resolution. ## Out of scope - **The label's name.** It stays `curated`. `lar.ad` carries the same meaning as `Reviewed/Curated`; unifying the two across the estate is a decision for whoever owns both, not a side effect of this file. - **The forge's own default merge style.** It stays `rebase`, still upstreamed from `lar.ad/flake-ops`'s `app.ini`. If "squash unless curated" should also be what a human gets clicking merge unaided, that is a change to the instance default and belongs in flake-ops — made here it would only disagree with the instance and fail the check that exists to catch exactly that. - **Performing any of it.** Comments, merges and updates are still declared and never executed. - **Creating the label on the forge.** This declares it; reconciling stays the nixops4 provider's, per the ruling at the top of `nix/forgejo.nix`. ## Boundary `nix/forgejo.nix` (the label, and rendering it), `nix/policy.nix` (reads it; two new assertions), `flake.nix` (one check), `src/policy.rs` (the standing comment), `README.md`. No change to standing, protected refs, required sections, the merge styles themselves, mirrors, or the NixOS module. ## Acceptance Verified locally, not argued: - **`nix flake check` passes**, including the new label check and the module eval. - **35 tests pass** (was 31 — five added, one replaced), and `cargo clippy --all-targets -- -D warnings` is clean. - `nix build .#policy` still renders `curated_label: "curated"`, so `policy/ting.json` is unchanged and the drift check stays quiet. ## Depends on lar.ad/jostoph-rs#5 Against the **currently pinned** substrate `nix flake check` **fails** here, correctly: ``` every_finding_lands_in_one_comment ... FAILED left: 2, right: 1 ``` One comment asked for by two findings is one comment only once `resolve` keeps the carried-effect set a set — which is jostoph-rs#5. With `--override-input jostoph-rs path:…` on that branch, all checks pass. So: land jostoph-rs#5, let the mirror carry it, `nix flake update jostoph-rs`, then this. Writing this is also what surfaced the other half of that PR: an approval from a login the estate does not recognize was denied *and* had its merge declared anyway. Authored as `agent.odin`.
Three things the owner asked for, and the first two are one thing.

The curated label is DECLARED now. nix/forgejo.nix carries it as org-level
desired state beside the repos and the webhook, and nix/policy.nix reads its
name from there instead of spelling it a second time — the same coupling
mergeStyles already has, and for the same reason. A rule that reads a label
the estate never creates cannot take that branch and cannot say so, because
an absent label and a withheld one are the same absence. Ting carries no
labels at all today, so the merge rule has only ever been able to declare
squash. Eval now fails if policy names a label forgejo.nix does not declare,
and a flake check fails if the label is declared without a description: a
bare label tells a reviewer nothing about what asserting it commits them to.

Squash when it is absent is now pinned rather than incidental — an eval
assertion, because the uncurated style is the one that applies when nobody
asserted anything, and a default that drifts is a default nobody chose.
Curated stays rebase, and still comes from the repos' own allow-set.

And the steward answers each subject on ONE comment, kept current. Every
issue and pull request it rules on gets a comment carrying a marker, and the
declaration asks to be upserted: edit the one with the marker, post only if
there is none. The body is an aggregate over every conclusion about that
subject, so two missing sections are one comment naming both rather than two
comments racing to be the last word — which is what this replaced. Sorted,
so an unchanged standing writes identical bytes and "nothing changed" is a
comparison rather than a guess. A push is not a subject and gets none.

That aggregate is a second ascent! program run after the rules. An aggregate
says what it means only over a relation that has stopped growing, and inside
the rule set conclusion is still being derived — same shape as the
substrate's own dispatch-then-resolution.

Depends on lar.ad/jostoph-rs#5: one comment asked for by four findings is
one comment only once resolve deduplicates carried effects. Against the
pinned substrate the new test fails, correctly.
Owner ruling, 2026-08-11: one name across the estate. lar.ad already
carries this label on bifrost's forge-conformance root, and a label means
"the approver vouched for this history" — a meaning spelled two ways is
two meanings the moment anyone reads across both namespaces, and the
steward reads across both.

The description is lar.ad's verbatim, for the same reason: it is the
sentence that tells an approver what applying it commits them to, and two
wordings would be two commitments.

policy/ting.json regenerated, so the drift check stays quiet. The test
fixture and its payloads carry the real name now too — a fixture that
spells the label differently from the forge tests the rule against a
world that does not exist.
larandar deleted branch tango/curated-and-standing 2026-08-11 20:13: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.

Blocks Depends on
You do not have permission to read 1 dependency
Reference
Ting/Jostoph!20
No description provided.