From c93f8b40d96f04e1411dd0d03b7a75f027a1e7ae Mon Sep 17 00:00:00 2001 From: spacedandy-agents-simplex <342056592+spacedandy-agents-simplex@users.noreply.github.com> Date: Fri, 23 Oct 2026 01:12:28 +0000 Subject: [PATCH] docs: apply review round 17 to the several-directories plan --- plans/2026-10-22-directories-in-operators.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plans/2026-10-22-directories-in-operators.md b/plans/2026-10-22-directories-in-operators.md index b2a4bfc7bb..7a09753c59 100644 --- a/plans/2026-10-22-directories-in-operators.md +++ b/plans/2026-10-22-directories-in-operators.md @@ -2,7 +2,7 @@ Page for ep: https://claude.ai/artifact/YRpeaz3JcJ72onsRYy7n5G -Run: 009-directory-operators · Repo: simplex-chat · Estimated diff: ~4,950 changed lines in four pull requests (core ~1,050, directory ~600, iOS ~1,700, Kotlin ~1,600, CI included) · Bases and order: section 6 +Run: 009-directory-operators · Repo: simplex-chat · Estimated diff: ~4,950 changed lines in four pull requests (core ~1,050, directory ~600, iOS ~1,700, Kotlin ~1,600, CI included; rough, perhaps a third either way, and the Kotlin figure may be low by ~200 for the desktop CI helpers slice K10a builds, which iOS already has) · Bases and order: section 6 ## Summary: what ep decides @@ -41,7 +41,7 @@ Apps (pull requests 3 and 4; not compiled in the container, checked on the runne - AC13: CI no longer edits app sources and never reaches the live SimpleX Directory: a scripted step meets the SimpleX Directory preset turned on only to press Cancel in an alert that names it; whenever a test backend runs, every Claude session starts with the preset off and the test directories added; and every prompt, those used without a backend included, forbids Enable or Search in an alert that names SimpleX Directory. It checks on the runners, in two steps: - With the consent and fan-out slice (8a): the test backends start two directories, "CI Directory A" and "CI Directory B", B listing a few of A's entries (a shared channel, a shared ordinary group, two entries with large images that come one per page, so B has a second page for the turn-off check, and what the Claude cases need), each logging the service requests it receives. First, with the preset still on and nothing sent: turn the switch on in Privacy settings, search, and check that the second alert names only SimpleX Directory and that Cancel leaves no spinner and no rows. Then turn the preset off, turn the switch off in Privacy settings and add A in Settings ("Test directory" passes); today's one-directory checks run with A alone, their texts unchanged ("Search failed, tap to retry", "Load more · N left"). Then add B, turn the switch off again, and check: the first alert names both "(added by you)"; results from both arrive. Then that a directory turned off receives nothing, by A's request count, which first must rise: by one after "Test directory" for A and by one after each search with A on, or the check fails (so a missing log line or a wrong count fails, not passes). Search a text both list over more than one page (A's count rises), turn A off in Settings, tap Load more (B's next page comes; A's count unchanged); turn A on, stop A, search (a retry row shows: the generic one until 8b, A's own after), start A and wait until A's log shows the request queued while it was stopped, failing after 60 s, read A's count, turn A off, tap the retry row (it goes; A's count unchanged); search again (no row and no alert names A; A's count unchanged); turn A back on. - With the submenu slice (9): the shared channel's `group_profiles.short_descr` changed in B's database (sqlite3, while B is stopped, checked to survive B's restart), since a directory sends a channel's own short description; with A and B on: one list with the shared channel once, with A's description; the shared ordinary group twice, A's row first, each submenu naming its own directory; "about N left"; with B stopped, B's own retry row and A's results kept, then B started again; the submenu of the shared channel listing both; footer rows in both list layouts. - - On a device or by hand (each needs agreeing to the live SimpleX Directory, which a runner must not reach): the one-time upgrade step, and forgetting on switch-off and on turning a preset off. + - On a device or by hand (each needs agreeing to the live SimpleX Directory, which a runner must not reach): the one-time upgrade step, and forgetting on switch-off and on turning a preset off. Before slice 8a, with the two test directories: Open on an ordinary group's row from one directory after joining it through the other's link (open question 15's inference), so the apps' work knows whether a second join happens. ## 2. Non-goals @@ -101,7 +101,7 @@ New mechanisms, each with its requirement: - Merge in `updatedUserServers`: every preset directory of the group's operator matched by address to the group's stored rows (kept as stored, so a renamed preset keeps its stored name, as relays do) or new, plus the group's stored custom rows: what `updatedRelays` does for relays (Operators.hs:434-447), so slice 1 turns its body into one function over a list's presets, an address accessor and the stored and new wrappers, which relays and directories both call. Grouping by `domains` likewise: `addChatRelay` (:516-521) becomes one function over a domains accessor and an add function, as `addServer` already is for servers (:510-513). - Validation: `USEDuplicateDirectoryAddress {duplicateDirectory :: Text, duplicateAddress :: ShortLinkContact}` over the request's groups — so the settings screens can mark a duplicate. A request without directories carries an empty list, so it can't raise this error; no other user's directories are checked (other users get only missing-server errors today). No "no directories" warning: an app shows that note from its own data (section 4.3), which needs no new case in the apps' closed warning types and no "For chat profile X:" warnings about other profiles. - A directory's name is normalised wherever app input reaches the core: the `APISetUserServers` and `APIValidateServers` handlers pass each requested directory's name through `mkValidName` (Commands.hs:6948 / 6968) before saving or validating, so a duplicate error names the directory as it would be stored; an empty result is stored as "", and the apps show "Unnamed directory" where the name is empty (not the address's host, which is its router and can look official, section 4.7). The name comes from the directory's own profile and is shown in the consent alert, so this bounds what a directory can put there (section 5.4). -- A command that sends a request only to a directory in use: `APISendDirectoryRequest {userId, address, requestTimeout, request}` (`/_directory_request
[timeout=] `) looks the address up among that user's directories (`sameShortLinkContact`), refuses with a command error unless it finds one enabled and not deleted, and otherwise sends the request as `/_service_request` does (`sendServiceRequestTo`, Commands.hs:1508-1509 and :5969 on master), answering with the same response, without parsing the request. The apps send every search through it; "Test directory" and "Test servers" keep using `/_service_request`, since a tested address may not be saved yet and they send no search text. It puts the "in use" half of section 3's invariant in the core, tested in Haskell, so a bug in how an app chooses directories can't send search text to a directory the user turned off or never added. `/_service_request` stays open for "Test directory" and for an older paired desktop of the search-only release (section 5.3), so the core doesn't guard those; making `/_service_request` also refuse an address that is one of the user's directories turned off would close the older-desktop case for ~20 lines, at the cost of refusing "Test directory" on a turned-off directory's screen (not planned). Undocumented, like the servers commands (in `undocumentedCommands`), so the generated bot API files stay the same. +- A command that sends a request only to a directory in use: `APISendDirectoryRequest {userId, address, requestTimeout, request}` (`/_directory_request
[timeout=] `) looks the address up among that user's directories (`sameShortLinkContact`), refuses with a command error unless it finds one enabled and not deleted, and otherwise sends the request to the address of the row it matched (`CTShortContact (CTLink storedAddress)`, since `sameShortLinkContact` compares servers by host and port only), as `/_service_request` does (`sendServiceRequestTo`, Commands.hs:1508-1509 and :5969 on master), answering with the same response, without parsing the request. The apps send every search through it; "Test directory" and "Test servers" keep using `/_service_request`, since a tested address may not be saved yet and they send no search text. It puts the "in use" half of section 3's invariant in the core, tested in Haskell, so a bug in how an app chooses directories can't send search text to a directory the user turned off or never added. `/_service_request` stays open for "Test directory" and for an older paired desktop of the search-only release (section 5.3), so the core doesn't guard those; making `/_service_request` also refuse an address that is one of the user's directories turned off would close the older-desktop case for ~20 lines, at the cost of refusing "Test directory" on a turned-off directory's screen (not planned). Undocumented, like the servers commands (in `undocumentedCommands`), so the generated bot API files stay the same. - `copyServers` (Commands.hs:501-511) copies directories to a new profile as new rows, as it copies relays. - The CLI view gains a "Directories" part in `viewUserServers` (View.hs:1668-1674). @@ -117,7 +117,7 @@ As drawn, built from `ChatRelayView`'s shared pieces, not a copy of it: - Board 11: Your servers gets a "Directories" section with delete, and "Directory" after "Chat relay" in Add server. - Board 12: New directory: "Your directory address", "Your directory name" (read-only, filled by the test), "Test directory" with its result, "Use for search". Added below the address: "The directory receives the text of each search, without your profile." — what a user is told about a directory they add, and the consent for it (section 4.4). - A directory's address must be a short contact link (the type is `ShortLinkContact`; a full link fails to parse and would fail the whole servers save, whose validation error the app only logs, NetworkAndServers.swift:361-363): `validDirectoryAddress`, like relays' `validRelayAddress` (ChatRelayView.swift:43-51 at 4afce4931) but also requiring the short form, since a contact link parses as a contact address in both forms (Markdown.hs:524-543), is checked before "Test directory" and before adding, failing with "Not a directory address"; slices 7a and K7a check a full link is refused. -- "Test directory" runs two steps in the apps: the address's connect plan (`APIConnectPlan`; it must be a contact address: ok, known, contact via address, or connecting all pass, own link fails; the name comes from the link data's profile, or from the known contact's profile when the user already has the directory as a contact, as many do after registering a group, Commands.hs:4780-4781 at 4afce4931) and a `tools/list` request in ep's envelope, of the same fixed shape as a search (section 4.4), following `nextCursor` for at most 3 pages and stopping at the first page that lists it; the answer must list the directory tool, `simplex.directory.search`, whose `inputSchema` has a `searchText` property, or the test fails with "This address offers no directory search". A failure says which step failed: "Not a contact address", "No answer from the directory", "This address offers no directory search". No search text is sent, and no core command is added, so the core stays unaware of the directory's JSON. "Test servers" on the operator and Your servers screens tests enabled directories the same way, whatever the Directory search switch says. On an operator screen it stays inside the operator-enabled part, as today, so while an operator's servers are off its directories are tested from their own screens. +- "Test directory" runs two steps in the apps: the address's connect plan (`APIConnectPlan`; it must be a contact address: ok, known, contact via address, or connecting all pass, own link fails; the name comes from the link data's profile, or from the known contact's profile when the user already has the directory as a contact, as many do after registering a group, Commands.hs:4780-4781 at 4afce4931) and a `tools/list` request in ep's envelope, of the same fixed shape as a search (section 4.4), following `nextCursor` for at most 3 pages and stopping at the first page that lists it; the answer must list the directory tool, `simplex.directory.search`, whose `inputSchema` has a `searchText` property, or the test fails with "This address offers no directory search". A failure says which step failed: "Not a contact address", "No answer from the directory", "This address offers no directory search". No search text is sent, and no core command is added, so the core stays unaware of the directory's JSON. "Test servers" on the operator and Your servers screens tests enabled directories the same way only while Directory search is on (PRIVACY.md:203: nothing that isn't directly required is sent without the user's consent); with search off, a directory is tested only by its own screen's "Test directory". On an operator screen it stays inside the operator-enabled part, as today, so while an operator's servers are off its directories are tested from their own screens. - When no directory is in use, Network & servers says "No directory is in use, so search finds only your chats." beside its other notes, computed in the app. ### 4.4 Search with several directories (boards 13–17) @@ -437,7 +437,7 @@ Verified on the desktop-linux and android-emu workflows. ## 9. Open questions -1. **This design or the service layer first** (ep). What decides (section 12): whether more kinds of services users pick from a list are coming (you wrote you expect "all the other services we need", starting with telemetry, which here is one receiver per operator, not a list), and whether the core should also build and read every request (the services design) or only check where it goes (this design's request command, section 4.2). Recommended: this design, unless you expect other kinds of services users pick from a list. It is ~450 lines smaller now (~4,950 against ~5,400), drawn, changes no profile format, needs less settled in your draft, reuses every layer relays built, speaks your envelope from the start, refuses in the core these apps' searches to anything but a directory in use, and keeps storage, search, consent, the screens' places and the request format if services come later (~1,400 lines, or ~2,750 with the core building requests and the `protocols` declaration; section 4.6.3). Its cost, if such services do come: ~6,350–7,700 lines in all against ~5,400, and a second settings key carried for a release. If the services design: ~450 more lines now and two new core commands, which buy one parser for every request in the core and the `protocols` key in profiles (work your draft already plans for AI agents, as that plan notes); its policy of turning a directory off with its operator's servers is separate and can be decided either way in either design (open question 9); the search side of this plan (section 4.4) carries over. +1. **This design or the service layer first** (ep). What decides (section 12): whether more kinds of services users pick from a list are coming (you wrote you expect "all the other services we need", starting with telemetry, which here is one receiver per operator, not a list), and whether the core should also build and read every request (the services design) or only check where it goes (this design's request command, section 4.2). Recommended: this design, unless you expect other kinds of services users pick from a list. The two first releases are about the same size (~4,950 against ~5,400: the 450-line gap is inside both estimates' error), so size doesn't decide; this design is drawn, changes no profile format, needs less settled in your draft, reuses every layer relays built, speaks your envelope from the start, refuses in the core these apps' searches to anything but a directory in use, and keeps storage, search, consent, the screens' places and the request format if services come later (~1,400 lines, or ~2,750 with the core building requests and the `protocols` declaration; section 4.6.3). Its cost, if such services do come: ~6,350–7,700 lines in all against ~5,400, and a second settings key carried for a release. If the services design: ~450 more lines now and two new core commands, which buy one parser for every request in the core and the `protocols` key in profiles (work your draft already plans for AI agents, as that plan notes: the draft's planned `mcp_call` uses Service RPC only for an address that declares it, agent-protocols.md:458, so until the key exists AI agents' planned tools won't reach this design's directories); its policy of turning a directory off with its operator's servers is separate and can be decided either way in either design (open question 9); the search side of this plan (section 4.4) carries over. 2. **The core pull request to master right after question 1** (ep). Recommended: yes, if question 1 is this design, and before directory search is released if timing allows: then every phone with directory search already answers with its directories, which removes question 3's first cost. It is invisible to users and settles storage, presets and compatibility before the apps start. Merging it into a release freezes the migration and writes a SimpleX Directory row into every profile, so a later change of storage (a services table) is a second migration. If not: it waits, and the apps' work waits for it. 3. **Release order** (ep and the user). Recommended: ship directory search when it is ready, and several directories in a later release. Shipping them together delays directory search, already reviewed, by the core, directory and both apps' pull requests (~4,950 lines and two runner passes), while several directories add little until other directories exist. Costs of shipping separately, accepted: a newer desktop paired with a phone of the search-only release can't search through it (gone if the core lands before that release, question 2); an older desktop paired with a newer phone sends its searches to SimpleX Directory even if the phone's user turned it off (section 5.3); the consent text changes in two releases; the one-time consent step is needed; SimpleX Directory answers two request forms for as long as the search-only release is in use. If together: none of these, and directory search waits. 4. **Consent with several directories.** Recommended: section 4.4 (one switch in app preferences; remembered operator directories only; a user-added directory agreed to by adding it; a second alert for a preset not yet agreed to). The alternative is a consent flag per directory in the core, in the `directories` table. What it would fix: consent per profile, as directories are, so saving one profile's servers never re-asks in another; consent dropped with the table on a downgrade, so the downgrade-and-upgrade case of section 4.4 can't send without a question; the user's own directories could be remembered too without their addresses in app preferences, outside the encrypted database (hidden profiles' included); and consent tested in Haskell. What it would cost: the flag in `/_servers` and the migration, decided before the core pull request merges; turning the device-wide switch off must clear every profile's flags (a core command, or a save per profile); in a remote-desktop session the phone's flags would decide the desktop's searches, while the desktop's switch is its own today; and every place that saves servers must carry the flag through. Separately: a new profile copies the user's own directories in use, so they receive the new profile's searches without an alert (section 4.4); if you'd rather a new profile, hidden ones especially, ask first, it copies them turned off (the user turns each on, which agrees to it). @@ -445,13 +445,13 @@ Verified on the desktop-linux and android-emu workflows. 6. **Order and arrival.** Recommended: section 4.4 (settings order; a directory's rows appear once every earlier one has answered, failed or passed 15 s; the earliest directory's entry for a shared link; footer rows once the load is complete). Alternatives: arrival order (no waiting, but the fastest directory's rows and claims come first, and the order changes from search to search); placing rows in settings order as they arrive (rows move under the user's finger); waiting for all (the slowest holds every result for up to 15 s, the same bound without showing anyone's results early). 7. **"Test directory".** Recommended: in the apps, the connect plan for the name plus a `tools/list` request that must list the directory tool. If you'd rather have a core command like "Test relay": the core learns the request's shape. 8. **No directory in use.** Recommended: "Search in Directory" hides, the Search key searches nothing beyond your chats, and Network & servers says so. If not: the row stays and a tap says no directory is in use. -9. **The operator switch and directories** (ep: SimpleX Directory's terms are part of SimpleX Chat's conditions, PRIVACY.md:124-126, so this is a policy call). Recommended: a directory's own switch decides; an operator's "Use servers" switch is about its servers for messages, and the operator screen shows its Directories section also while its servers are off. Then a new user who chose only Flux at onboarding, or someone running their own servers with both operators off, still finds SimpleX Directory, named in the consent alert before their first search; cost: a user who turned an operator's servers off can still send searches to its directory once they agree to it in the alert, and its section shows on a screen whose relays are hidden. If turning an operator's servers off also stops its directories (as channel creation skips a disabled operator's relays; its argument: SimpleX Directory's terms are part of the operator's conditions, which the user accepts by turning it on): every user without the SimpleX Chat operator, Flux-only users and self-hosters included, has no directory search; they can't add SimpleX Directory themselves (its address collides with the hidden preset row); the only way back is turning SimpleX Chat's servers on, which can mean accepting its conditions and using its servers again; a user who searched in the search-only release with that operator off loses search on the upgrade; and a directory a later version presets under a turned-off operator stops being searched. The services design takes that other answer and enforces it in the core. +9. **The operator switch and directories** (ep: SimpleX Directory's terms are part of SimpleX Chat's conditions, PRIVACY.md:124-126, so this is a policy call). Recommended: a directory's own switch decides; an operator's "Use servers" switch covers its message and file servers and its chat relays, and turning it on accepts its conditions (OperatorView.swift:313-338, "Use for messages" :119, "Use for files" :196), and the operator screen shows its Directories section also while its servers are off. Then a new user who chose only Flux at onboarding, or someone running their own servers with both operators off, still finds SimpleX Directory, named in the consent alert before their first search; cost: a user who turned an operator's servers off can still send searches to its directory once they agree to it in the alert, and its section shows on a screen whose relays are hidden. If turning an operator's servers off also stops its directories (as channel creation skips a disabled operator's relays; its argument: SimpleX Directory's terms are part of the operator's conditions, which the user accepts by turning it on): every user without the SimpleX Chat operator, Flux-only users and self-hosters included, has no directory search; they can't add SimpleX Directory themselves (its address collides with the hidden preset row); the only way back is turning SimpleX Chat's servers on, which can mean accepting its conditions and using its servers again; a user who searched in the search-only release with that operator off loses search on the upgrade; and a directory a later version presets under a turned-off operator stops being searched. The services design takes that other answer and enforces it in the core. 10. **Retry rows.** Recommended: as drawn (one per failed directory, the others' results stay). If one row for all failed directories (retrying only those): fewer rows, but the user doesn't see which directory failed. 11. **A cap on directories searched at once.** Recommended: none for now (each one is added by the user; iOS requests are off the cooperative pool, on `DispatchQueue.global`, whose ~64 threads are the real bound: a new load after 15 s can start while earlier calls still block). Cost of none: every directory in use receives every search, and each costs a connection and a reply queue. If wanted: search the first N in settings order and say so in Settings (the services design caps at five). 12. **A directory a later version no longer presets, and when that rule ships.** Recommended: keep it, turned off, under Your servers (section 4.2, rule 2), shipped with the first version that drops a preset (slice 3c), not now: until then it would do nothing and only start its cost (a binary with the rule that presets fewer directories turns the others off silently). The adopt rule ships now: it costs nothing and guards a start-up failure in the first version that presets an address a user may have added. If deleting is preferred: a store delete outside the preset guard at start-up, which the app's remembered addresses don't see, so a later re-preset turns it back on and it can receive searches without a question. If retire ships now: written and tested once, and its cost starts with the first release. 13. **Which protocol directories speak** (ep). Once users can add directories, what the apps send is the protocol every other directory implements. Recommended (a): your envelope with an MCP `tools/call` of `simplex.directory.search`, from the release that lets users add directories; "Test directory" checks that `tools/list` includes it; SimpleX Directory answers it and today's request (pull request 2, ~600 lines with the bot helper, before the apps; ~80 more lines per app than today's request). The tool's contract keeps today's fields and is the one the services design proposes: a namespaced name, so a bot with its own `search` tool never passes "Test directory" or, later, fills the directory role, and the directory's work carries over to that design (which also needs its profile to declare `protocols.mcp`). Costs: the apps depend on your draft, so a change to the envelope or the binding before that release changes the directory and both apps; after it, changes must stay compatible or be versioned; SimpleX Directory must be redeployed before the apps ship; before pull request 2 merges, you confirm the tool's contract, and the draft gains the compression paragraph of section 5.1. A variant of (a): directory search's own release already sends this form (slice 6's change, ~80 lines per app, made in pull request #7588, and pull request 2 deployed before that release); then no app ever sends a SimpleX-only request and SimpleX Directory never answers two forms, at the cost of tying directory search, already reviewed, to your draft and to the directory's redeployment. (b) Today's request with a version field: no dependency on the draft; but every directory anyone builds speaks a SimpleX-only request, and a later move keeps both forms in both apps for as long as any added directory speaks only the old one (~150 lines per platform, no end date). (c) Only preset directories until the envelope is final: no commitment; but adding a directory (boards 11–12) waits, and with SimpleX Directory the only preset, several directories change nothing until another operator presets one. 14. **Shipping while searches can be linked** (ep). Recommended: ship. The alerts promise only "each search separately and without your profile", which holds; colluding directories can link one search by its text and time, and a self-hosting user's searches can be linked by their reply server, as with one directory today. With default servers every reply queue is on a SimpleX Chat server, so SimpleX Chat, which runs SimpleX Directory, can already link each search to the device's session there, with one directory as with several (section 5.4). The simplexmq fix (reply queues on their own transport session; the services plan sketches a related "strict" request that avoids a named operator's proxy and servers) is proposed and unplanned. If not: several directories wait for it. -15. **An ordinary group listed by two directories** (ep). Each directory lists an ordinary group with the group link it created itself, so the apps see two links and nothing that says they are one group; channels and public groups publish one link in their profile, so they merge. Recommended: show both rows, each in its directory's place and naming its directory in the long-press submenu; the user sees both descriptions and chooses. Merging by name, image or description is rejected: a look-alike would hide the real group, or the real one the look-alike. A key that every directory would send for one ordinary group doesn't exist (the group profile carries an identity, `publicGroupId`, only for public groups, Types.hs:906-911); adding one is a change to the chat protocol's group profile, outside this design. Cost of two rows: a member who joined through one directory's link sees the other directory's row with Open, since the core finds a group link's plan by that link's own hash (Commands.hs:4790-4796 at 4afce4931), and Open would likely join again as a second member (an inference, to check on a device); this happens today for a group joined through a link the directory didn't create, and becomes common with several directories. Merging by name would avoid it, at the look-alike cost above. +15. **An ordinary group listed by two directories** (ep). Each directory lists an ordinary group with the group link it created itself, so the apps see two links and nothing that says they are one group; channels and public groups publish one link in their profile, so they merge. Recommended: show both rows, each in its directory's place and naming its directory in the long-press submenu; the user sees both descriptions and chooses. Merging by name, image or description is rejected: a look-alike would hide the real group, or the real one the look-alike. A key that every directory would send for one ordinary group doesn't exist (the group profile carries an identity, `publicGroupId`, only for public groups, Types.hs:906-911); adding one is a change to the chat protocol's group profile, outside this design. Cost of two rows: a member who joined through one directory's link sees the other directory's row with Open, since the core finds a group link's plan by that link's own hash (Commands.hs:4790-4796 at 4afce4931), and Open would likely join again as a second member (an inference, checked on a device before slice 8a, AC13); this happens today for a group joined through a link the directory didn't create, and becomes common with several directories. Merging by name would avoid it, at the look-alike cost above. 16. **SimpleX names from directories you added** (ep). Recommended: a result shows its SimpleX name only from an operator's directory; SimpleX Directory sends a name only after verifying the group's domain, a directory the user added can send any, and the row would show it the same way. If you'd rather show them all: a look-alike channel can carry a known channel's name. If the apps should check each name: one names-server lookup per shown result, which tells the names server what each search found. ## 10. Out of scope (noticed, not doing) @@ -480,4 +480,4 @@ From the services design's plan as of 2026-10-22 22:04 UTC (/workspace/shared/di - **First release with several directories, submenu included:** here ~4,950 lines (core ~1,050, directory ~600, iOS ~1,700, Kotlin ~1,600, CI included); there ~5,400 (core and bots ~2,500, apps ~2,900; ~5,000 without its submenu; that plan notes its app figures may be low by a few hundred). - **Statistics:** both need the privacy policy changed and a simplexmq change that keeps reports away from the receiving operator (that plan's sketch, ~600–1,000 lines); here an operator setting, ~900 lines; there a role with its switch in Privacy settings, ~900 lines. - **Later:** here, moving to services as that design plans them is ~1,400 lines, or ~2,750 with the core taking over requests and the `protocols` declaration (section 4.6.3); a directory added or re-tested after that move then has to publish the declaration, which a core without the key can't (`toChatPrefs` drops untyped preference keys, Types/Preferences.hs:539-550); migrated directories keep being searched, as that design's role call reads their seeded tools, except those of an operator whose servers are off. There, nothing to move. -- **In short:** two things decide. Whether more kinds of services users pick from a list are coming: ep wrote that he expects "all the other services we need", starting with telemetry, which in this design is one receiver per operator, not a list. If such services are coming, the services design costs less in total (~5,400 lines against ~6,350–7,700 for this design and the move); if not, this design is ~450 lines smaller now and changes no profile format. And whether the core should also build and read every request, with one parser (the services design), or only check where each goes, the apps building and reading requests in Swift and Kotlin (this design). +- **In short:** two things decide. Whether more kinds of services users pick from a list are coming: ep wrote that the layer should run "all the other services we need", starting with telemetry, which this design assumes is one receiver per operator, not a list. If such services are coming, the services design costs less in total (~5,400 lines against ~6,350–7,700 for this design and the move); if not, the two are about the same size now (the ~450-line gap is inside both estimates' error), and this design is simpler and changes no profile format. And whether the core should also build and read every request, with one parser (the services design), or only check where each goes, the apps building and reading requests in Swift and Kotlin (this design).