- Rust 75.7%
- Nix 23.5%
- Shell 0.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <[email protected]> |
||
| fixtures/lifecycle/v1 | ||
| nix | ||
| openspec | ||
| policy | ||
| src | ||
| .gitignore | ||
| .rumdl.toml | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTRACT.md | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| resolve-new.rs | ||
Jostoph
Ting's Forgejo steward. Jostoph hears forge events, applies declared law, records the ruling, and performs only effects already named by that ruling.
It does not deliberate. Policy is Datalog, parameters are Nix, and every answer leaves evidence.
Built on jostoph-rs, consumed through its public midgard mirror. The substrate supplies service lifecycle, deduction, dispatch, and resolution. This repository supplies Ting's policy, Forgejo integration, and deployment.
The steward's circuit
| Step | Responsibility | Home |
|---|---|---|
| Hear | Verify a Forgejo delivery and extract facts | src/forgejo.rs |
| Rule | Apply Ting's Datalog policy | src/policy.rs |
| Remember | Append the verified delivery record | src/record.rs |
| Answer | Resolve conclusions and retain the audit trail | src/audit.rs, jostoph-rs |
| Act | Reconcile only a declared effect | src/reconcile.rs, src/conformance.rs |
The answering service never acts. It can declare an effect such as a comment or merge, return it, and audit it. One-shot executors carry separate credentials and perform only the effect they were given.
Today, jostoph conform --apply can reconcile the declared repository and
organisation-label state. Webhook provisioning belongs to the Forgejo nixops4
provider. Merge, comment, label-transition, and base-push effects remain
declared but unperformed; their executor is tracked in
#26.
Perimeter
In scope
- The Jostoph CLI and answering service.
- Ting's rules and their Nix-crystallized parameters.
- Forgejo webhook verification, delivery records, and audit trail.
- Report-first conformance of repository settings and organization labels.
- Nix packaging, module, and service wiring.
Out of scope
- General service substrate: that belongs to
lar.ad/jostoph-rs. - Agentic judgment or an LLM in the steward loop.
- Repository creation, deletion, or undeclared Forgejo mutation.
- Performing ruling effects until their executor lands.
Policy
Rules live in src/policy.rs. Their parameters live in nix/policy.nix, which
renders the policy baked into the binary. policy/ting.json is the checked-in
development value; nix flake check fails when it drifts from the rendered
policy.
Current gates require:
- Open issues and pull requests declare In scope, Out of scope, Boundary, and Acceptance.
- Direct pushes to
refs/heads/mainare denied. - Unrecognized actors are gated.
- A pull request carries one or more standalone
Closes #nlines, or aCloses nothing:reason. - An author's own approval does not count.
- An approval names the reviewed
head_commit_id; drafts cannot merge.
Standing flows through groups rooted at steward_root; it is not a flat login
list. nix/policy.nix declares group membership and nesting, while the Datalog
relation closes the hierarchy transitively.
CLI
jostoph manifest
jostoph --policy candidate.json check event.json
jostoph check event.json
jostoph check --webhook --kind issues body.json
jostoph serve --listen 127.0.0.1:8420 \
--webhook-secret-file /run/secrets/hook \
--audit-log /var/lib/jostoph/audit.jsonl \
--record-log /var/lib/jostoph/records.jsonl
jostoph roster --api https://jo.et0.pw/api/v1 \
--token-file /run/secrets/ro-token --repo Ting/Jostoph
jostoph kinds --audit-log /var/lib/jostoph/audit.jsonl
jostoph replay --record-log /var/lib/jostoph/records.jsonl
jostoph conform --spec /etc/jostoph/forgejo-repos.json \
--api https://jo.et0.pw/api/v1 --token-file /run/secrets/token
jostoph conform --apply --spec /etc/jostoph/forgejo-repos.json \
--api https://jo.et0.pw/api/v1 --token-file /run/secrets/token
check exits 1 on denial. roster, kinds, and report-mode conform exit
non-zero on drift. conform --apply performs exactly the reported edits and
observes the forge again; the live forge, not the request log, is proof of
convergence.
--policy FILE swaps the compiled-in parameters for a trial before a policy
change is crystallized into the Nix derivation.
replay rebuilds the fact view from verified delivery records. Replaying the
same records with the same binary and parameters produces the same view.
Duplicate delivery_id values append nothing.
The lifecycle audit
Jostoph also hears the Forge identity lifecycle (#103): the declaring
authority, Kanidm, OpenBao, Vedanta, the Forge credential adapter, and
Forgejo each report their own outcome in one non-secret envelope
(src/lifecycle/envelope.rs; the same contract as fixtures and a JSON
Schema under fixtures/lifecycle/v1/). Jostoph keeps each layer's word
verbatim, joins the layers by stable identifiers, and reports what is
missing, contradictory, or drifted. It never mints, rotates, revokes,
invalidates, activates a grant, or alters membership: no module under
src/lifecycle/ holds a client with which to do so.
jostoph serve ... \
--lifecycle-registry /etc/jostoph/lifecycle-emitters.json \
--lifecycle-chain /var/lib/jostoph/lifecycle.jsonl \
--lifecycle-quarantine /var/lib/jostoph/lifecycle-quarantine.jsonl
jostoph lifecycle ingest --registry R --chain C --quarantine Q events.jsonl
jostoph lifecycle view --chain C --quarantine Q
jostoph lifecycle drift --chain C
jostoph lifecycle acceptance --chain C --quarantine Q --attestations A.json
With the registry, serve also answers POST /v1/lifecycle/events. Every
document — delivered or read from a file — passes the same gauntlet:
secret-shaped paths are quarantined by path before the parse; the closed
envelope is validated; the claimed component must be a declared emitter
whose key verifies the document's own signature; the event type must be
one that layer owns and the registry grants; then the append. A replay of
a stable event id appends nothing and says so; the same id with different
content is a conflict the original outlives.
view is grant -> lease -> operation -> token -> resource event, every
layer under its own name, missing layers named, contradictions carried
with the ids they came from. drift reports C01–C08 and
revocation_pending, each naming the affected layer and the owning
compensation path. acceptance renders the canonical production
acceptance facts and old-path exit conditions as pass, fail, pending,
missing (naming the source), contradictory, or attested — never as pass
without the event ids that earned it. All three exit non-zero while a
finding stands, and none contains a credential value.
Deployment
{
inputs.jostoph.url = "git+https://jo.et0.pw/Ting/Jostoph.git";
imports = [ inputs.jostoph.nixosModules.default ];
nixpkgs.overlays = [ inputs.jostoph.overlays.default ];
services.jostoph = {
enable = true;
listen = "127.0.0.1:8420";
webhookSecretFile = config.sops.secrets.jostoph-webhook.path;
forgejo = {
url = "http://127.0.0.1:8420/webhook/forgejo";
repositories = [ "Ting/Jostoph" ];
};
};
}
This deployment currently belongs on the Forgejo host. The instance permits
webhook delivery to loopback, not arbitrary private or external addresses. The
listener therefore stays on 127.0.0.1; moving it requires an explicit
Forgejo-side policy change.
Deliveries must carry a valid X-Forgejo-Signature. There is no unsigned mode.
The webhook secret is minted and rotated by the deployment and reaches services
through systemd credentials.
The same listener serves /healthz, /v1/manifest, /v1/evaluate,
/v1/audit, and POST /webhook/forgejo. Keep it on loopback unless a reverse
proxy deliberately exposes only the intended paths; otherwise the audit and
evaluation surfaces would travel with the webhook.
services.jostoph.forgejo declares the expected webhook; it does not provision
it. Forge-side resources with assigned identities remain the Forgejo nixops4
provider's responsibility. Repository and organization-label desired state is
rendered by:
nix build .#forgejo-repos
Conformance is add-and-repair, never delete: an absent declared repository and an undeclared live resource are findings for a person.
Credentials
The build needs no Forgejo credential. jostoph-rs is pinned through the
anonymous public mirror, and Cargo consumes the flake-populated vendor/jostoph
path offline.
A credential is needed only to work directly against the private canonical substrate:
install -Dm600 /dev/null ~/.config/nix/netrc
printf 'machine jo.et0.pw login <user> password <forgejo-token>\n' >> ~/.config/nix/netrc
For a local checkout, --override-input (below) needs no credential at all.
The doctor
The estate's checkup reaches a repository as a devenv module, not as a file to
fetch. There is no bootstrap step and nothing to keep in sync by hand: a repo
declares Jostoph as an input, imports jostoph/nix/ting, and the whole
ting:* task surface — plus openspec, nu and gum at the revisions
Jostoph locks — arrives with it.
# devenv.yaml
inputs:
jostoph:
url: git+https://jo.et0.pw/Ting/Jostoph
flake: false
# devenv.nix
imports:
- jostoph/nix/ting
devenv update is the sync. Which doctor a repo ran is then a fact recorded in
its devenv.lock, readable afterwards, rather than a race against whatever was
on main that minute — and a local experiment is an ordinary input override
instead of an edit the next run silently overwrites.
The checks run before the shell is entered, at most hourly and whenever either
lockfile moves. Most report; a few heal. What the doctor will not do is edit a
tracked file to impose a choice that is yours — governance in AGENTS.md, a
default branch on the forge, a devenv input pointed at a public mirror are all
named and left to you.
This repository imports ./nix/ting from its own working tree rather than a
published copy of itself, so an edit to the doctor takes effect in the shell
that is editing it.
Development
direnv allow # mandatory once per checkout; loads the default flake shell
cargo test
cargo clippy --all-targets -- -D warnings
nix flake check # build + tests + module eval + policy-drift check
vendor/jostoph is a path dependency populated from the jostoph-rs flake
input rather than a Cargo git dependency, so cargo build --locked --offline
works inside the Nix sandbox with no forge credentials.
To work against a local substrate checkout:
nix flake check --override-input jostoph-rs path:../jostoph-rs
The implementation campaigns remain on the forge: effect execution #81 and trusted delivery #83. Reusable failures and the rules they changed live in the incident postmortems.