DEVELOPMENT VERSION: This is an experimental DALICC environment. Features, data and APIs may change or be reset without notice. A stable version of this service, with a potentially different feature set, will be made available soon at dalicc.net.

Administration


Last changed Markdown source

On this page

Part of the documentation index.

This is the manual for everything an administrator does in the browser, on the pages under /admin: people and invitations, the review queue, the curated license records and the core dependency graphs, the blog, what the API is being used for, how much the service is used, the assistant settings, what the site looks like and the record of what changed.

Who it is for. Somebody with the admin role on a running DALICC installation. You need no checkout, no terminal and no access to the server for any of it. Using the site itself is USERS.md; installing the service and looking after the machine it runs on belong to an operator, in DEPLOYMENT.md and OPERATIONS.md.

Contents: 1. what an administrator can do, 2. accounts and invitations, 3. the review queue and correction requests, 4. curated license records, 5. core dependency graphs, 6. the blog and the media library, 7. API usage and per-account limits, 8. the assistant settings, 9. notification and contact mail, 10. the audit log, 11. where the administration pages are, 12. publisher names, 13. usage statistics, 14. appearance, 15. registration.


1. What an administrator can do

There are two account roles, admin and member. An administrator manages people, invitations and the settings page, reads the record of what changed, and may act on any license or dependency graph whoever owns it. The whole matrix, from a member's point of view, is in USERS.md, and /admin/settings shows the same table on the site.

Where the pages are. Administration is the third block of the column that runs down the left of every page behind the login, and the Account entry in the main menu carries a shortcut to it. Section 11 lists every page and its address.

How the menu is grouped. Under Administration the first entry is Overview (/admin, the dashboard), and the other pages follow in five groups, each under a small heading of its own:

Group Entries
Accounts Users, Invitations, Reserved names
Review Review queue, Correction requests to review, Review inbox
Content Curated records, Licenses, Dependency graphs, Blog posts, Media
Reports Statistics, API usage, Audit log
Settings Registration, Assistant, Appearance, Deployment settings

The dashboard at /admin shows the same five groups, in the same order, as sections of cards, one card per page with a line on what it is for, and the menu has an entry for every card. The two Review entries are named so that they cannot be mistaken for Correction requests and Inbox in your own groups above them, which are your requests and your conversations. The page of all accounts is called Users in the menu, on its card, in its heading and in its breadcrumb.

What this installation is configured to do. /admin/settings shows the values that shape the account area, each with the name of the variable it comes from. It is read-only on purpose: those values come from the installation, so a restart of the service can never silently undo a change somebody made in a browser. Changing one is an operator's job (DEPLOYMENT.md).

The first administrator is not created here. An installation starts with no account and no default password, and the first administrator is made while the service is being set up (DEPLOYMENT.md). Every administrator after that is invited from the page below.


2. Accounts and invitations

Invite somebody. /admin/invitations, enter the address, pick the role, submit. If the page shows a link instead of confirming a mail, copy it and send it yourself, through a channel you trust: it is a credential for one account and it is shown exactly once. You can withdraw a pending invitation, or create a new link for the same address, which withdraws the old one so that only the newest mail works.

Inviting an address that already has an account is legitimate and useful: it is how a deactivated account is invited back, and how somebody who has no account yet is given a role on a license. An invitation never lowers an existing account's role.

Which privacy notice somebody has read is kept on the account. Accepting an invitation records the wording that was on /privacy that day. When the notice is replaced, every member whose account records an older wording sees a banner once at their next sign-in: plain lines saying what changed, and a button that records that they have read it. It blocks nothing and asks for no consent; where a change needs a consent, the consent is asked for on the form where it is given. The button writes privacy.acknowledged to the audit log with the version, which is how the association can say what somebody agreed to. There is nothing for you to send and nothing to clear.

Somebody lost their password. /admin/users, open the account, then either Send a reset link (mails it, or shows it when the installation cannot send mail) or Set a temporary password (shown once, and the person must change it at their next login). Prefer the reset link: a temporary password travels through you. Nobody, you included, can read an existing password; only a one-way hash of it is kept. Ask for a new password at the next login, a box on the same page, makes the person choose a new password the next time they sign in, without sending anything.

Somebody left. Open the account and clear Account is active. Their sessions end at once, their API tokens stop working and they cannot log in. Nothing they own is deleted. Inviting the address again reactivates the same account.

Change somebody's name. The name on the account page is the publisher of every license that account publishes, so it belongs to one account and to nobody else. Three kinds of name are refused, each with the reason: one that another account already uses, an e-mail address that is not this account's own, and one that a published license already carries. Section 12 says what to do about the last of those. Accounts marked name shared on /admin/users were already sharing a name before the rule existed, and they keep it until one of the two is changed.

Promote or demote. Change the role on the account page. You cannot remove your own administrator role or switch your own account off, so the last administrator cannot lock the installation.

What the forms refuse. A value the form does not offer is refused with a sentence and changes nothing, rather than being turned into something else: a role that is not in the list (on an account and on an invitation), an action the account page does not have, an empty name, and a name longer than 120 characters. The same holds on the other pages: a token name longer than 80 characters, a model name longer than 200, a provider the assistant page does not list, a deprecation reason longer than 2,000 characters and a successor graph that does not exist. Before, a long value was cut short and saved.

If the name somebody picks when accepting an invitation is already taken, the invitation form comes back with the reason under the name, and the invitation stays good for the next try.

Suspected compromise. Open the account, Log this person out everywhere, then Send a reset link. Then check /admin/audit for bursts of failed logins and for created tokens you do not recognise.

Write to one person. Message on /admin/users/{id} starts a conversation with that account, which they read in their own inbox. Nobody else can open it.

Somebody asks to be deleted. A member does this themselves at /account/delete, where their password is asked for. Anonymise account on /admin/users/{id} is for the case where they cannot: the address no longer works, the account is switched off, or the request arrived by post. Tick the confirmation and submit; you cannot do it to your own account, because that one belongs on your own page behind your password.

It replaces the name and the address with placeholders (a former member and deleted-<id>@invalid), clears the password, revokes every token, invalidates every session, removes the notification preference row and every notification written for the account, deletes the license drafts and the dependency graph drafts with all their saved versions, deletes the invitations carrying that address and the ones the account sent, takes the account id off the counting rows of the two assistants, and detaches the account from the publisher name it reserved.

It does not touch a published document. A license says who granted it, so the name stays on it. What the person can do instead is mark a license withdrawn, which the delete page offers for each of theirs before they confirm. If somebody asks for a name to be taken off a withdrawn license as well, the board decides and an administrator carries it out by hand; the version as published stays in license_revisions and is not republished.

