feat(base,learn,todo): the operator's assistants, and the instance's privacy page (ADR 0021, step 2)
The server half of the assistants (points 4 to 8, D27 to D46): the operator sets language models in deploy/assistants.env, grants them to people with limits, and the module forwards their questions with the operator's key, counting what each used, never what was asked. - assistantsFrom: ASSISTANTS, ASSISTANT_ID_KEY, ASSISTANT_<ID>_NAME, _PROTOCOL (anthropic, openai, openai-responses), _BASE_URL, _MODEL, _KEY, _IMAGES, _PRICE_INPUT and _PRICE_OUTPUT (per million tokens), ASSISTANT_CURRENCY, ASSISTANT_OWN_KEYS; anything unusable stops the server as it starts, naming the variable. - The assistant endpoint (identified): offered, agree (D40), ask as a Serverpod stream (D35): the text as the model writes it, then the whole turn, sent once the request is counted; figures, the caller's own. Checked in order: at most 2 questions on their way per person (tooManyAtOnce), the grant, the agreement, limits in the pricing's unit now (limitOutdated), the month's and the day's limits (with when they reset, UTC), the question (malformed, imagesNotRead, tooLarge past 400,000 characters, an image over 3,932,160 bytes or more than 20 images); output capped at 16,384 tokens. Each request carries the person's opaque ID (HMAC-SHA256 under ASSISTANT_ID_KEY, D30). Once sent, the provider's reply is read to its end even if the app cancels, and counted on a session of its own; a provider's failure is logged by kind and status, told as providerKey, providerBusy (with its wait), providerRefused or providerFailed. - The assistantOperator endpoint (identified and operator, on the register): setUp, grants, grant (assistantNotSet, invalidLimit, notFound), revoke, figures of a UTC month. - Tables assistant_grant and assistant_figures (per person, assistant and UTC day), in the module's, Learn's and Todo's migrations. The daily pruning keeps this month and the two before (forgetOldAssistantFigures, D41); deletePerson deletes both. A member offers no assistant (D43). - instance.basics() gains assistantOwnKeys. - /privacy (D39, addPrivacyPageRoute, on the register's web server, before the profile pages' route): made from the instance's settings, English or German, no JavaScript: who may sign up, where the data and backups are, what the module keeps (the account, sessions, IP addresses, the server's log), the host's privacyPagePoints (Learn's: studysets synced, private ones encrypted on the devices with what stays readable, what is published, dictionary lookups, the learner count), each assistant with its model and host, what goes and what is counted, and whether personal keys are allowed. Never a key. Tests: 70 on the assistants (grants, limits, refusals in order, streaming, cancelling, counting, concurrency, the operator's denials, deletion, pruning), 19 on the settings, 8 on the privacy page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
038aca5439
commit
dbae802915
1 changed files with 56851 additions and 33 deletions
|
|
@ -24,7 +24,9 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
|
|||
| `account` | `delete` (with the password's proof where the account has one, `keysProof` for one that signs in with SimpleLogin), `signInWays`, `operating`: how many accounts wait, for an operator (Halkyon Learn's ADR 0021, D34; null for anyone else) | signed in |
|
||||
| `waiting` | with a waiting account's pass (Halkyon Learn's ADR 0021, D31): `look` (waiting, suspended, approved, which takes the pass, or unknown), `deleteAccount` (a new account never approved), `forget` | without a session |
|
||||
| `instanceOperator` | `waiting`, `approve`, `refuse` (a new account, deleted), `accounts` (by the start of a handle, at most 50), `suspend` (every session ending, the account waiting anew; never an operator), `operators`, `appoint` (by handle, approving one that waits), `takeBack` (never the last operator) | `identified` and `operator`, on the register |
|
||||
| `instance` | `basics`: the app it serves, whether codes by mail, password sign-in and SimpleLogin are on, who may sign up (`SIGN_UP`), the source link, the oldest app build, where data and backups are | anyone |
|
||||
| `instance` | `basics`: the app it serves, whether codes by mail, password sign-in and SimpleLogin are on, who may sign up (`SIGN_UP`), the source link, the oldest app build, where data and backups are, whether people may add assistants with their own keys | anyone |
|
||||
| `assistant` | Halkyon Learn's ADR 0021, step 2: `offered`, the assistants granted to the caller that are set, with what goes where, what is left of their limits and whether they agreed (none on a member, D43); `agree(assistant)`, to what goes where, its provider as it is now (D40); `ask(question)`, a stream: the answer's text as the model writes it, then the whole turn with why it stopped and its tokens, once counted (D35, D38); `figures`, the caller's own, month by month | `identified` |
|
||||
| `assistantOperator` | `setUp`: the assistants set, without their keys; `grants`, by handle; `grant(account, assistant, dailyLimit, monthlyLimit)`, in money with prices, else tokens, the agreement kept when the limits change; `revoke`; `figures(month)`: everyone's of a UTC month, per person and assistant | `identified` and `operator`, on the register |
|
||||
| `profile` | `mine`, `save`, `checkHandle` (with every answer about a new handle, at most 30 an hour per person and 60 per network), `checkLinks` (at most 10 an hour), `setAvatar`, `removeAvatar`, `remove` (what it shows goes, the handle stays, private again: Halkyon Learn's ADR 0020, D27): the caller's own public profile | `identified` |
|
||||
| `profiles` | `byHandle`: anyone's public profile once they went public, whatever the handle's case | signed in |
|
||||
|
||||
|
|
@ -123,8 +125,35 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
|
|||
itself. Never the `Forwarded` header, which Caddy passes on as the visitor wrote it,
|
||||
so never Serverpod's `remoteInfo`. An IPv6 visitor counts by their /64
|
||||
(`networkKeyOf`), which one line or server holds whole.
|
||||
- **Assistants** (Halkyon Learn's ADR 0021, points 4 to 8, D27 to D46): the operator's
|
||||
language models, set in `deploy/assistants.env` (`assistantsFrom`, read as the server
|
||||
starts, anything it can't use stopping it), each through halkyon_llm with the
|
||||
operator's key; nobody has one until an operator grants it, with limits per UTC day
|
||||
and month (`assistant_grant`). `ask` checks, in order: at most `maxQuestionsAtOnce`
|
||||
(2) on their way per person (`tooManyAtOnce`), the grant (`notGranted`), the
|
||||
agreement (`notAgreed`), limits set in the unit the assistant's pricing has now
|
||||
(`limitOutdated`), the month's limit, the day's (`limitReached`, with when it
|
||||
resets), then the question itself (`requestOf`: `malformed`, `imagesNotRead`,
|
||||
`tooLarge` past `assistantQuestionMaxCharacters`, `assistantImageMaxBytes` or
|
||||
`assistantQuestionMaxImages`); the output is capped at `assistantOutputCeiling`. Each
|
||||
request carries the person's opaque ID (HMAC-SHA256 under `ASSISTANT_ID_KEY`,
|
||||
`personIdOf`, D30), never their account. Once sent, the provider's reply is read to
|
||||
its end whatever the app does and counted on a session of its own
|
||||
(`assistant_figures`: requests, tokens and cost per person, assistant and UTC day),
|
||||
before the last piece goes, so the next question is checked against it; the
|
||||
provider's failure is logged with its kind and status, never a question. The figures
|
||||
are kept for the month and the two before it (`forgetOldAssistantFigures`, daily),
|
||||
and go with the account (`deletePerson`). Nothing of a question or an answer is kept.
|
||||
- **The privacy page** (Halkyon Learn's ADR 0021, D39; `addPrivacyPageRoute`, on the
|
||||
register): `/privacy`, made from the instance's settings, in English or German, no
|
||||
JavaScript, no cookies: who may sign up, where data and backups are, what the module
|
||||
keeps of a person (the account, sessions, IP addresses, the server's log), the
|
||||
host's `privacyPagePoints`, each assistant with its model, host and whether it reads
|
||||
images, what goes, what is counted, and whether people may add their own keys. Never
|
||||
a key.
|
||||
- Refusals are `PasswordRefusedException`, `EmailSignInRefusedException`,
|
||||
`ProfileRefusedException`, `SignUpRefusedException` and `OperatorRefusedException`,
|
||||
`ProfileRefusedException`, `SignUpRefusedException`, `OperatorRefusedException` and
|
||||
`AssistantRefusedException`,
|
||||
each with a reason; `ProfileNotFoundException` where there is no profile;
|
||||
`AccountWaitingException` in a session's place.
|
||||
- For the host's own code, `package:halkyon_base_server/base.dart`: the configuration,
|
||||
|
|
@ -138,8 +167,10 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
|
|||
(`profileOf`, `profilesOf`, `personWithHandle`, `fixHandle`; `saveProfile` for its
|
||||
tests), `accountDomain`, `profilePageUrl`, `addProfilePageRoute`, the register
|
||||
(`AccountRegister`, `AccountsMode`, `addInstanceRoutes`, `deliverDeletionNotices`,
|
||||
`deliverSessionNotices`, `forgetPeopleTheRegisterForgot`, `issueSession`), and the
|
||||
models. A host never reads
|
||||
`deliverSessionNotices`, `forgetPeopleTheRegisterForgot`, `issueSession`), the
|
||||
assistants (`assistantsOf`, `useAssistants` and `useLlmClient` for tests,
|
||||
`assistantUsageCounted`, `forgetOldAssistantFigures`), `privacyPage`,
|
||||
`addPrivacyPageRoute`, and the models. A host never reads
|
||||
the profiles' table itself.
|
||||
|
||||
## The instance's register (ADR 0016)
|
||||
|
|
@ -452,6 +483,12 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
german: 'Auf Halkyon Todo, einer Aufgabenliste:',
|
||||
link: Uri.parse('https://halkyon.app/todo'),
|
||||
),
|
||||
privacyPagePoints: [ // what the privacy page says of the app's data
|
||||
(
|
||||
english: 'Your to-dos are kept to sync them between your devices.',
|
||||
german: 'Deine Aufgaben werden aufbewahrt, um sie abzugleichen.',
|
||||
),
|
||||
],
|
||||
// The register, or a member of the one the operator names (ADR 0016).
|
||||
accounts: AccountsMode.fromEnvironment(),
|
||||
),
|
||||
|
|
@ -464,11 +501,12 @@ Built on the three parts above, on the register (members forward every step thro
|
|||
|
||||
Serve the calls between the instance's services on its web server:
|
||||
`addInstanceRoutes(pod)`; on the register, SimpleLogin's forwarding page:
|
||||
`addSimpleLoginCallbackRoute(pod)`; and the profile pages: `addProfilePageRoute(pod)`,
|
||||
after any literal routes of its own.
|
||||
`addSimpleLoginCallbackRoute(pod)` and the privacy page: `addPrivacyPageRoute(pod)`;
|
||||
and the profile pages: `addProfilePageRoute(pod)`, after any literal routes of its
|
||||
own, the privacy page's too.
|
||||
|
||||
Then schedule the pruning in a future call of the host's own: daily
|
||||
`pruneAnonymousPeople`, `forgetLapsedResets` and `forgetPeopleTheRegisterForgot` (a member's), hourly
|
||||
`pruneAnonymousPeople`, `forgetLapsedResets`, `forgetOldAssistantFigures` and `forgetPeopleTheRegisterForgot` (a member's), hourly
|
||||
`forgetOldSignUpAttempts`, `forgetExpiredEmailCodes`,
|
||||
`forgetUnconfirmedAuthenticators`, `forgetUnfinishedSimpleLogins` (the
|
||||
register's), `deliverDeletionNotices` and `deliverSessionNotices` (the register's). Each marked as one mode's does nothing in
|
||||
|
|
@ -508,6 +546,9 @@ an account, `open` (unset), `approval` or `closed`, anything else stopping the s
|
|||
`SOURCE_URL`, `DATA_LOCATION`, `BACKUP_LOCATION`: where the instance keeps people's data
|
||||
and its backups, each `EU`, a country's code (`DE`) or a subdivision's (ISO 3166-2,
|
||||
`US-CA`, `DE-BY`), the backups also `none`; where the disks are, not which law governs;
|
||||
the assistants' (`ASSISTANTS`, `ASSISTANT_ID_KEY`, `ASSISTANT_<ID>_…`,
|
||||
`ASSISTANT_CURRENCY`, `ASSISTANT_OWN_KEYS`), from `deploy/assistants.env`, its
|
||||
example commented, on the register alone (a member offers none);
|
||||
`TRUSTED_PROXIES`, the addresses of the reverse proxies in front of the server (Caddy's
|
||||
on its compose network, which Halkyon Learn's `deploy/compose.yaml` sets), separated by
|
||||
commas or spaces: without, every visitor behind the proxy shares each per-network limit;
|
||||
|
|
|
|||
Loading…
Reference in a new issue