halkyon/apps/todo/halkyon_todo_server/migrations/migration_registry.txt

31 lines
1.1 KiB
Text
Raw Normal View History

### AUTOMATICALLY GENERATED DO NOT MODIFY
###
### This file is generated by Serverpod when creating a migration, do not modify it
### manually. If a collision is detected in this file when doing a code merge, resolve
### the conflict by removing and recreating the conflicting migration.
20261019193547604
20261019201213254-profiles
20261019204122341-deletion-notices
20261020111950710-passwords
20261020125124942-sessions-ended
20261020185752014-recovery-proof-key
fix(auth): codes wait after ten wrong in a row, and the owner is told Whoever knew the password could guess codes at 10 an hour forever, unnoticed, each guess checked against every authenticator (the second reading's M4). Every code an account's second factor refuses after a right password (sign-in, a phrase's reset, a change) or from a signed-in device (removing an authenticator, new recovery codes, the code from one confirmed before) now counts, per account, from every network; after every tenth in a row no code is taken (codesWait, saying until when), an hour, then twice as long each time, a day at the most (longestCodeWait, second_factor_waits.dart): at a day, ten guesses a day still have about a 1 % chance a year against one authenticator, so the first wait mails the owner that someone knows their password. The instance keeps no address, so sign-in and the phrase's reset now send the address the device typed (and its language), used only for that mail after its hash is checked against the account's. A code accepted clears it all. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "second_factor_failures" ("id" bigserial PRIMARY KEY, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "waits" bigint NOT NULL, "waitUntil" timestamp, "updatedAt" timestamp NOT NULL) with a unique index on "authUserId": a new table, nothing touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 21:47:38 +02:00
20261020194229631-second-factor-waits
fix(auth): a session's wrong codes end the session, not the account's codes Wrong codes for removing an authenticator, new recovery codes or confirming one counted toward the account's waits (the re-review's third finding): a stolen session alone, without the password, could keep the owner's codes waiting for up to a day at a time, renewed without end, holding them out of signing in and of changing the password, the way to end that session, and nobody was mailed. Those codes now count per session, where the session is (the register's own, or a member's, which forwards the code): each try counted before its code is checked (one statement, so tries sent at once count each), a code accepted clearing the count, and the tenth wrong in a row ending the session (sessionEnded; the device says it was signed out). A try that checked no code (a refusal of something else) is taken back. The account's waits now count only codes after a right password, and the per-account ten tries an hour, which a session could fill as well, go. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "session_code_failures" ("id" bigserial PRIMARY KEY, "sessionId" uuid NOT NULL, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "updatedAt" timestamp NOT NULL) with a unique index on "sessionId": a new table, nothing touched. Its rows go with a code accepted, with the person, and hourly once their session is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 22:53:20 +02:00
20261020204619863-session-code-tries
20261020211225101-email-code-purpose
20261021100422779-session-ended-warning
20261021110200318-second-factor-paths
20261021134804476-reset-hold-outs
20261023181810436-oidc-module
feat(base): SimpleLogin accounts' keys without SRP, signing up in one call (ADR 0020, step 3) An account that signs in with SimpleLogin keeps its keys under a password that signs nobody in (D19): `account_password` keeps the password's own proof key (Ed25519, `passwordProofKey`) in place of SRP's verifier, which becomes nullable. The migration adds the column and drops the NOT NULL, nothing else. - `simpleLogin.signUp(attempt, handle, setup)`: someone new whose identity waits under the attempt gets, in one transaction, an account (identified), the link, a profile with the handle and the keys; then a session (D18). Refused, the identity still waiting, for a setup out of range and a handle refused as saving a profile refuses it. - `simpleLogin.checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network. - `simpleLogin.complete` says, for an account, whether it has its keys and its handle, and keeps the sign-in ten minutes under its attempt, the account as its caller: D20's confirmation for the changes to come. - `keys.begin`, `keys.unlock`: the keys for a signed-in device once the password is proven with its proof key, for a nonce, bound to the base URL the device addressed; wrong proofs counted per session as ADR 0017's. - An authenticator is enrolled only for a password that signs in. - Members ask the register for all of it; `RemoteRegister` and the instance API carry the four new operations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 21:49:44 +02:00
20261023192913500-simplelogin-keys
feat(base)!: sign-ups as the operator says, waiting accounts, operators (ADR 0021, step 1) SIGN_UP takes open (unset), approval or closed; anything else stops the server as it starts. Closed refuses a SimpleLogin sign-up before its handle is counted, an address sign-up only once the code has proved the address, so nothing tells whether an address has an account. Approval lets a new account wait, at most ten at once: no session is issued for a waiting or suspended account, only a pass (32 random bytes, kept as its SHA-256, 30 days, five per account) and four BIP-39 words the operator reads out to match it. The pass looks, forgets, and deletes an account that was never approved; nothing else. Operators hold the scope operator, given first by SQL (D32), then named in the app: InstanceOperatorEndpoint lists, approves and refuses waiting accounts, finds accounts, suspends them (ending their sessions), and names and takes back operators, never the last. account.operating() counts the waiting for the badge. Members ask the register (five new operations, a refusal kind signUp), and say closed while it is out of reach. Never-approved accounts go after 14 days in the daily pruning. The migration only adds the tables waiting_account and waiting_pass, so the server before it keeps working on the new schema. BREAKING CHANGE: a register and its members must run this version together: a member asks the register operations an older one doesn't know. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 15:04:21 +01:00
20261025135744931-waiting-accounts
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>
2026-10-25 18:07:01 +01:00
20261025165103193-assistants
20261026140330157-assistant-budgets
20261101000019144-projects
20261101000604776-tasks
20261101180755366-settings
20261106183802735-filters
20261106184947570-comments
refactor(base,learn): people's files kept by halkyon_base ADR 0043, D2, D4, D6 and D7, the first step towards Todo's attachments: Learn's file store moves into the module, for every server that keeps files, and Learn becomes its first host. halkyon_base gets the store as Learn had it: uploads into the caller's own space by SHA-256, downloads by a row the caller may read, each person's room (FILE_LIMIT_IDENTIFIED_MB, FILE_LIMIT_INSTANCE_MB, the operator's file_allowance, which moves into the module unchanged) and the pruning of files no live row names. It stays free of the sync engine: a host hands it a FileStore saying which of its rows name a file and what it takes (HalkyonBase.files); without one the files endpoint refuses. Its tests, with refusals, run on a stand-in host. Learn hands it learnFiles: its asset rows, read by their space's members, a readable file over 20 MB only as a PDF, up to 100 MB. Its app calls the module; its own asset endpoint stays for released apps alone, forwarding to the module in Learn's own models, whose names are on the wire. Its file tests still pass through it, end to end. Migrations: the module's creates file_allowance. Learn's server migration holds version rows alone, as tables are matched by name (ADR 0014); its device migration creates the module's empty table, as every module's. Todo's, Train's and Meditate's create the empty table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 00:11:36 +01:00
20261110225635732-file-store
feat(todo,app): files attached to tasks and comments Halkyon's ADR 0043, D1, D3, D5 and D8: any file up to 100 MB, attached to a task's description or to a comment, sealed as everything of Todo's. Server: the attachment table, synced and sealed as the others, with the sealed file's hash readable for the store; todoFiles hands halkyon_base its rows, readable by their space's members, and takes sealed files alone; the files no attachment names go daily. Its tests hold downloads to the row's space, refuse a readable file and prune what no live row names. Migration attachments: CREATE TABLE "attachment" with its indexes and the foreign key to its space, cascading as every synced table's. App: AttachmentRepository seals each file for its row, keeps it sealed and opened in the person's folder, uploads it after each sync and fetches it as it shows, both held back by Without files on mobile data but for a file tapped. Under the description and a comment's text, a row of them: pictures as thumbnails opening full-size, other files as chips opening in the system's app, else saved; the paperclip beside the description and in the comment's composer adds them, the composer holding them until it is sent. A task deleted takes its files and its comments' along, a comment its own. halkyon_app opens a file in the system's app (FileOpener): on Android through its plugin and a FileProvider of its own class, sharing the cache's halkyon_opened/ folder alone; on the desktop as a file: URI. Whats-New: Attach files to a task's description or a comment with the paperclip: pictures show as thumbnails, other files open in their app. Whats-New-De: Hänge mit der Büroklammer Dateien an die Beschreibung einer Aufgabe oder an einen Kommentar: Bilder erscheinen als Vorschau, andere Dateien öffnen sich in ihrer App. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 01:17:22 +01:00
20261110231607673-attachments
feat(base,learn): spaces people share, kept by halkyon_base ADR 0044, step 2 (D1, D2, D11, D13, and D15 to D17, decided today). halkyon_base keeps the spaces several people share, for every app: shared_space, its members with their roles (Owner, Editor, Viewer), and invitations by handle waiting for someone with no account on the service yet. The sharedSpace endpoint creates a space, lists its members, invites by handle, changes roles, removes members, hands the space on, leaves and deletes it. Who may do what: - Only people with an identity reach the endpoint. - A space the caller isn't in can't be told from one that doesn't exist. - Members are named by their membership, never by their account's ID. - Only the Owner manages the members, unless the space lets members invite. - The Owner's role passes only by handing the space on, and the Owner leaves only naming who takes it on. - An invitation counts as an answer about a handle, as it tells whether one exists. Every rule has a test asserting the refusal. The module stays free of the sync engine: a host hands it a SpaceGrants, and every call runs in the module's transaction, so the engine's grants follow the roles or nothing changes. The Owner and an Editor write; a Viewer reads. Learn hands over SyncSpaceGrants on OfflineSyncSpaces. Todo hands over none yet, so its server refuses until its projects are shared. Deleting an account hands each space the person owns to the Editor who joined first, else the Viewer who joined first. A space with no other member goes. This runs before the host's own deletions. A person's first sign-in on a service makes their waiting invitations memberships, in the same transaction that makes them a person there. Migrations: the module's and every host's (Learn, Todo, Train, Meditate) create the three tables, their indexes and the two cascading foreign keys to shared_space, and nothing else. The old server keeps working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-11 10:33:12 +01:00
20261111091721303-shared-spaces