Until September 2026 anonymising rewrote dct:publisher and dct:creator to a former DALICC member. Licenses from accounts anonymised before that still carry the placeholder literal and are left as they are: putting a name back would be a change to a published document nobody asked for.

The publisher name stays reserved when the account published something, with no row saying whose it was, so that nobody else can publish under it; when nothing was published the reservation goes and the name is free again.

The ownership rows, the submissions, the correction requests and the messages stay, under the placeholder, so the record of a review that other people took part in is not rewritten.

The audit entry is user.anonymised, with by (self or admin) and the counts of tokens revoked, drafts deleted, graphs deleted, invitations removed, notifications removed, assistant rows detached, licenses left published and licenses withdrawn. The entry names the account id, which after this points at nothing that identifies a person.

If the triple store cannot be reached the database half still happens; a withdrawal that could not be written is skipped and can be made again from the license page.

The placeholder address can neither sign in nor be invited: an invitation for an address that already has an account reactivates it, and a deleted account must not come back that way. /admin/invitations refuses it with that reason. There is no way back: if the same person returns, create a new account for them.


3. The review queue and correction requests

Members offer their own licenses and dependency graphs for the curated library, and report that a curated record is wrong. What those two look like from their side is USERS.md section 20. This is the other side of the same conversation.

The review queue is /admin/review, also reachable as Review queue in the Review group of the administration menu. Filter it by kind, by status and by age, where age means older than, so that what has been waiting longest is easy to find. One submission shows what was offered, with links to its public page and its manage page, the rationale, the conversation and the decisions:

  • Start reviewing it, so the submitter can see somebody is;
  • Ask for changes, with a note saying what;
  • Approve it, then Publish it with the curated identifier (2 to 64 characters; letters, digits, dots, plus signs and dashes for a license; letters, digits, dots, dashes and underscores for a graph). Publishing creates the record (section 4) and cannot be undone;
  • Reject it, with a note.

The correction queue is /admin/requests, Correction requests to review in the Review group. One request shows the structured reasons next to a link to the record and a link to the editor. Accept it and it appears in the Correction request select on that record's publish form. Publishing a version that names the request resolves it and links the version in its conversation. Use Reject or Close with a note when no version will answer it, and Mark it resolved by hand when it was fixed some other way.

The queue inbox is /admin/inbox, Review inbox in the Review group of the administration menu: every submission and correction thread in one list, whoever started it. Your own conversations, including the ones somebody started with you, stay in your personal /inbox.

Nothing is moved, renamed or deleted when a submission is published. A new curated record is created that says the same thing, names the submitted license as its source and its owner as its creator, and starts at version 1 with the review state dalicc:Created. The submitter's own license keeps its address, its owner and its history. The new record has not been reviewed against the legal text; review it before anybody relies on it.


4. Curated license records

The 581 records of the DALICC license library are a different kind of object from the licenses accounts compose: nobody owns them, they are listed and searched, and what they say decides what the compatibility checker answers. They are kept in the project repository, and they can also be changed here in the browser, which is the way to make a correction that cannot wait for a release.

Where. Administration then Curated records, or /admin/licenses/core. The list shows every record with its model version, when it last changed, its review state, and a badge when it has an unpublished draft or a correction that is not in the repository yet. When the license store does not answer, the list is read from the files shipped with DALICC, without titles, and the page says so above the list.

Two editors, one draft. /admin/licenses/core/{id}/edit has two tabs:

  • Guided form is the License Composer, prefilled from the record, with the fields a curated record needs and a composed one does not (alternative names, SPDX identifier, legal code URL, publisher, logo, the version of the license itself). It is a faithful editor for almost every record; when a record holds something the form cannot carry, the page says so and lists the statements, so the choice to use the Turtle tab is an informed one.
  • Turtle is the whole record as text. It is checked before it is stored: it must parse, hold exactly one odrl:Set whose subject is the record's own address, use only actions of the DALICC vocabulary and only absolute addresses, and keep the review state. A document that does not parse is refused with "That is not valid Turtle (line n)", keeping the line number where the parser gave one; the other errors say which rule the document breaks.

Saving in either tab writes the same draft, which is private to the administration pages. Preview renders it read only, as the public page would.

Publishing. "Publish as version n+1" asks for a summary (one sentence, which becomes the headline of the change log entry), a reason (free text, which becomes the reason of every change in it) and optionally the identifier of a correction request. It then:

  1. stores the version, immutably, with your name and the date;
  2. keeps the version it replaces retrievable;
  3. replaces the record in the license library, so the site and the API serve the new model at once;
  4. raises the record's version number and its modification date, and keeps its version history;
  5. writes the change log entry, which appears on the license page and in /license-library/changes in the same sequence as the versions that came from the repository.

It cannot be undone. A mistake is corrected by publishing another version.

Deprecating. A curated license can be withdrawn with a reason and an optional successor. It is a version like any other: the record keeps resolving and gains a deprecation marker, the date and the pointer at the successor.

Getting a correction into the repository. The server does not write into the project repository, so a version you publish here lives on this installation until somebody with a checkout writes it back. /admin/licenses/core/export lists what is waiting, without writing anything, so you can see at a glance whether anything is outstanding. Doing it is an operator's task, and a weekly rhythm is reasonable (OPERATIONS.md). Nothing is lost while it waits: the versions stay in place and the site keeps serving them.

A submission becoming a curated record. On /admin/licenses/{id}/promote you give a published, composed license a curated identifier (2 to 64 characters, letters, digits, dots, plus signs and dashes, unique against the records that already exist). The rest is in section 3.

Every license with an owner is at /admin/licenses, with filters and the deprecate and transfer actions. You may open any of them in the composer, save drafts, publish new versions and deprecate them, which is how the DALICC team corrects a license it is asked about without taking it over. It is never silent: the revision you save is attributed to you with a badge, the owner's manage page tells them their license was last edited by an administrator and when, the act is recorded in the audit log, and the owner can restore any earlier revision in one step. Ownership does not change.


5. Core dependency graphs

A dependency graph says which actions include, imply, equal or contradict which other actions, and it is what the compatibility checker reasons with. A core graph is curated data: an administrator edits it in place, publishing mints the next version rather than creating a second object, and nobody but an administrator may touch one. The graph that ships with the service, dg_default, cannot be deprecated, because withdrawing it would silently change every answer the service gives. How members work with graphs of their own is USERS.md section 18, and what the reasoning does with a graph is explained for visitors on the How reasoning works page at /reasoning.

Promoting somebody's graph. /admin/dependency-graphs, Change, Promote to core. You assign the identifier by hand (2 to 64 characters of letters, digits, dots, dashes or underscores; it becomes the address and never changes). The statements are copied, version 1 of the core graph is stored with a change log naming who promoted it, and the user's graph is marked deprecated with a pointer at its successor. Nothing is deleted and no address stops resolving.

