Everything a site has to publish for another program to find it lives under /.well-known/, and portal had no way to put a file there: the catch-all answered those paths 200 with the app shell, so a client probing for security.txt or matrix/server got HTML where it expected a document, and could not tell a missing file from a present one. The prefix is the one place this is safe to overlay. RFC 8615 reserves it, and a question's id comes from a file name under questions/, which cannot begin with a dot - so no file a repo ships can shadow a page. The path still goes through is_safe_site_path, so it cannot climb out of the repo either, and a miss is a real 404. Types: an extensionless file under the prefix is JSON, which is what matrix/server and nodeinfo are; .mobileconfig is the Apple profile type, without which iOS downloads a profile instead of offering to install it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
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.