docs(adr): ADR 0021 accepted, the assistant's boundaries (D7–D30)
The developer's decisions of 25 Oct, reviewing ADR 0021 whole: - What an assistant may see and do (D7–D12): the scope of each question (the parts ticked, the studyset, its tree, the library), looked up through read-only tools the app answers on the device; private studysets out of a wider scope unless switched on, and a warning whenever one's content would go to a remote model, Lumo included, as no provider keeps a question confidential while its model answers; changes only as drafts; progress never; text first, an image when ticked or asked for. - What it keeps, does and where it lives (D13–D18): conversations kept sealed with the person's own key; four features (questions about what is open, cards from text, explaining a miss, checking a written answer); grants and limits in halkyon_base, the adapters in a package of their own (halkyon_llm, BSD-3-Clause); the proposal's points 4–8 and 10 as written; the warning once per studyset and assistant; built for v0.21.0. - Sign-ups and operators (D19–D26): SIGN_UP open, approval or closed; the first operator by SQL, others named in the app; suspending an approved account; a wait screen with a matching code the operator checks with the person; 14 days, 10 waiting. - What people are told and what never goes (D27–D30): the app and a privacy page of each instance's own; others' posts only with their author's switch, a copy's licence being its agreement; an opaque ID per person for the provider. ADR 0022 and Learn's AGENTS.md now point to it as accepted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
c38dc69018
commit
c48d423619
1 changed files with 153 additions and 19 deletions
|
|
@ -103,7 +103,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
|||
- **The client's local migration** (in `apps/learn/halkyon_learn_client/lib/migrations/`, written by the same `create-migration`) creates the device's SQLite schema from the whole definition, so it also contains the server's own tables. Only the synced tables and the bookkeeping ever hold data on a device.
|
||||
- **Older apps** can't be made to update once published. Before the first sync of each launch the app asks whether the server still works with its build (`minimumAppBuild` in `instance.info()`, from `lib/src/instance/app_compatibility.dart` in the server, which Learn's configuration of halkyon_base carries; the build is the number after `+` in the app's version), and the sync engine refuses an app whose synced tables differ from the server's. Both end in `AppServerMismatchException` (halkyon_app), and the app says so: a banner on Home and the Sync card in Settings. Raise `minimumAppBuild` in the commit that breaks older apps (an endpoint they call changes or goes), to the build of the first app that works again; a change to the synced tables needs no raise. Every release raises the build number. It is 24 since GitHub went (ADR 0020, D36): builds up to 23 (v0.20.0) read whether an account has GitHub (`SignInWays.gitHub`), which is gone, so **the server without GitHub deploys only together with the release of build 24** (as the server with the module did with build 21, ADR 0014); Learn's own `instance.info()` stays, with every field, as released apps read `minimumAppBuild` there.
|
||||
- **Instances** (decided 2026-10-10, as Moshidon does): the device keeps an account on each instance it was used with, one in use at a time (halkyon_app's instances, ADR 0014; Learn gives them `learnFrame`, its words and look). `InstanceChoice` holds them (`instances.json` in the app's support folder); a new install chooses at first start (`FirstStartScreen`, under the wordmark on a cobalt wash: two tiles as Proton's account page shows its services, decided 2026-10-18, the build's own in one tap, with where it keeps people's data (below), and another instance, which unfolds the address to type, checked with halkyon_base's `instance.basics()`, which must name the app `learn` (`ServerpodInstanceChecker(app: 'learn')`: another Halkyon app's instance is refused), and only over HTTPS or plain HTTP on this machine (`Instance.isSecure`, ADR 0017: an address over plain HTTP elsewhere is refused before anything is sent there, and one kept from before is never opened, the app offering another instance or removing it; development builds keep their own over HTTP, as an emulator reaches the developer's machine as 10.0.2.2); `InstanceTile`: a ring from the primary colour to the tertiary, a glow and a tagline come in under the pointer, on the keyboard's focus, while pressed or open, and without a mouse the taglines always show), and one from before there was a choice goes on with the build's (`SERVER_URL`), as it has a `people/` folder. `InstanceGate` builds `Dependencies.create(instance, device)` (`main.dart`) for the one in use and builds it anew on a switch, the previous closed first; `DeviceParts` (version, sounds, Pomodoro platform, device settings) outlive switches. Each instance's people's folders and session are its own (`InstanceStorage`): the build's own instance keeps `people/` and Serverpod's own session key, so existing installs keep everything; others `instances/<name>/people/` and a key of their own. Settings › Switch account lists them (`showAccountSwitcher`): another one switches, Add account chooses another instance, and removing one not in use deletes its folder and stored session (the session stays valid on the instance until it expires). Signing out still ends only the account in use. **While nobody is signed in, the app asks how to start** (decided 2026-10-16, `StartScreen`; without anonymous accounts since ADR 0020, D25): on the instance in use, with SimpleLogin or a password, or on another instance; an instance out of reach is said so, with Try again, and one offering no way to create an account too. A first start leads there once the instance is chosen, and so do signing out and a session the server ended. An anonymous account's session, left from before, is deleted as the app starts. Settings names the account's instance ("Signed in on learn.halkyon.eu"). **Where an instance keeps people's data** (decided 2026-10-18): its operator says so in `deploy/.env` (`DATA_LOCATION`, `BACKUP_LOCATION`: `EU`, a country's code or a subdivision's, ISO 3166-2 such as `US-CA`, the backups also `none`, decided 2026-10-19; where the disks are, never which law governs, as a court reaches the operator wherever the server stands), halkyon_base's `instance.basics()` hands it on (Learn's `instance.info()` too), and the first start's tile and Switch account show it once the instance answers (`InstanceLocationBuilder`): the most specific flag of where the data is (`InstanceFlag`, from the `flag` package, which draws lipis' flag-icons: a subdivision's own where it has one, Scotland's, else its country's) and the words, as labels rather than sentences ("Data: European Union", "Data: Germany (EU)", "Backups: California, United States"), since "in" would need the article some countries take; a member country of the EU gets "(EU)" and a subdivision its country; countries are named by Unicode CLDR 48.2's names that halkyon_app bundles (`AssetCountryNames`, the same as the world map's, Hong Kong, Macao, Myanmar and Palestine by their short names), subdivisions by Unicode CLDR 48.2's, bundled in halkyon_app; `registerHalkyonAppLicenses` registers the flags' notice and that CLDR's, beside Learn's own for the map.
|
||||
- **Searching the web** (decided 2026-10-10; `feature/assistant/`; the hand-off to AI assistants and Claude's connector went on 23 Oct 2026, ADR 0022): a device setting, off unless the person turns it on (Settings › Web search), shows a field at the foot of Home (`AssistantBar`) and a button that unfolds into one (`AssistantButton`): at the lower left of the Library, across from Create studyset, at the left above a studyset's bottom bar (floating alone at the lower left from expanded windows on), and at the lower left while studying, above the buttons that answer the card (decided 2026-10-16, as Vaia places it). Above the field, chips offer the parts of what it is about that a search takes (`AssistantTopic.parts`, those `searchable`: a card's front, back, each hint, its explanation; never a studyset's name or description), none chosen, so only what the person picks goes along, each cut at 600 characters. `WebSearch` opens the chosen engine's results for the question and the chosen text (`SearchEngine`: Brave Search by default on desktops, Ecosia, DuckDuckGo, Startpage, Qwant, Google, Bing, or the person's own address with `%s`), or on Android hands them to the device's own web search (`ACTION_WEB_SEARCH` in `MainActivity.kt`, Brave Search where nothing answers). Its names keep "assistant": the assistant ADR 0021 proposes, on an instance whose operator pays for it, is to answer in the same field. Until then Halkyon calls no language model, and nothing on an instance reads people's content for one (ADR 0022): with API keys on a public instance anyone could drive up the bill, and each person decides what a model sees.
|
||||
- **Searching the web** (decided 2026-10-10; `feature/assistant/`; the hand-off to AI assistants and Claude's connector went on 23 Oct 2026, ADR 0022): a device setting, off unless the person turns it on (Settings › Web search), shows a field at the foot of Home (`AssistantBar`) and a button that unfolds into one (`AssistantButton`): at the lower left of the Library, across from Create studyset, at the left above a studyset's bottom bar (floating alone at the lower left from expanded windows on), and at the lower left while studying, above the buttons that answer the card (decided 2026-10-16, as Vaia places it). Above the field, chips offer the parts of what it is about that a search takes (`AssistantTopic.parts`, those `searchable`: a card's front, back, each hint, its explanation; never a studyset's name or description), none chosen, so only what the person picks goes along, each cut at 600 characters. `WebSearch` opens the chosen engine's results for the question and the chosen text (`SearchEngine`: Brave Search by default on desktops, Ecosia, DuckDuckGo, Startpage, Qwant, Google, Bing, or the person's own address with `%s`), or on Android hands them to the device's own web search (`ACTION_WEB_SEARCH` in `MainActivity.kt`, Brave Search where nothing answers). Its names keep "assistant": the assistant of ADR 0021 (accepted 25 Oct 2026, to be built for v0.21.0), on an instance whose operator grants it, is to answer in the same field. Until then Halkyon calls no language model, and nothing on an instance reads people's content for one (ADR 0022): with API keys on a public instance anyone could drive up the bill, and each person decides what a model sees.
|
||||
- **Where sync stands** is `SyncService.status` (syncing, the last sync that went through, the last failure; since the app started), which `SyncStatusBloc` (halkyon_app, provided by `PersonApp`) hands to the trouble dot on the Settings cogs (on Home and Profile; no tab carries it), the Sync card in Settings and the banner on Home. Only the last outcome counts: one sync that goes through clears a failure. It also counts the changes not on the server yet (`pending`): every save asks for a sync (`requestSync`), and a sync that goes through takes those saved before it started; the engine can't say, since the server only tells it at the start of a sync what it has. The count is kept in `sync_pending.json` in the person's folder (`PendingChangesStore`) and shown on the Sync card, beside whether a sync ever went through on this device (`syncedHere`, read as the person's data opens): until one has, an empty library says it is on its way from the instance rather than that there are no studysets (decided 2026-10-17: a first sync brings everything at once, at its end). The Library's Refresh and pulling it down sync now (`syncAndWait`, which a pull's spinner waits on).
|
||||
- **Serverpod's version moves together with `serverpod_offline_sync`**, which allows only a narrow range of Serverpod (v0.0.10: `>=4.0.4 <4.1.0`; v0.0.8 pinned v4.0.0 exactly, which the root `pubspec.yaml` overrode until 2026-10-09). Every package pins Serverpod's packages to one version, halkyon_base's too: upgrade them all together with the sync packages, halkyon_base and the CLI version CI generates code with (`SERVERPOD_VERSION` in `.forgejo/workflows/ci.yaml`), after reading the sync package's changelog. Never override a version the sync package doesn't allow without checking what changed in `serverpod_database`, which it builds on. Its shared part, `serverpod_offline_sync`, comes from our fork (the root `pubspec.yaml`'s `dependency_overrides`, `project-halkyon/serverpod-offline-sync` on code.halkyon.app, pinned to a signed commit of branch `halkyon/batch-inserts`, which stacks six branches): `halkyon/byte-payloads` (decided 2026-10-12: v0.0.10 compares byte payloads byte by byte through a UUID parse that throws for each byte, so one sync touching a few thousand cards blocked the server for minutes); `halkyon/inbound-merges` (2026-10-17: `onInboundMerge`, called only when changes from the remote peer were merged, which the app's sync uses; `onMergeSuccess` fires for a space only pushed to, so every repository read everything again after each save's sync); `halkyon/fast-deletes` (2026-10-17: a local delete skips the foreign key projection pass when it can't change it, `canSkipProjectionAfterLocalDelete`; the pass loaded nearly every synced row, 8 s for one delete in a library of 8,000 cards, 30 s for a card; not reported upstream yet, the developer's call); `halkyon/fast-merges` (2026-10-17: a merge only inserting new rows proven unable to change the projection skips its passes, `canLeaveProjectionUnchangedByMergedInserts`, 9.5 s to 60 ms for a rating another device made in such a library; and projections are rebuilt once after the synced tables changed, not on every session and merge, which fixes #150 below); `halkyon/fast-merge-deletes` (2026-10-17: a merge only deleting rows skips the pass when every visible row below them goes with them, `canLeaveProjectionUnchangedByMergedDeletes`: a card deleted on one device took the server 5.4 s to merge and each other device 4 s, now 35 and 99 ms); `halkyon/batch-inserts` (2026-10-17: such a merge writes its new rows up to 1,000 to a statement, and the insert proof takes a parent of the row's own table inserted before it, as nested studysets are: a first push of 8,000 cards took the server 35 to 41 s, now about 5; a device receiving it 60 s, now 44, as Serverpod's SQLite adapter still inserts row by row). Move the fork along when upgrading, and drop each change once a release has it.
|
||||
- **The source-code link**: both licences require offering the source of what an instance runs to the people who use it. `instance.info().sourceCodeUrl` names it (the operator's `SOURCE_URL`, else this project's repository, `upstreamSourceCodeUrl` in `learn_base.dart`; halkyon_base's `sourceCodeUrl`) and the app's About dialog shows it next to the licences and every package's notice (halkyon_app's `showAppAbout`, asking `Dependencies.sourceCodeUrl`, with Learn's credits as its own parts, `feature/about/`). Keep it working when changing the dialog or the endpoint.
|
||||
|
|
|
|||
Loading…
Reference in a new issue