Editing a core graph. Open the ordinary editor on it. Publish version n+1 needs a one-sentence summary and takes an optional reason. It archives the statements the graph had as version n and writes the new ones as version n+1, and it works the change list out from the statements themselves rather than from what you believed you changed. The history in the Dependency Graph Viewer (/dependency-graph) then shows the versions that came from the repository and the ones published here as one sequence.

Like a curated license record, a version published here waits on this installation until an operator writes it back into the project repository (OPERATIONS.md). A graph a member published is never written back, because it belongs to them.

Editing a jurisdiction graph. The seven jurisdiction graphs (dg_eu, dg_us, dg_cn, dg_gb, dg_jp, dg_in, dg_br) are complete graphs, and the editor edits them as a whole like the core graph: the 46 axioms, the core graph's adopted rule and the graph's own rules. Every rule of a core graph needs an explanation beside its basis, two or three plain sentences on what the rule does, why, and what a person combining licenses will notice; the editor refuses to save a rule without one. What the editor does not show is kept from the version before: the title and description, the core graph and version the graph was built from, and the records of the axioms it removes. The page of such a graph says that its source of truth is the difference file licensedata/dependencygraph/differences/<id>.ttl in the repository. A version you publish here takes effect on this server at once; to keep it, an operator exports it to git exactly like an edit of the core graph (make export-library writes the complete graph to licensedata/dependencygraph/<id>.ttl), then folds it back into the difference file and regenerates the graph:

python scripts/build_dependency_graphs.py --extract dg_eu     # complete graph -> difference file
git diff licensedata/dependencygraph/differences/dg_eu.ttl  # review what the edit changed
python scripts/build_dependency_graphs.py --bump --summary "..."   # the next version, logged
python scripts/validate_data.py

--extract refuses an edit a difference file cannot say (an axiom the core graph does not hold, or a core rule dropped without a rule that replaces it); such a change belongs in the core graph. An axiom you removed on the server comes back as a removal record whose basis and explanation are empty, and the build refuses it until somebody writes both. An edit of the core graph changes every jurisdiction graph as well: after it is exported, --bump gives each of the seven its next version, "Follows core graph version n".


6. The blog and the media library

The blog is managed in the browser by administrators, at /admin/posts and /admin/media (also Blog posts and Media in the Content group of the administration menu). Members cannot open these pages. Everything that changes is recorded in the audit log.

The blog is seeded from markdown files that ship with the service, in app/content/blog. At every start the service imports the seed files it does not have yet, by address. A new release that brings a new seed file adds that post at the next restart, next to everything you have written.

A start also reads the files it has imported before, so a corrected seed file reaches an instance that is already running. A post is rewritten from its file only if you have never saved it in the editor, and only the fields that differ are touched: the date it carries and its place in the blog do not move. The moment you open such a post and save it, the editor owns it and no release changes it again. If you want the file's text back after that, delete the post and restart. Posts recovered from the old site are never rewritten this way, because those files are not maintained.

Deleting a seeded post is permanent too. The address goes onto a list of removed seeds, and a restart does not bring the post back.

The same start also clears up the other way round. A post that came from a seed file and whose file is no longer in app/content/blog is removed, and its address goes into the removed-seed list, so the blog holds what the files say it holds. Three kinds of post are left alone: one you wrote in the editor, one whose file is still there, and one that was edited in the editor after it was imported. Each removal is written to the service log on its own line.

Some installations still hold posts recovered from the old dalicc.net site that were edited here, so the start leaves them. They carry an archived badge in the list and can be removed in one go, see Remove the posts of the old site.

Write a post

  1. Blog posts in the admin menu, then New post.
  2. Title is required. The address under it is filled in from the title while you type and is what the post is published at (/blog/<address>). You can change it, and the form refuses an address another post already uses. Changing the address of a post that is already public breaks the links people have to it, so change it only if you have to.
  3. Teaser: the sentence shown in the blog list, on the home page and in the feed. Leave it empty and the first paragraph of the body is used.
  4. Body: markdown. # for a heading, *text* for emphasis, [label](address) for a link, ![description](address) for an image. The preview pane on the right shows the result as you type; it shows exactly what the published page will show, because it is rendered and cleaned the same way. With JavaScript switched off the preview is simply absent and everything else works.
  5. Categories and tags: separated by commas. Each one becomes a page (/blog/category/..., /blog/tag/...) and appears in the sidebar of the blog.
  6. Status: Draft while you are working. A draft is invisible to everybody else: it is not on /blog, not on a category or tag page, not in the feed, not in the sitemap and not on the home page, and its address answers 404.
  7. Publication date: in UTC. Left empty, publishing sets it to the moment you publish. Set it by hand to keep the original date of an older piece. Author name beside it is the name shown on the post, up to 120 characters; never put an e-mail address there.
  8. Save keeps your work. Save and publish puts it on the blog. A published post can be taken down again with Save as draft; the date is kept.

Add a photograph

You can upload from the editor (right-hand side, "Upload an image") or from Media. Either way:

  • A description is required. It is read out to people who cannot see the picture and it is shown when the image fails to load. The form does not accept an empty one.
  • Accepted formats are JPEG, PNG, GIF and WebP, up to 8 MB. What counts is the content of the file, not its name: renaming a document to .png does not get it in.
  • SVG files are refused. An SVG is a document that can carry script, not a picture. Export the graphic as PNG or WebP.
  • Every upload is re-encoded, which removes the camera model, the timestamp and the location a photograph usually carries. Animated GIFs keep their frames.
  • The file is stored under a random name. Nothing you type is ever used as a path.

Uploading from the editor inserts ![your description](/media/...) at the cursor straight away. From the media library, use Copy address, or the markdown line shown next to each image in the editor.

Cover image: pick one in the editor from the images already uploaded. It is shown above the post, in the blog list and as the preview image when the post is shared.

Delete something

  • A post: the Delete button opens a confirmation page. Tick the box and confirm. It is removed from the blog, the feed and the sitemap immediately and cannot be recovered. If you only want it off the blog for now, save it as a draft instead.
  • An image: Delete in the media library. This is refused while any post still uses the image, as a cover or in its body, and the message names the posts. Take it out of those posts first.

Remove the posts of the old site

The posts recovered from the old dalicc.net site carry an archived badge. Every start already removes the imported posts whose markdown file is gone, so what is usually left is the ones somebody edited here. Where an installation still holds any, the Blog posts page offers Remove all archived posts.

The button opens a confirmation page that lists every post it would take, with its address and its publication date. Tick the box and confirm, and all of them go at once: off the blog, the feed and the sitemap, with their addresses written into the removed seed list so a restart leaves the blog as you left it. Posts you wrote in the editor are not touched, and the removal is written to the audit log as post.archive_removed with the number of posts.

