• 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