A MemoryStore meant every restart signed out every desk on the site being deployed. Bearable while a desk is three people, and not the kind of thing that gets better on its own. They live in NATS KV now: the JetStream already under the events, the projections, the content reload and the access questions. No new service, no new store, and the same bucket a second instance of a site would read if there ever is one. `create` draws a fresh id rather than writing over a session already at that key, the bucket's `max_age` collects what nobody comes back for, and a record's own expiry is what answers a load, whatever the bucket still holds. Proved the only way it can be: a store opened twice against one JetStream, which is what two runs of the binary are. Also fixed, while there: the integration test asserted a record's projected state was one that `aggregates::transition` never writes - it appends events, the desk handler updates the projection - so it failed whenever it was actually run. It now asserts what it says it does, that a mailed reply leaves the state alone. test.yml has been red since 0.3.47 while publish.yml stayed green, which is how v0.4.0 went out over it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
portal
A content-driven question engine: one Rust binary that turns a Git repo of YAML into a live site — pages, forms, review desks and state machines, with a durable event-sourced record of every answer underneath.
The engine special-cases nothing. Everything a site serves is declared in its content repo, content pushes hot-reload every running instance, and the same build serves any number of sites: instances differ only by environment variables. uhhm.no and redoal.com are two faces of the same binary from the same release artifact.
It composes with the surrounding stack rather than bundling it. Gitea hosts the content, NATS JetStream stores the events and projections, Kanidm provides identity, and anything downstream — automations, newsletters, onboarding — subscribes to the answer stream.
Documentation
Everything is under docs/:
- Architecture — what the engine is made of.
- Content reference — every key a content repo may write.
- State machines —
aggregates.yaml. - Access — the policy compiled from content.
- Operations — releasing, deploying, adding a site.
Development
cargo leptos build # server + wasm
cargo test --features ssr # tests/jetstream.rs needs PORTAL_TEST_NATS_URL
Content is linted by iris, which depends on this crate and carries the same version, so a content repo can check itself against the exact portal its instance runs:
iris check --path questions
Releasing
cargo release patch tags and pushes; CI builds the tarball and
attaches it to the Gitea release. A site takes it by bumping
PORTAL_RELEASE in its own content repo. See
operations.