From 34a027e17b4a0c57f64f874762c33db64ffe0b1d Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Wed, 12 Aug 2026 23:30:29 +0200 Subject: [PATCH] README: what the engine provides Co-Authored-By: Claude Sonnet 5 --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a67a79 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# portal + +The question runtime behind [uhhm.no](https://uhhm.no). Every page, +form, review desk, and state machine it serves is declared in the +[`questions`](https://project.uhhm.no/uhhm/questions) content repo — +this codebase is the engine that renders, enforces, and records, and +it special-cases none of it. + +## What the engine provides + +- **Content-driven pages** (`src/content.rs`, `src/app.rs`): YAML + loaded from Gitea at boot and hot-swapped on a NATS reload signal; + a bad push keeps the last-good content serving. A page's `id` is + its URL; `qualifies` gates it to a Kanidm group. +- **State machines as content** (`src/aggregates/`): `aggregates.yaml` + declares each bucket's states and legal transitions; the engine + replays a record's event history and refuses undeclared moves, with + optimistic concurrency (CAS on the event log's sequence) against + racing decisions. An empty history reseeds from the KV projection, + so wiping the event stream strands nothing. +- **Durable events + projections** (`src/events/`, `src/answers.rs`): + every submission and decision appends to a JetStream event log and + projects into a NATS KV bucket pages read back; every one also + publishes on `portal.answers.submitted` for automations (n8n) to + react to. +- **Answer chains** (`src/chain.rs`): each submission hashes its + parent(s), so a visitor's path through the questions is a verifiable + lineage; `?chain=` links carry it, and self-service transitions + (unsubscribe) authorize by holding one. +- **Live resources** (`src/resource.rs`): content can pull a KV + bucket, Gitea starred/org repos, or any public JSON URL (SSRF + fail-closed), reshaped by a content-declared `jq` filter. +- **Review desks**: any Kv resource with `transitions` renders rows + with per-state action buttons and one shared confirm per + alternative — owners walk records through their graphs without + bespoke UI per bucket. + +## Binaries + +- `portal` — the server (Leptos SSR + hydrate, Axum underneath). +- `question_lint` — headless content validation, run by the + `questions` repo's CI against a prebuilt copy this repo's deploy + publishes; also works offline: `question_lint --path `. + +## Development + +```sh +cargo leptos build # full app (server + wasm) +cargo test --features ssr # engine tests +cargo build --features ssr --bin question_lint +``` + +Runtime configuration is env vars (see `src/main.rs` and +`.gitea/workflows/deploy.yml`): `NATS_URL`, `CONTENT_REPO`/ +`CONTENT_BRANCH`, Kanidm OIDC (`KANIDM_URL`, `OAUTH2_CLIENT_*`), +optional `GARAGE_*` for uploads, `GITEA_API_TOKEN` for authenticated +resource pulls, `AUTOMATION_READ_TOKEN` for the automation KV read +endpoint. + +## Deploy + +Pushing `main` triggers `.gitea/workflows/deploy.yml` on the +bare-metal runner: release build, ship to +`/srv/app/uhhm-portal/releases/`, flip the `current` symlink, +restart `app@uhhm-portal`, reload Caddy. Content changes never come +through here — they hot-reload live from the `questions` repo.