Export the blog first if you want to keep a copy of them, see Export and import the whole blog. The export can be read back here or into another installation.

The feed

https://dalicc.net/blog/feed.xml is an RSS 2.0 feed of the twenty newest published posts. The blog pages link to it, and any feed reader can subscribe.

Export and import the whole blog

Two buttons at the top of the Blog posts page move the blog as a whole.

Export all posts and media downloads dalicc-blog-<date>.tar.gz. Every post is in it as a markdown file, drafts included, with its title, address, dates, author, status, categories, tags and teaser at the top. Every uploaded image is in it as a file, and media/index.json lists the description, the type, the size and the checksum of each one. Unpack the archive with any archive program and you can read a post in a text editor and open the pictures.

Import an archive takes such a file back in, on another installation or on this one. Choose the file, choose what should happen to something that is already here, and confirm:

  • Add only keeps what is stored here. A post whose address is already taken and an image that is already in the media library are left untouched and listed as skipped.
  • Replace existing takes the archive's version: a post with the same address is overwritten, and the description of an image that is already here is replaced.

Posts are matched by their address, images by the content of the file. Importing the same archive twice therefore changes nothing the second time, and an image that is already in the library is never stored a second time. An imported image gets a new id in this installation, so every /media/... address inside a post body and every cover image is rewritten to point at the image the import stored.

The result page lists what was created, what was replaced and what was skipped, and the import is written to the audit log.

What is refused. The archive is read and checked in full before anything at all is written, so a refused archive leaves the blog exactly as it was. An archive is refused when a path inside it is absolute or reaches outside the archive with .., and when it holds a link or a device entry. It is refused when a file is larger than its limit, which is 200 MB for the archive and 8 MB for one image, and when an image is not a JPEG, PNG, GIF or WebP by its content, does not match the checksum in the index, or has no description. It is refused as well when manifest.json is missing, unreadable or written in a format this installation does not read. The page then names the file inside the archive and the reason.

Backups

A post and the pictures it shows are useless apart, so the posts and the uploaded images have to be backed up together. On a standard installation they sit side by side and one copy covers both; the procedure is an operator's (OPERATIONS.md). An export is a copy of the content in a form you can read and load again, and it is worth making one before a large edit. It is not a substitute for that backup, because it holds no accounts, no licenses and no audit log.


7. API usage and per-account limits

Requests made with a personal API token are counted per account, in four windows at once (per second, per minute, per hour and per day), and an expensive call counts as more than one. What a member sees of that is USERS.md section 21.

  • /admin/api-usage lists the last 24 hours: the busiest accounts, the busiest endpoints, how many requests were refused, the defaults in force and what each expensive endpoint costs. /admin/api-usage/data is the same figures as JSON. The rows behind it are kept for seven days, so export them before that if an investigation will run longer.
  • /admin/users/{id}/limits, reachable from every account page, shows that account's four limits, what it has used of each window right now, and its last fifty requests. Saving stores limits for that account alone; "Use the defaults again" removes them so the account follows the installation's defaults once more. Both are written to the audit log.

The four numbers have to be whole numbers of at least one and have to grow with the window: a per-hour limit below the per-minute one would be a per-minute limit with a confusing label, and the form says so instead of storing it.

A fifth field on the same form, translations per day, caps how often that account may use Text-to-License. Leave it empty and the account follows the installation default; zero means the account keeps its API allowance but cannot start a translation, which is not the same as switching the account off.

If somebody is abusing the API, escalate in this order and stop as soon as it is enough:

  1. Revoke the token. /admin/users/{id} lists the tokens of an account, each with a Revoke button (audited as api_token.revoked_by_admin); the person revokes their own on their tokens page, and switching the account off stops all of them at once. Revocation is immediate: the next request with that token is refused.
  2. Lower that account's limits on /admin/users/{id}/limits. It takes effect on the next request, and the counts already recorded stay where they are.
  3. Look at what they called, in the last fifty rows on the limits page or in the endpoint table on /admin/api-usage.

Lowering the limits for everybody, or blocking traffic that carries no token at all, is a change to the installation and belongs to an operator (OPERATIONS.md).

This page is the last 24 hours. For weeks and months, with charts and an export, see section 13.


8. The assistant settings

/admin/assistant is Assistant in the Settings group of the menu, and the page itself is titled Assistant settings. It has two sections of equal rank, one per direction of the assistant:

  • Text-to-License holds the providers, their access keys, their models and the daily caps, together with the runs of the last two hours (up to fifty) and what each of them cost; a run row is deleted after two hours, and the day's number stays on the statistics page. Each provider has a Test connection button. How the feature looks to the person using it is USERS.md section 19.
  • License-to-Text holds the switch, the allowance, a Try it button and the figures of the other direction, described below.

Both directions use the same providers and keys, so they are set once, in the Text-to-License section.

