What Lictor 0.2 does
Lictor 0.2.0, released on 19 October 2026 with Praxis v0.63.0, serves its site and lets somebody in by invite. Everything on this page was run against that release.
Running it
From the lictor/ directory of the repository:
LICTOR_PORT=4123 LICTOR_BASE_URL=http://localhost:4123 prx run --sname lictor --no-halt
tailwind: stylesheet built posthorn mailbox on http://localhost:64064 lictor: development admin seeded -- log in as admin, password admin. This account exists in development only: any other environment refuses it. lictor listening on http://localhost:4123
Two addresses: the site, on LICTOR_PORT (4000 unless it says otherwise; this run chose 4123), and the development mailbox, on whichever port the system had free. --no-halt keeps the server running after main returns; --sname lictor makes the instance a node, which is how prx lictor invite reaches it. In development an admin, admin/admin, is seeded and announced — and any other environment refuses to start with it switched on.
Registration by invite
The beta is invite-only, and registration is reached through an invite and nothing else — there is no /register. In another terminal:
prx lictor invite Ada Lovelace
http://localhost:4123/invite/WPt3PZvpeo3FLslP6ugrKtO55X74XI7F2wkM_23G-Tc
The running Lictor makes the link: while the accounts are in memory an invite exists only in the instance that made it, so the task joins its node and asks Kether there. The link works once, and for a week.
GET /invite/<token>shows the form: a username, an email address, a password of twelve characters or more. Asking does not use the invite.POST /invite/<token>registers: *Check your mail*, and one message in the mailbox. A username taken, an address already registered, a username that is an address or a short password answer the form again with422, saying which field — and never writing the password back.GET /verify/<token>— the link in the mail, valid a day — answers *Your account is ready*. An invite or verification link used, unknown or expired answers *no longer valid* with410; a form without its CSRF token is a403.
Walked through by hand against 0.2.0: the form posted with a five-character password answered 422 with *A password needs at least 12 characters.*, an address as the username 422 with *A username cannot be an email address.*, a correct form *Check your mail*; the invite link opened again then answered 410, the mailed link *Your account is ready*, the same link again 410, and /register 404.
The development mailbox
Nothing is sent. Every mail the running Lictor would send is kept by posthorn’s development mailbox and listed, newest first, on the page prx run printed — on 127.0.0.1 only, each mail opening to its text with the verification link in it. prx posthorn --node lictor serves the same page from another terminal; the mailbox is found because it advertises itself as it starts.
Routes
The routes are data in lictor.elysium, and prx routes reads the table back:
prx routes lictor.elysium
GET / lictor.elysium/front-page GET /invite/:token lictor.elysium/invite-form POST /invite/:token lictor.elysium/register GET /verify/:token lictor.elysium/verify-address POST /theme lictor.elysium/choose-theme
Anything else is a 404. /assets/ serves the stylesheet, the seal and the icons. POST /theme takes *light*, *dark* or *system*, remembered in a cookie for a year with no JavaScript; *system* follows the browser.
Configuration
Configuration is CASC, config/config.casc, with an overlay per environment, read by cooper_prx before main runs. Each application reads its own block and nothing else’s:
#@version = 1.0
lictor_elysium {
port = "${LICTOR_PORT:4000}"
base-url = "${LICTOR_BASE_URL:http://localhost:4000}"
mail-from = "lictor@localhost"
}
lictor_kether {
}
import "${PRX_ENV:dev}/*.casc"| Kether setting | Default | |
|---|---|---|
invite-validity | 604800 | a week, in seconds |
verification-validity | 86400 | a day |
minimum-password | 12 | characters |
password-iterations | 600000 | the PBKDF2 cost of shibboleth |
The comments are left out of the file above. The development overlay names the environment and switches the admin seed on.
Testing
Every journey starts as an acceptance test that asks the application the way a browser would, without a server — with the store, the mailbox and the clock handed in, so a test moves a week on without waiting for one.
(deftest the-site-answers-at-its-root-with-a-page-that-names-lictor
(let [answer ((elysium/app) (test/request :get "/"))]
(assert= (:status answer) 200)
(assert (str/contains? (:body answer) "Lictor"))))prx test # the suite's tests prx precommit # compile, fmt --check, test
ok an-invite-link-leads-to-a-verified-account ok an-invite-works-once … 213 test(s), 0 failure(s)