A person stays in dialogue with their case
Five things a building authority needed and portal could not say,
found by building one and replaying its cases through the engine.
The one that mattered: a submitter could only ever act on a record
nobody had touched. `access.rs` pinned a self_transition's from-state
to `open`, though the runtime had always passed the real state and
asked. So the applicant written to for a missing drawing could not
send it, and nobody could withdraw once anything had happened.
`self_transition` now takes `from: [state, ...]` and compiles one rule
per state; omitted it still means the initial state alone, which is
what every unsubscribe link already meant.
Then the four beside it. A record can belong to another record
(`type: record`, `of:`), with `where: { field, equals }` on a listing
as the other half, so an application's page lists the objections that
name it. A public view can be narrowed to the states worth showing,
so a register that is public by law is public about what was decided
and not about what was withdrawn. A state can grant a group, so an
approval that means membership needs no second step. And a state can
be moved by a clock (`after: { days, to }`, src/deadlines.rs) - held
to the same declared edge as a desk button, firing the same mail,
publishing the same event.
Also: the README is a README again, and everything that was crowding
it is in docs/ - architecture, content reference, state machines,
access, operations.
95 tests, up from 82.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
26c1762dfa
commit
f18d1a5181
@@ -0,0 +1,80 @@
|
||||
# Operations
|
||||
|
||||
Portal does not deploy itself. It publishes versions; each site
|
||||
decides when to take one.
|
||||
|
||||
## Cut a release
|
||||
|
||||
```sh
|
||||
cargo release patch # or minor / major
|
||||
```
|
||||
|
||||
That bumps `Cargo.toml`, tags `v<version>` and pushes. CI
|
||||
(`.gitea/workflows/publish.yml`) reacts to the tag, builds once, and
|
||||
attaches `portal-v<version>.tar.gz` (`portal`, `iris`, `hash.txt`,
|
||||
`site/`) to the Gitea release. Plain pushes to `main` only run the
|
||||
tests: nothing reaches production from this repo.
|
||||
|
||||
The same job re-pins, tests and tags [iris](https://project.uhhm.no/uhhm/iris)
|
||||
to match, so a portal change to anything the lint reads fails the
|
||||
release rather than the next content push.
|
||||
|
||||
Before cutting, move the `Unreleased` lines in `CHANGELOG.md` under a
|
||||
heading for the new version. That section becomes the release notes,
|
||||
and the table at the end of the file records which version each site
|
||||
runs.
|
||||
|
||||
Pkg files are content-hashed (`hash-files = true`, instances run with
|
||||
`LEPTOS_HASH_FILES=true`), so a freshly served page can never pair a
|
||||
stale cached bundle with new wasm.
|
||||
|
||||
## Roll it out
|
||||
|
||||
In the content repo, one line of `.gitea/workflows/deploy.yml`:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
PORTAL_RELEASE: v0.4.0
|
||||
```
|
||||
|
||||
Its CI downloads the pinned artifact, ships
|
||||
`/srv/app/<instance>/releases/<tag>`, flips `current`, rewrites the
|
||||
instance env from that repo's own Actions variables and secrets,
|
||||
restarts `app@<instance>` and refreshes the Caddy route. Every rollout
|
||||
is a commit, so reverting a bad version is `git revert` and a push.
|
||||
|
||||
Sites upgrade independently and may pin different versions.
|
||||
|
||||
Content changes never come through any of this: they hot-reload live
|
||||
from the content repos over NATS.
|
||||
|
||||
## Add a site
|
||||
|
||||
A new content repo with a copy of an existing `deploy.yml` (change
|
||||
`INSTANCE`, the port and the domains), Actions variables
|
||||
(`KANIDM_URL`, `OAUTH2_CLIENT_ID`, `PUBLIC_URL`, `SITE_NAME`) and
|
||||
secrets (`NATS_URL`, `OAUTH2_CLIENT_SECRET`, `PORTAL_GITEA_API_TOKEN`
|
||||
— Gitea reserves the `GITEA_` prefix for secret names), a Kanidm
|
||||
OAuth2 client, and DNS. `site.yaml` handles all branding; no portal
|
||||
change is needed.
|
||||
|
||||
## Environment
|
||||
|
||||
Runtime configuration is environment variables; `src/main.rs` is the
|
||||
authority and each content repo's `deploy.yml` holds the production
|
||||
values.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `NATS_URL` | events, projections, content reload, access questions |
|
||||
| `CONTENT_REPO`, `CONTENT_BRANCH` | the repo that is the site |
|
||||
| `KANIDM_URL`, `OAUTH2_CLIENT_ID`, `OAUTH2_CLIENT_SECRET` | sign-in |
|
||||
| `KANIDM_API_TOKEN` | invites and `grants:`; unset, neither happens |
|
||||
| `PUBLIC_URL`, `SITE_NAME` | what the site calls itself in links and mail |
|
||||
| `GITEA_API_TOKEN` | authenticated resource pulls |
|
||||
| `AUTOMATION_READ_TOKEN` | the automation KV read endpoint |
|
||||
| `GARAGE_*` | uploads |
|
||||
| `MAPBOX_TOKEN` | map tiles, proxied; unset, maps are off |
|
||||
|
||||
Mail is delivered by [gdo](https://project.uhhm.no/uhhm/gdo), a
|
||||
separate unit on the same host reading the same NATS.
|
||||
Reference in New Issue
Block a user