Establish the Ting/Jostoph integration perimeter #2
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Jostoph!2
Loading…
Reference in a new issue
No description provided.
Delete branch "perimeter/steward"
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 #1. Depends on lar.ad/jostoph-rs#2.
In scope, as built
manifest,check(exits 1 on a denial, so it composes with a shell gate),serve.nix/forgejo.nixrenders the webhook and a reconcile script.nix/module.nix.Rules are Datalog; parameters are Nix
src/policy.rsis anascent!program.nix/policy.nixholds what the estate requires — sections, protected refs, stewards — crystallized into a JSON derivation and baked in withinclude_str!.nix/policy.nixsrc/policy.rsNeither can be done by accident in the other's name.
policy/ting.jsonis the checked-in development default;nix flake checkfails if it has drifted from the derivation, so the two cannot silently diverge.The program hands its output relations back verbatim — precedence is the substrate's, in Datalog, and is not restated here.
Currently required: an opened issue or PR declares In scope, Out of scope, Boundary, Acceptance (an empty body is its own denial);
refs/heads/mainis not pushed to directly by anyone; an unrecognized actor is gated.Notable decisions
listendefaults to loopback./v1/auditis a record of everything the steward has been told.vendor/jostophis a path dependency populated from the flake input, not a Cargo git dependency, socargo build --locked --offlineworks in the Nix sandbox with no forge credentials.Verification
19 tests (policy, webhook signature handling, event translation).
nix flake checkruns the build, the package's tests, apostInstallmanifest check, a NixOS module evaluation producing both units, and the policy-drift check. End-to-end: a real server accepted a signed delivery, denied it with four named missing sections and four declared (unperformed)forgejo.commenteffects, refused an unsigned one with 401, and served it all back from/v1/audit.Known gap
No
flake.lockis committed.lar.ad/jostoph-rsis private, so Nix cannot fetch the input without a credential and the lock cannot be generated. The README documents the~/.config/nix/netrcsetup; the lock should be committed once that is in place (or once the substrate repo is readable). Everything here was verified with--override-input jostoph-rs path:../jostoph-rs.🤖 Generated with Claude Code
Standing was a flat list of logins. It is now held by groups and flows downward from steward_root: stewards └── estate <- larandar └── agents <- agent.odin nix/policy.nix declares only the direct nesting; `contains` is tagged #[ds(ascent_byods_rels::trrel)], so agents-under-stewards follows from the two declared edges. No closure rule is written, so none can be forgotten when the hierarchy grows. Dropping the tag makes standing_reaches_through_nested_groups fail, so the tests distinguish it. Standing flows one way: a member of a group that merely contains the root holds nothing, and a group outside the hierarchy confers nothing. A BYODS-backed relation cannot be assigned from Rust, so the declared edges arrive in the plain group_nests relation and are copied in by one rule. nix/policy.nix refuses at eval time to reference a group it has not declared -- otherwise a typo would silently strip someone's standing rather than fail. Policy schema bumped to 2. The denial for a missing section and the comment explaining it are now one rule with two heads over a shared `let reason` binding, so the (key, reason) join that ties an effect to its conclusion holds by construction instead of because the same string was written twice. Co-Authored-By: Claude Opus 5 <[email protected]>@ -0,0 +51,4 @@# absent and updates it if present, and never deletes hooks it did not create.# A deployment tool that removes unrecognized webhooks is one bad eval away# from severing an unrelated integration.reconcile =The tool of the trade in the estate are not impure nix but either nixops4 or deploy-rs
Fixed in
fd8d47a. You were right, and reading the law made it clearer than "not impure nix" alone: there was no seat for that script anywhere.Removed: the activation-time
curl/jqreconcile script, itsjostoph-forgejo-webhooksystemd oneshot, and thetokenFile/reconcileWebhookoptions. The deployment holds no Forgejo token at all now.Kept:
nix/forgejo.nixas declaration only. Nothing reaches the network, nothing mutates the forge. The rendered spec lands at/etc/jostoph/forgejo-webhook.json, so the host can be asked what it believes Forgejo ought to be sending it — and that is the end of it.Where reconciliation actually belongs, per the layering in flaky-mesh#22 and law 12:
just forgejo, reconciled forever after.So the honest answer to "nixops4 or deploy-rs" turned out to be neither — this belongs to the steward, and the issue asked for the webhook to be declared through the overlay, which is exactly what is left.
A
nix flake checkassertion now fails the build if any unit this module generates grows acurlor aFORGEJO_TOKEN, so the boundary cannot erode quietly.Three things I found while reading the law that are not addressed by this commit, flagged rather than silently fixed:
This PR violates law 9's branch naming. It is
perimeter/steward, nottango/1-<slug>, and jostoph-rs#2 isperimeter/substrate. My miss — the work was dispatched after ratification, so the transition clause does not cover it. Renaming means closing and reopening both PRs (Forgejo cannot retarget a head), which costs this review thread. Happy to do it on your word.Both PRs target
mainbecause neither repo has afastlane. Same position agent.teyla recorded for flaky-mesh PR #23 — "could only have targetedmain, and did". NeitherTing/Jostophnorlar.ad/jostoph-rshas had the law-12 birth recipe run: no fastlane, no law-13 labels, andTingdoes not appear in law 8's org/seat table at all.A possible specification conflict, worth a ruling. Issue #1 puts a "systemd webhook listener" in scope, and that is what I built. But flaky-mesh#22 comment 336 ratified the opposite for the steward: "the steward is a polling conformance worker, not a webhook listener — all its transitions are derivable from API state, and a poller that dies re-derives on restart (a dead listener silently misses events)." If Jostoph is that steward, the listener is the wrong shape and the rationale against it is exactly right. If Ting's steward is a different animal, no conflict. Under law 13 a spec miss is a new linked ticket and never a bounce, so I have not touched it — say the word and I will file one.
Correction to my last comment — I over-read law 12.
just forgejopredates the nixops4/deploy-rs ruling, so citing it as the seat for this was wrong. Reconciliation is back in64e171b, as deploy-rs.Built on
deploy-rs.lib.${system}.activate.custom. Because the call is idempotent the recipe is always call it, not a state machine that tries to remember what it did last time.deploy --dry-activatewires through to--dry-run, which reports what would change and touches nothing.What did not come back: the systemd oneshot, and any forge token in the system closure. The NixOS module still only declares — renders the spec to
/etc/jostoph/forgejo-webhook.jsonand stops — and thenix flake checkassertion still fails the build if a unit it generates grows acurlor aFORGEJO_TOKEN.Identity is not created here, per the layering: the token and the webhook secret are nixops4's to mint and arrive as files.
secretFileis deliberately the same file the service reads, so the two cannot drift apart. The secret is spliced in at run time rather than baked into a store path, and a check asserts it never reaches the store.Forgejo has no upsert for hooks, so create-or-patch is the minimal idempotent form. Hooks it did not create are never touched, and a check fails the build if a
DELETEever appears in the script.Verified
--dry-runagainst the live forge with a real token: it authenticated, listed the hooks onTing/Jostoph, reportedwould create webhook on Ting/Jostoph, and mutated nothing.writeShellApplicationruns shellcheck, and a check asserts the profile builds and is executable — so a broken script failsnix flake checkrather than mid-deploy.The three items at the end of my previous comment still stand, unchanged and still yours to call: the law-9 branch names, the missing
fastlaneon both repos, and whether the webhook-listener shape conflicts with comment 336's polling steward.52bd0a92cato30bddb6f4e