The providers are a table of three. Each one has its own key, its own model and its own allowance, and a run uses one of them: the one that can answer and has the largest tokens-per-minute allowance it has reported, with the order below deciding between two that report the same. When a provider refuses a part, that part and the rest of the run go to the next provider in the table, so a free allowance that runs out in the middle of a long license no longer ends the reading.

  • The access key, per provider. A key stored on this page is encrypted where it is kept and is shown only by its last four characters. It wins over the one the installation was started with, which is how it is replaced without anybody touching the server. Test connection lists that provider's models, asks it one small question and prints the allowance figures that came back. Remove stored key puts the provider back on the key the installation was started with. A provider with no key anywhere is simply not used. Storing a key is refused on an installation that has no signing key of its own, because the stored value could not then be read back after a restart. Fixing that is an operator's job (OPERATIONS.md).
  • The model, per provider. Chosen from that provider's own list. Changing it changes what the assistant proposes, so change it deliberately.
  • Use this provider switches one provider off without removing its key, and Up and Down change the order the providers are tried in.
  • Requests a day and Tokens a day are what that provider's tier allows. The page counts what has been spent today next to them, and when a cap is reached the next provider takes over until midnight UTC. Empty or 0 means no cap.
  • Who the provider is, per provider: its legal name, its country and the address of its own privacy notice (an https:// address). The privacy notice lists every provider a text may go to with these three, because a label such as the one on this page does not tell a reader which company receives their text. Nothing is filled in for you: take the three from the provider's terms and privacy notice, and check them again when the provider changes hands. Until all three are recorded, the notice prints the label and says what is still missing. Save who the provider is writes an assistant.identity_changed audit entry with what changed.
  • The caps further down are about people rather than providers: how many translations one account may run per day and how many the whole installation may run per day. A key costs money when it is used, and the whole installation's cap is the backstop. A single account's cap is set on that account's limits page (section 7).
  • The state column shows what each provider will still accept and, while one is away, when it comes back. The feature is only unavailable while every provider is away, and the countdown a reader then sees is the first one back.

To add a provider: get a key from it, paste it into that provider's box, press Store the key, then List the models and choose one, and set the two daily caps its tier allows, and record who the provider is. Nothing else is needed; the next run can already use it.

/admin/assistant/prompt shows the whole instruction the assistant is given, exactly as it is sent, together with its version. What you see on either page is what goes out.

Every change here is written to the audit log with the provider it was about, including the last four characters of a key that was set or removed. The text people submit is never recorded.

License-to-Text has its own section

/license-to-text writes the license text of a license model, the other direction of Text-to-License (USERS.md section 19a). It uses the providers, the keys and the models above, and the installation's daily cap, because it spends the same provider budget. Everything else is its own, in the section License-to-Text further down the same page.

  • License-to-Text is available on this deployment switches the whole feature on and off. Off, /license-to-text, the Write this as a license text button in the composer and POST /licenselibrary/narrate all say that it is switched off and answer 503. GET /licenselibrary/license/{id}?format=text keeps working, because that text is written from the DALICC vocabulary and asks no provider.
  • License texts per account per day is the allowance. Leave it empty and an account may write as many license texts as it may run translations, which is what an installation that treats the two directions as one budget wants. 0 switches the feature off for everybody without switching the feature itself off.
  • Visitors without an account is a choice of three: follow the Text-to-License rule, allow it, or ask people to log in first.

However you set the allowance, the two counts are kept apart: writing a license text never takes a translation away, and running a translation never takes a license text away.

Try it writes the license text of MIT, a license from the library, the way a reader's request would at that moment. The providers are asked in their usual order, and the page shows the text that came back, which provider wrote it and with which model, and what it spent: one request, the tokens in and out, and the requests and tokens the provider says are left. When no provider answers, the text comes from the DALICC vocabulary and the page gives the reason, for instance that no provider has a key or that the daily allowance is used up. The try counts as one License-to-Text run in the figures below, and it spends none of your own account's allowance. It is written to the audit log as assistant.narrate_tried.

So there are two ways to check the providers from this page. Test connection in the Text-to-License section asks one provider a very small question and costs a handful of tokens. Try it in the License-to-Text section does a full run on a real license and costs a few thousand tokens of whichever provider answers.

What License-to-Text did under the form is the record: license texts today, over the last 7 days and over the last 30 days, and a table of who wrote them, one line per provider plus one line for the texts the DALICC vocabulary wrote with no provider asked. The figures are whole days in UTC, today included, and are counted in the daily counts as each text is written, so they cover the whole week and month although the run itself is deleted after two hours.

Nothing of a license text is stored beyond that count. Where the key is missing, where a cap is reached or where the provider does not answer, the feature writes the text from the DALICC vocabulary instead of failing, and the page tells the reader that is what happened. So switching the assistant off does not switch License-to-Text off: it makes every text the plain one. Switching License-to-Text off is the setting above.

The three settings can also be set in the environment as DALICC_NARRATE_ENABLED, DALICC_NARRATE_PER_DAY and DALICC_ANONYMOUS_NARRATE (DEPLOYMENT.md); what is stored on this page wins over them, and emptying a field here gives the environment back. Every change is written to the audit log as assistant.narrate_changed.


9. Notification and contact mail

Every notification always appears in the recipient's inbox, whether or not the installation can send mail. When it can, the same notification is also mailed, with a one-line summary and a link back, and each person can switch that off for themselves.

When it cannot, nothing is silently dropped and every page says so. An invitation link and a password-reset link are shown to you instead of being mailed, to pass on by hand; "forgot password" tells the visitor to contact an administrator; and the contact form offers the visitor a prefilled mail link. Configuring a mail server removes all of that at once, and it is an operator's task (OPERATIONS.md).

A mail server that is down never fails what somebody was doing: the notification is written, the send is attempted once, and the failure is logged. The inbox shows which notifications went out by mail.


10. The audit log

/admin/audit is the record of everything that changed, newest first, 25 entries a page, with the account that did it, the object it was done to and the time. The page has no search of its own; the action names are stable dotted strings, so an operator can search the audit_log table for one.

Area Actions
Sessions and passwords auth.login, auth.login_failed, auth.login_refused, auth.login_unverified, auth.logout, auth.logout_everywhere, auth.password_changed, auth.password_reset_requested, auth.password_reset
Invitations invite.created, invite.accepted, invite.revoked, invite.resent
Signing up and signing in with a provider register.created, register.verification_sent, register.verified, register.duplicate, sso.linked, sso.refused
Accounts user.role_changed, user.activated, user.deactivated, user.updated, user.password_set, user.bootstrapped, user.approved, user.rejected, user.anonymised, privacy.acknowledged
API tokens token.created, token.revoked, api_token.revoked_by_admin
Publisher names name.released, name.refreshed
Licenses license.draft_created, license.published, license.version_created, license.deprecated, license.transferred, license.member_added, license.member_updated, license.member_removed, license.member_invited
Curated records core_license.created, core_license.published, core_license.draft_discarded
Dependency graphs depgraph.draft_created, depgraph.published, depgraph.version_created, depgraph.deprecated, depgraph.promoted, depgraph.transferred, depgraph.member_added, depgraph.member_updated, depgraph.member_removed, depgraph.member_invited
Review workflow submission.created, submission.status_changed, submission.published, request.created, request.status_changed, request.resolved, thread.created
Blog and media post.create, post.update, post.publish, post.unpublish, post.delete, post.archive_removed, post.export, post.import, post.import_refused, media.upload, media.delete
API limits ratelimit.updated
Usage statistics stats.rollup
The two assistants assistant.key_set, assistant.key_removed, assistant.model_changed, assistant.provider_changed, assistant.order_changed, assistant.quota_changed, assistant.identity_changed, assistant.narrate_changed, assistant.narrate_tried, assistant.tested
Registration and appearance registration.policy_changed, registration.policy_reset, appearance.changed, appearance.reset, banner.changed

Three rules hold for every entry:

  • No secret is ever written. Not a password, not a token, not an access key, not the body of a request. A failed login is counted without the address that was tried.
  • Nothing is rewritten. A withdrawal, a deprecation and a revocation are entries of their own; the earlier ones stay as they were written.
  • It is not the only record. A status change in the review workflow is also a message in the conversation, a notification for the people it concerns, and a decision recorded on the submission or the request itself.

Two years, then the entry goes. An hourly sweep drops every entry older than DALICC_AUDIT_RETENTION_DAYS, which is 730 days (DEPLOYMENT.md). The same sweep enforces the other retentions the privacy notice states: seven days of API request rows, DALICC_STATS_RETENTION_DAYS of daily usage counts, two hours of the assistants' finished runs, and the account ids of the daily counts, folded into a number after two days, a sign-up whose address was never confirmed, a week after its last link ran out, a password reset link once it is used or has run out, and an invitation nobody accepted, a week after it ran out or was withdrawn. Long enough to investigate an intrusion that is noticed late, short enough that the record of who did what does not follow a person for ever. Export what an investigation needs before it ages out.

What a publish records. license.published carries the consent the publisher gave and the version of the privacy notice it was given against, for the web form, for the API and for an anonymous publish, which writes an entry with no account behind it. The version comes from the same wording the account accepted, so an entry says what somebody agreed to and not only that they agreed.


11. Where the administration pages are

On a page with several forms (the assistant settings, an account, registration, the statistics), the sentence that says what a form did appears inside that form's section, and the page scrolls there.

Page What it is
/admin the administration home
/admin/users, /admin/users/{id} accounts
/admin/names the publisher names that are already in use
/admin/users/{id}/limits one account's API limits and usage
/admin/api-usage the last 24 hours across all accounts
/admin/statistics how much the site and the API were used, over weeks and months
/admin/invitations invitations
/admin/licenses, /admin/licenses/{id} every owned license
/admin/licenses/{id}/promote publish a submitted license as a curated record
/admin/licenses/core, /admin/licenses/core/{id}/edit the curated records and their editor
/admin/licenses/core/export what is waiting to be written back into the repository
/admin/dependency-graphs core and promoted dependency graphs
/admin/review, /admin/requests, /admin/inbox the review queue, the correction queue and their conversations
/admin/posts, /admin/media the blog and the media library
/admin/assistant, /admin/assistant/prompt the assistant settings: Text-to-License (providers, keys, models, caps, runs) and License-to-Text (switch, allowance, Try it, figures), and the instruction as it is sent
/admin/appearance the palette, the font set, the type scale, the menu style and the logo the site is drawn in, and the site banner
/admin/registration invitation only or self-registration, and the options that come with it
/admin/audit the audit log
/admin/settings what this installation is configured to do

The pages a member uses are in USERS.md, the same objects over the API are in API.md, and the server behind all of it is in DEPLOYMENT.md and OPERATIONS.md.


12. Publisher names

A published license names its publisher, and that name is the only thing about the author it carries. DALICC therefore treats a name as belonging to one account. No two accounts can publish under the same name; an account may use an e-mail address as its name only when the address is its own; and a name that a published license already carries cannot be taken by somebody else. What that looks like from a member's side is USERS.md section 12.

/admin/names is the list of the names that are already in use. Each row shows the name as the license writes it, the key it is compared by, where it was found and whether it has been released to an account.

  • Held. Nobody can take the name as the name of their account. This is the state of every name that DALICC found on a custom license with no owner, which is most of the legacy library: those licenses were published before there were accounts, so the service cannot know whose names they are.
  • Released. You have said that the name belongs to one account. That account can use it as the name it publishes under; for everybody else it stays held.

Release a name. Copy the name into the form, give the address of the account it belongs to, and submit. Ask for evidence first, the way you would before moving a license to a new owner: releasing a name lets that account publish documents under it. The release is written to the audit log as name.released, with the name and the account.

Refresh from the library reads the publisher and creator names out of the library again, and out of the private drafts of every account. The same sweep runs whenever the service starts, so the button is for the license that was published a minute ago and for seeing the effect of a release. A name that no longer appears anywhere is dropped from the list, unless it has been released. When the triple store cannot be reached the page says so and nothing changes.

An account is never blocked by a name found on a license it owns, because the sweep records which account that is. Somebody can always take the name their own published license carries; only another account is refused.


13. Usage statistics

/admin/statistics answers the question "how much is DALICC being used", over a range of days rather than over the last day. It is the long view; /admin/api-usage (section 7) stays the operational one, and the two pages link to each other.

What is counted, and what is never counted

The page is built on counts and on nothing else. DALICC stores no IP address, no browser name, no screen size, no referrer, no cookie value and no visitor identifier of any kind, and there is nowhere in the table behind this page that such a value could go: a row is a day, what was counted, which one of it, and a number. "Unique" on this page therefore always means a DALICC account or a DALICC API token, never a person behind a browser. Anonymous traffic is a count and can never be a uniqueness.

Counted How it is worked out
Page views Every request that was answered with a page, under the family of its address: /license-library/* rather than one line per license, /admin/users rather than one line per account
API requests From the API usage log, by operation family (/licenselibrary/license/*, /compatibilitycheck)
Answers The same requests by class: 2xx, 4xx, 429 and 5xx. A redirect counts with the answers that worked, and 429 is kept apart because a refused request is what an operator looks for
Active accounts and tokens An account that called the API or signed in that day, and a token that was used that day
Logins, sign-ups From the audit log (auth.login); a sign-up is an invitation that was accepted or an account created on the sign-up form or through the identity provider (register.created)
Translations Runs started, counted as they start, so the figure survives the run rows, which are deleted after two hours; parts read, runs that failed and runs per provider, worked out from the run rows while they exist
Licenses, dependency graphs, posts What was published that day
Submissions What was offered for the curated library that day

Not counted at all: assets under /static and /media, the health check, and this page itself, so looking at the figures never changes them. A request that ended in an error is not a page view either.

Every day runs from midnight to midnight UTC, wherever you and the server are.

How to read the page

The controls sit in one row above everything they scope, and they are all in the address bar, so a range can be bookmarked or pasted into a message and shows the same page to the next person.

  • Range. Six presets (7 days, 30 days, 90 days, 12 months, this year, all), or two dates. The dates win over a preset; a range given backwards is turned around and one that ends in the future is cut at today.
  • Grain. Day, week or month. Left alone it follows the length of the range: days up to three months, weeks up to two years, months beyond that.
  • Calendar. Which measure the calendar at the bottom draws, API requests or page views.
  • Compare with the previous period draws the range before this one as a muted line behind the measure, and it is the same comparison the tiles use.

The tiles are the headline numbers, each with its change against the previous period of the same length. "No earlier figure to compare with" means the earlier period was empty; a rise from nothing is not a percentage and DALICC will not print one.

The charts are drawn on the server, so they are there with JavaScript switched off and they print. Each one shows one measure on one axis, never two scales on one plot. Moving the pointer across a chart over time brings up a hairline and a readout of every series at that date; that is the only thing the page's JavaScript does. Under every chart is Table view, which opens the same numbers as a table, so no value is only available by hovering.

  • Page views over time and Translation runs over time are single measures, with the previous period behind them when the compare box is ticked.
  • Translation runs per provider ranks the providers by the runs they answered in the range.
  • API requests over time, by answer stacks the four answer classes. A tall amber or red band is the thing to look at.
  • Active accounts and tokens over time counts each account once per period. Account ids are dropped after two days and only the daily number is kept, so a long range is a lower bound rather than an exact figure. The note under the chart says so.
  • The ten busiest pages and the ten busiest API operations are ranked bars, every bar the same colour: the length already says which is bigger.
  • Content published per period stacks licenses, dependency graphs and posts.
  • The calendar is the last twelve months, one cell per day, darker where there was more. A day with nothing stays in the page colour, because nothing happened is not a small amount of something. Its table view is the same grid as a table, one row per week.

The exports

  • Download CSV, at /admin/statistics/export.csv, gives the rows the page is drawn from for the selected range: day,kind,key,count, one line each. It is what to open in a spreadsheet or attach to a report.
  • Open the JSON at /admin/statistics/data.json gives the tiles and every chart's table as JSON, for an operator with curl.

Both take the same range parameters as the page, so the address in the bar and the address of the export are the same range.

When the figures are worked out

Page views are counted as they happen, in memory, and written every half minute or every two hundred pages, whichever comes first. Everything else is worked out once a day from records DALICC already keeps, and the same work runs at startup, on the first page view after midnight, and from Work this range out again at the bottom of the page. Running it twice writes the same numbers, so pressing the button can never double anything. Press it when you have just published something and want the figure now. The button is written to the audit log as stats.rollup.

The rows are kept for three years (DALICC_STATS_RETENTION_DAYS, in DEPLOYMENT.md); the oldest days are dropped by the hourly sweep. Because the API usage log itself only keeps seven days, a day that is never rolled up within a week keeps whatever it already had and cannot gain the parts that came from that log.

A fresh installation is empty on purpose. Counting started when this version was deployed, so a range that reaches back further shows the empty state rather than zeroes pretending to be measurements. The page names the first day it has anything for.


14. Appearance

/admin/appearance decides what the whole site looks like: the colours, the three faces it is set in, how large everything is, how the menu is set and how the logo is drawn. Nothing here changes what a page says or what anybody may do; it changes how the site is drawn, for every visitor.

Five things are chosen on one page, each in a select of its own.

A palette says what the colours are. Nine are offered:

  • DALICC classic, the colours the site has worn since the original theme was recovered, and the one it starts with.
  • Institutional Navy, navy for the wordmark, the headings and the menu, coral for the second half of the wordmark and for the call to action, a pale blue hero.
  • Graphite and amber, Forest and sand and Slate and teal, three quieter registers.
  • Ink and gold, Charcoal and mint and Midnight and sand, three more professional registers: ink with a deep gold, charcoal with a mint hero, midnight blue with warm sand.
  • High contrast, for readers who need the strongest separation: near black on white, a saturated blue for links and buttons, black on a pale yellow hero. Every pair of text and background is above 7:1.

A font set says which three families carry the site: one for the hero line and the headings, one for running text, one for identifiers, license expressions, versions, hashes and timestamps. Classic, Institutional (Barlow Condensed, Inter, Inconsolata), Plex, Geist, Source, Merriweather and Inter (a serif for the headings, an editorial feel) and Manrope and JetBrains Mono. Every file is served by DALICC itself, so choosing a set asks nothing of any other company and sends nobody's address anywhere.

A type scale says how large everything is, from one body size: Compact (15 px), Regular (16), Comfortable (17) and Large (18, which is what the site is set in today and therefore the one it starts with).

A menu style decides whether the main menu is set in capitals in the display face or in sentence case in the reading face. Each font set arrives with the answer that suits its display face; you may change it.

A logo says how the DALICC wordmark is drawn: Wordmark (as it has always been, "dali" in the brand colour, "cc" in the accent, the subtitle line under it), Wordmark, single colour (all of it in the brand colour) or Wordmark without subtitle. Every option takes the palette's own logo colours. There is no option with a separate symbol, because DALICC has none. The small icon in the browser tab follows the palette as well; the picture shown when the site is shared on social media stays the original wordmark.

The palette, the font set and the logo also dress the animated picture on the home page, so the circle of steps always matches the band it sits on.

What the page shows

One preview panel sits beside the five selects, in the site's own words: the logo, the menu, a hero line, a button, a card, a table with a license identifier and a date, a muted note. Change any select and the panel is redrawn at once in the combination the five make, without reloading the page, so the choice is made by looking rather than by reading a colour value. Under each select is a sentence about the option chosen in it.

Under the panel are the contrast ratios of the chosen palette: how far apart body text, second-rank text, muted text, a link, a heading, a button label and the hero line are from the surfaces they sit on. A ratio of 4.5 is what text at a normal size needs to reach accessibility level AA, and 3.0 is what a heading and the hero line need. Each row says passes or under 4.5. The seven palettes designed for this page reach every one; DALICC classic and Institutional Navy each fall short on a few, and the page names which, because they were given rather than designed. That is a decision to make with open eyes, not a reason the option is hidden.

Trying it before anybody else sees it

Preview on the site applies the choice to your own browser and to nothing else. Walk through the home page, a license, the composer and this page in it; no visitor and no other administrator sees any of it. Stop the preview puts your browser back. The preview is a cookie: it does not outlive a browser that is closed, and copying it into another browser does nothing, because it is read only for an administrator.

Applying it

Apply to the site stores the choice and every visitor sees it, on every page that is not the legacy /web pages. The change is written to the audit log as appearance.changed, with the palette, the font set, the scale, the menu switch and the logo.

Reset to the deployment's choice removes the stored choice. The site then wears whatever the installation is configured to wear, which unless an operator set something else is DALICC classic, the Classic font set, the Large scale and the Wordmark. The reset is audited as appearance.reset.

A stored choice wins over the installation's own setting, the same way the Text-to-License settings work, so a palette can be changed without a deployment. The variables are DALICC_APPEARANCE_PALETTE, DALICC_APPEARANCE_FONTS, DALICC_APPEARANCE_SCALE, DALICC_APPEARANCE_NAV_CAPS and DALICC_APPEARANCE_LOGO (DEPLOYMENT.md).

Where the reasoning is

What the roles mean, why each palette is what it is, where each font file came from and how to add a palette, a font set or a scale, is in the developer documentation of the service, section "Typography and colour". That section also carries the typography guidance this system was built to: one face for reading, one for display, one for exactness, never four; monospace where exact characters matter and proportional where language matters; and the size system the four scales follow.

The site banner

The last section of /admin/appearance, Site banner, puts one line of text in a band above the header of every page: the public pages, the account and administration pages and the error pages, for every visitor, signed in or not. Use it for something every reader should know about the installation itself, such as that it is a development version or that it will be down for maintenance on Friday.

It has three controls, saved together with Save the banner:

  • Show the banner, a switch. Off unless the deployment turned it on.
  • Visitors can close the banner, a switch, off unless the deployment turned it on. When it is on, the band has a Close button. A visitor who closes the banner does not see it again in that browser until the text changes; any change to the text, even one character, shows it again to everybody. The browser keeps this itself, so closing it in one browser does not close it in another, and a browser that blocks site storage shows the banner without the button. When the switch is off there is no button and the banner is shown to everybody, including those who closed it before.
  • Banner text, up to 500 characters of plain text. Line breaks become spaces, so the banner is always one paragraph; it wraps onto more lines on a narrow screen. A tag or a character reference such as &amp; is refused with a note beside the field, and nothing is saved. Leave the field empty to show the default text:

DEVELOPMENT VERSION: This is an experimental DALICC environment. Features, data and APIs may change or be reset without notice. A stable version of this service, with a potentially different feature set, will be made available soon at dalicc.net.

The answer appears beside the form: Saved. The banner is on and every page shows it., Saved. The banner is off and no page shows it., or Nothing changed: the banner was already like this. Another server process of the same installation follows within five seconds.

The band is drawn in the warning colours of the palette the site is wearing. Leave the second switch off for a notice every reader must keep seeing, such as that the installation is a development version. Programs that use the API see the same text as notice in GET /v2, null while the banner is off, and notice_closable, true when visitors may close it.

A change is written to the audit log as banner.changed, with both switches and the text before and after. A save that changes nothing is not recorded.

What is saved here wins over the installation's own settings, DALICC_SITE_BANNER_ENABLED, DALICC_SITE_BANNER_TEXT and DALICC_SITE_BANNER_CLOSABLE (DEPLOYMENT.md). The sentence under the form says which of the two is in force.


15. Registration

/admin/registration decides how a person gets an account. It is Registration in the Settings group of the administration menu. The page has one form: the mode first, then the options that self-registration takes. Each option has a ? next to it; the fields of the identity provider (issuer, client id, secret, scopes and claim) have a line of help under them instead. Rest the pointer on it, focus it with the keyboard or press it, and a paragraph says what the option does and why it is or is not recommended. A screen reader reads the same paragraph with the button without anybody opening it.

Invitation only (recommended) is how the site has always worked and what it starts with. Nobody creates an account on their own; /register answers that accounts are given out by invitation. It is recommended because every account is then somebody a member vouched for, and a publisher name on a license belongs to a person the association can reach.

Self-registration opens /register. Invitations keep working exactly as before, so you can still invite an administrator or a collaborator. The options decide what state a new account starts in:

Option Default What it does Why
E-mail verification (recommended) on The account stays switched off until its owner opens a link sent to the address. The link works once, for 24 hours. Signing in with the right password before that sends a fresh link. Without it anybody can sign up with somebody else's address, and every mail the site sends afterwards goes to a stranger.
Administrator approval (recommended) on The account can sign in, compose and save drafts, but it publishes nothing until you approve it. A published license is permanent and public, so somebody should look at an account before its first one goes out.
Form check off The sign-up form carries the reCAPTCHA check the license composer uses. It keeps automatic sign-ups out, but the visitor's browser loads a script from Google, which may process what it collects outside the European Union. Verification and approval stop most automatic sign-ups without that.
Allowed e-mail domains empty Only addresses at these domains may sign up. A subdomain counts: ustp.at admits fh.ustp.at. Useful when the members come from a known set of institutions. Invitations are not affected.
Sign in with an identity provider (SSO) off People sign in with the account they already have at their organisation. See below. Nobody has to remember another password, and the provider has already confirmed the address.

With both verification and approval on, a new account first confirms its address and then waits for you. With neither, anybody can create an account with any address and publish at once; the page warns about that combination.

The approval queue is at the top of /admin/users whenever an account is waiting. Approve lets it publish and mails the owner. Reject mails the owner and then deletes the account the way /account/delete does: the name, the address and the password are replaced by placeholders and the drafts go. The queue also says how many sign-ups have not confirmed their address yet; those are deleted a week after their last link runs out, by the same periodic sweep that trims the logs. The audit entries are register.created, register.verified, register.duplicate, user.approved and user.rejected; the last two record in mailed whether the mail went out. On an installation that cannot send mail the queue says so, and after Approve or Reject the page says that the person was not told, so tell them yourself.

Sign in with an identity provider uses OpenID Connect with PKCE. Register this site at the provider as a confidential client with the redirect address the page prints (https://<your site>/login/sso/callback), then enter:

  • the issuer, the address the provider's discovery document hangs under, over https;
  • the client id and the client secret. The secret is encrypted before it is stored, like the Text-to-License keys, and only its last four characters are shown again. It can also stay in DALICC_SSO_CLIENT_SECRET;
  • the scopes, openid email profile unless the provider needs others; openid is required;
  • the claim that holds the e-mail address, usually email. The provider also has to report the address as verified (email_verified), or the sign-in is refused.

Test the provider, under the issuer, asks the issuer in the form for its discovery document (<issuer>/.well-known/openid-configuration) and saves nothing; the answer appears inside the provider's section. Saving with the provider switched on asks the same question first. When the address does not answer, answers with an error status, or serves a document that names another issuer or lacks the sign-in, token or userinfo address, nothing is saved and the reason stands under the issuer, for example "Nothing was saved. No discovery document at https://login.example.org/realms/members/.well-known/openid-configuration (the address did not answer)." That keeps a wrong issuer from reaching the login page, where every member would otherwise meet "The identity provider cannot be reached at the moment".

The login page and the sign-up page then show a button. An existing member is found by the address the provider reports, the first time, and by the provider's own identifier for them after that, so a changed address at the provider keeps the account. A new person gets an account under the options above, after one more page that asks for the name to publish under and the privacy notice confirmation; the verification mail is skipped because the provider has confirmed the address, and approval still applies. Under invitation only the provider signs existing members in and creates nobody.

The identity provider is the only way in refuses a member's password and hides the password sign-up. Administrators keep their password, so a provider that is down cannot lock the site. Leave it off unless every member has an account at the provider.

What stands in the way is printed above the form: verification switched on while the installation cannot send mail (the sign-up page then stays closed unless the provider can still sign people up), the form check without the reCAPTCHA keys, an identity provider without its secret, and a secret that cannot be stored because DALICC_SECRET_KEY is unset.

Every save is written to the audit log as registration.policy_changed with the fields that changed; a new or removed client secret appears as stored or removed, never as the value. Reset to the deployment defaults removes every stored choice, the secret included (registration.policy_reset). The defaults are the DALICC_REGISTRATION_* and DALICC_SSO_* settings in DEPLOYMENT.md. The Deployment defaults paragraph under the form says what they are on this installation: the mode, verification, approval, the form check, the allowed domains, the identity provider and whether it is the only way in.