• v0.5.4 48a1a58d01

    v0.5.4
    Test / test (push) Successful in 55s
    Publish release / publish (push) Successful in 3m10s
    Stable

    gitea-actions released this 2026-09-29 19:11:13 +00:00 | 250 commits to main since this release

    • Accounts the content mandates, made at start (src/seed.rs). Every person a page names as responsible gets a Kanidm account if they have none, and a mail in the site's language saying they are listed as responsible for asking that question, with the link to sign in; RESPONSIBLE_GROUP puts them all in one group (people who already had an account are added without a mail). SEED_ADMIN_EMAIL (and SEED_ADMIN_NAME, SEED_GROUP) invites the first person into the site's most privileged group - the group whose desk may invite into the most groups - so a site with its own Kanidm has someone to invite everyone else. Both happen once per address and group (portal_seed), side by side, and each tries again a minute apart, twenty times, while Kanidm comes up. The responsible are gone over again whenever the content changes (portal.content.reloaded, published once new content is in place), so a new page's person is set up without a restart - and someone the content has stopped naming leaves RESPONSIBLE_GROUP in the same pass, if portal is who put them there.
    • An invite is a case that keeps itself current (portal_invites: open, expired, done). Signing in closes a person's invites; a sweep every ten minutes moves one whose link ran out unused to expired, and takes the link off the record. From expired a desk may send it again: the move back to open makes a fresh link and mails it. Content offers that with { from: expired, to: open } on the list of invites. The invites portal makes itself at start are on file with the rest.
    • Fixed: an invite mail said the link worked "until" and then nothing. Kanidm 1.11 gives a link's end as seconds since the epoch, and portal read only the older RFC 3339 string.
    • Fixed: a search by mail that Kanidm failed to answer (a 5xx) was read as "not permitted", and the invite went on to match by username - which could give someone with an account under another name a second one. Only a refusal is read that way now.
    • A page's responsible person, signed in, may suggest a change to it: a card at the foot of their own pages, checked on the server against the page's responsible.contact, published on portal.edits.suggested, and mailed to the site's inbox with them as reply-to. It is a case in the built-in portal_edit_suggestions bucket - open, discussed, done or declined - so a desk that lists the bucket takes it up and settles it, the person who made it is mailed at each move, and the card shows them what became of what they sent before. The session now carries the signed-in person's email.
    • The mail to someone named responsible names the first three questions and counts the rest; on a site where one address answers for every page it used to list them all.
    • Fixed: an invite of someone new failed on a Kanidm set up with the onboarding account alone. Portal created the person, then set their address in a second call, and idm_people_on_boarding may create but not modify: 403. The address now goes in with the person.
    Downloads
  • v0.5.3 46d19e6d9d

    v0.5.3
    Test / test (push) Successful in 51s
    Publish release / publish (push) Successful in 2m59s
    Stable

    gitea-actions released this 2026-09-29 11:30:22 +00:00 | 258 commits to main since this release

    • A way down the tree from every page: an arrow fixed at the left edge, half-way up, and the same link in words at the foot ("Tilbake til starten", "Back to “What came in?”"). It goes to the nearest page below the current one that the visitor may open, taking the path apart a segment at a time, and to the front door when none is; the front door has none. Content no longer needs a "back" alternative, and a visitor who went up a branch that turned out not to apply is never stranded.
    Downloads
  • v0.5.2 45240b7cab

    v0.5.2
    Test / test (push) Successful in 57s
    Publish release / publish (push) Successful in 3m1s
    Stable

    gitea-actions released this 2026-09-28 16:09:28 +00:00 | 263 commits to main since this release

    • The rate limiter answered 500 to any request that arrived without X-Forwarded-For. It reads the caller from that header or X-Real-IP and falls back to the socket, and 0.5.0 served the app without connect info, so there was no socket to fall back to and no key to rate-limit by. Through Caddy every request carries the header and the site was fine; a health check on localhost got a 500, which is how the first deploy of 0.5.1 failed.
    Downloads
  • v0.5.1 6ee6551400

    v0.5.1
    Test / test (push) Successful in 58s
    Publish release / publish (push) Successful in 3m2s
    Stable

    gitea-actions released this 2026-09-28 15:48:37 +00:00 | 264 commits to main since this release

    • A self_transition that names no from means the bucket's own initial state, not the word open. 0.5.0 read the literal, which is only right for a bucket whose records start there: redoal's watchers arrive at pending and confirm themselves into open, so the rule compiled as open -> open and the confirm link in the mail could never match. Wrong the same way before 0.5.0, and now visible because the lint checks the edge exists.
    Downloads
  • v0.5.0 68471969b5

    v0.5.0
    Test / test (push) Successful in 1m0s
    Publish release / publish (push) Successful in 6m47s
    Stable

    gitea-actions released this 2026-09-28 14:46:50 +00:00 | 265 commits to main since this release

    • Sessions live in NATS KV (src/sessions.rs), not in memory. Every restart used to sign out every desk on the site being deployed; they now survive it, in the JetStream that is already under everything else - no new service and no new store to run. The bucket's max_age collects what nobody returns for, and a record's own expiry is what answers a load.
    • Fixed: the JetStream integration test asserted a record's projected state was one a desk move had never written to it, so it failed whenever it actually ran. test.yml has been red since 0.3.47 while publish.yml stayed green, which is how 0.4.0 shipped past it.
    • A file can come back out. GET /attachment/{bucket}/{record}/{field} streams what a type: file field stored, reading the object key out of the record rather than taking one from the caller - so a file is only reachable through a record somebody may see, and a key that leaks off a desk is a link to nothing. Before this, uploads were one-way: a desk showed the key as a string and there was no way to open it.
    • Rate limiting, per caller IP, outside everything else: a burst of 30 refilled one every two seconds (PORTAL_RATE_BURST, PORTAL_RATE_SECONDS; either 0 turns it off). A form-filling visitor never reaches it; a script does. There was none at all before.
    • /upload asks the policy, like every other way in. It asked nothing: any caller who could reach the host could put 20 MB in the bucket, as often as they liked, and a site whose forms are public is a site whose upload route is public. It now takes the question and alternative the file is being attached to, refuses a page that declares no type: file field or an alternative that is closed, and asks Submit on that page - the same question the submission itself will ask - before a byte is written. Latent until an instance had storage configured; the first one now does.
    • A submitter stays in dialogue with their case for as long as it runs. self_transition takes from: [state, ...], and the access policy compiles one rule per state named; it defaulted to the initial state alone and was written that way in the compiler, so an applicant written to for a missing drawing could not send it, and nobody could withdraw once anything had happened. Omitted, it still means the initial state alone.
    • A record can belong to another record. type: record with of: <bucket> stores the parent's id, which arrives on the link rather than being typed; a submission naming a record that is not there is refused. The other half is where: { field, equals } on a Kv resource, so an application's page can list the objections that name it.
    • A public view need not publish the whole bucket. states: [...] on a Kv resource shows only those, for one record as well as a listing - a register that is public by law is public about what was decided, not about what was withdrawn. The desk over the same bucket is a different spec and still sees all of it.
    • A state can grant a group: grants: <group> beside its event, so an approval that means membership does not also need somebody to remember to invite them afterwards. The lint requires the forms that fill such a bucket to collect a required name and email, because by the time a desk presses approve there is nobody left to ask.
    • A state can be moved by a clock: after: { days, to }, swept hourly, measured from the record's last move (src/deadlines.rs). A deadline may only do what a person could have done - after.to is held to the same declared edge as a desk button - and the move fires the same mail and publishes the same event, stamped deadline.
    • Documentation moved out of the README into docs/: architecture, content reference, state machines, access, operations.
    • A page says nothing until it has something to say. The Suspense fallback on a question was the whole served page - no header, no wordmark - so the first and only thing a visitor or a crawler read was the word "loading…", which is true and is not about them. It now holds the space and stays quiet, with aria-busy for a reader that would otherwise find an empty document.
    • Gone: the gitea_org_repos resource kind, which no site ever named. gitea_starred and gitea_releases stay.
    Downloads
  • v0.4.0 26c1762dfa

    v0.4.0
    Test / test (push) Failing after 53s
    Publish release / publish (push) Successful in 3m14s
    Stable

    gitea-actions released this 2026-09-24 19:26:57 +00:00 | 271 commits to main since this release

    • A field can be a place: type: location puts a pin on a map and stores {lat, lon, zoom}; a record holding one renders as a still map on its desk and in any resource card.
    • A question can be about a place: place: { lat, lon, zoom, name } is the counterpart to event: - event makes a page an occasion with a time, place makes it somewhere, draws the map under the question, and opens any location field on that page there instead of on the globe.
    • Maps are served from this origin. Portal fetches every tile and every still itself (src/maps.rs, MapLibre vendored), so a page with a map on it makes no third-party request, needs no consent banner, and the MAPBOX_TOKEN never leaves the server. Unset, the feature is simply off.
    • A reply to a case mail becomes a note on its record: whatever reads the mailbox publishes on portal.mail.received and portal appends the note, after checking the record exists and the sender is the address the record itself holds. A note never moves a case.
    Downloads
  • v0.3.47
    Test / test (push) Failing after 55s
    Publish release / publish (push) Successful in 3m48s
    Stable

    gitea-actions released this 2026-09-24 03:17:45 +00:00 | 273 commits to main since this release

    • iris: needs.yaml takes stage_words, each state in the site's own words, carried into the simulation model for the trainer's desk score. No change to portal itself.
    Downloads
  • v0.3.46
    Test / test (push) Successful in 1m7s
    Publish release / publish (push) Successful in 3m45s
    Stable

    gitea-actions released this 2026-09-23 20:18:28 +00:00 | 274 commits to main since this release

    • An invite first looks the email up in Kanidm (/v1/raw/search on mail, when the service account may read people: idm_people_pii_read) and only adds an existing account, under whatever username, to the group; no second account, no reset link.
    Downloads
  • v0.3.45
    Test / test (push) Successful in 1m3s
    Publish release / publish (push) Successful in 3m18s
    Stable

    gitea-actions released this 2026-09-23 19:24:09 +00:00 | 275 commits to main since this release

    • A mail stream that cannot be created at boot (subjects owned by an older stream) is a warning, not a failed start; 0.3.44 took the kasse sites down for two minutes on the rename.
    Downloads
  • v0.3.44
    Test / test (push) Failing after 1m3s
    Publish release / publish (push) Successful in 3m6s
    Stable

    gitea-actions released this 2026-09-23 19:15:40 +00:00 | 276 commits to main since this release

    • The mail stream is named mail (was WORMHOLE); gdo 0.1.2 matches. Delete the old stream on each NATS after upgrading both.
    Downloads