From 84d5411a536c4e5646123d2e0e939749021e0e84 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sat, 3 Oct 2026 16:14:52 +0200 Subject: [PATCH] BNS indexer design: what steps 1, 2 and 4 built, and the step-3 contract Records where the Ariadne 0.2.0 implementation differs from the design and why: the protected index\ subfolder, the helper-relayed pipe check, ariadne-run.exe as the restarter because Task Scheduler does not restart a program that exits with an error, the resolver exiting in Theseus-only mode, no owners on the HTTP API, and setup's own scope page. It also records what the Theseus client needs from the pipe. Economy produces no "fresh" pushes, so a missing heartbeat while paused must not trigger a takeover. Last, the plan for bundling Ariadne's setup into Theseus's installer, not yet wired into the build. --- DESIGN-bns-indexer-service.md | 143 ++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/DESIGN-bns-indexer-service.md b/DESIGN-bns-indexer-service.md index 6955d2bb..28e363ba 100644 --- a/DESIGN-bns-indexer-service.md +++ b/DESIGN-bns-indexer-service.md @@ -7,6 +7,10 @@ Already implemented: - Theseus's indexer process `bns-indexer.js`, which becomes the last-resort fallback here; - Theseus's quick single-name lookup while no index exists. +- Migration steps 1, 2 and 4 (Ariadne 0.2.0, not released yet): the shared + modules, the indexer/resolver split, the installer. See "Implementation + notes" at the end for where the code deviates from this text, and what + step 3 (the Theseus client) gets from the pipe. ## The model @@ -357,6 +361,145 @@ settings: 5. **Pages:** the tools page and theseus.x describe Ariadne's Thread as part of Theseus, with All browsers as the default mode. +## Implementation notes (steps 1, 2, 4) + +### Where the code is +- Shared, in `Argus/src/lib/` (Theseus already loads its engine from there): + `bns-index-core.js` (the indexer), `bns-source-chain.js` (the reader), + `bns-pipe.js` (protocol, server, checked client). Ariadne vendors them, + together with `resolver-web.js`, through + `AriadneResolver/ariadne-resolver/scripts/sync-shared.mjs` (`--check` fails + on drift). +- Ariadne: `src/daemon/indexer.js`, `src/daemon/resolver.js` (`bnsd.js` is + now a shim for task definitions from older installers), + `src/ctl/ariadne-ctl.js` (mode / power / start-at-boot / status), and + `src/helper/ariadne-helper.cs`, compiled at install by the in-box C# + compiler into `tools\ariadne-helper.exe` and `tools\ariadne-run.exe`. + +### Deviations from the text above +- **Index folder:** the copies live in `\index\`, not the state root. + `%ProgramData%\Ariadne\` inherits ProgramData's "Users may create files", + so a user could plant the snapshot there before the indexer first writes + it. `index\` has users-read / SYSTEM+Administrators-write, set by + install.ps1 and re-applied by the indexer at every start. It also holds + `bns-index-state.json` (gen, builtAt, paused, pid: when the state was last + confirmed, since the snapshot is only rewritten on change), + `electrum-servers.json` (a writable pool file would choose the evidence), + the resolver's own copy, and `machine-install.json`, the marker that makes + a Just-me install stand down. +- **Server check:** Node cannot reach a pipe's handle, so the client spawns + `ariadne-helper pipe `. The helper connects, + checks the process on the other end of that same connection, prints one + JSON verdict line, then relays the pipe over its stdin/stdout. The checks: + - the image path is under the prefix; + - All users: the token user is SYSTEM, or, when a standard user may not + open a SYSTEM token, the server runs in session 0; + - Just me: the server runs as the same account. + + Node's pipe server grants everyone the right to add instances + (`writableAll`), so every connection is checked, not only the first. + Without the exe the same C# source runs through PowerShell (~0.6 s + instead of ~0.1 s). +- **Restart:** Task Scheduler's "restart on failure" only covers a task that + fails to *start*. A node process that exits with an error leaves the task + "Ready" until the next boot, and that was also true of the old daemon. + `ariadne-run.exe` supervises both tasks: it restarts after 1 min, 5 tries + (the count resets after 10 min up), and exit 0 means a deliberate stop. It + holds node in a kill-on-close job, and it is windowless, so Just-me tasks + show no console. +- **Mode:** in Theseus-only mode the resolver task stays registered. The + resolver removes its routing and exits when it sees the mode, at start or + on a switch. Switching back has to run the task (`ariadne-ctl mode + all-browsers` does). Authenticated users may run, not change, the + All-users tasks. +- **No owners on `/api/*`:** the resolver's HTTP endpoints carry records but + not `owner`. Owner data only travels over the checked pipe. +- **Installer scope question:** Inno's built-in dialog always pre-selects "for + me only" under `PrivilegesRequired=lowest`. Setup therefore asks scope and + mode on its own page and relaunches itself with `/ALLUSERS` or + `/CURRENTUSER` when needed (`PrivilegesRequiredOverridesAllowed=commandline`). +- **Scope switch and the snapshot:** All users -> Just me copies the machine + snapshot. Just me -> All users does not: the per-user file is + user-writable, and the machine indexer must not start from it. It takes + the public snapshot instead, one download. +- **Just me + Firefox:** HKCU\Software\Policies is not writable without + admin, so no Firefox policy is set. Firefox trusts the user-store root + only through its own "use the operating system's certificates" setting. + Not yet verified. + +### What the Theseus client (step 3) needs from the pipe +- **Name:** `\\.\pipe\ariadne-bns` (All users), else + `\\.\pipe\ariadne-bns-`. If both answer, use the machine + pipe. +- **Connecting:** through Ariadne's `tools\ariadne-helper.exe` with + `connectIndexerPipe` from `Argus/src/lib/bns-pipe.js`. + - `imagePrefix` is the folder of Ariadne's `runtime\node.exe`: the + `InstallLocation` from Ariadne's uninstall key, HKLM for All users, HKCU + for Just me. + - A rejected verdict (`{verified:false, reason}`) means: skip to the local + files. +- **Requests,** newline JSON `{id, op}` / `{id, ok, ...}`: + - `hello` returns `{version, network, gen, builtAt, snapshotPath, paused, + pid}`; + - `snapshot` returns `{network, gen, builtAt, tlds, names: [[key, entry]]}`; + the entries carry `owner`; + - `get {name}` returns `{gen, entry|null}`; + - `subscribe` returns `{gen}`; + - `poll` returns `{gen, polled}`, or `ok:false, error:"paused"` under + Economy. The indexer runs at most one on-demand poll per 5 s. +- **Pushes after `subscribe`:** + - `{event:"change", gen, changed, entries}`. `entries` is absent when more + than 500 names changed. Take `snapshot` again whenever `gen` skips. + - `{event:"fresh", gen, builtAt}` after each poll that changed nothing. + - `{event:"paused", paused}`. +- **Health:** a missing `fresh` is not by itself unhealthy. Under Economy + there are none, and Theseus must not take over then: that would undo + Economy. Use `hello.paused`, or the `paused` push. Unhealthy means: + - the pipe fails the check; + - `hello` gets no answer; + - or the indexer is not paused, yet `builtAt` has not moved for the + heartbeat window. + + `bns-source-chain.js` already implements the reader side (heartbeat via + `hello` after 3 silent minutes). +- **Warm start for Theseus's own indexer** when taking over: `snapshotPath` + from `hello`, or `\index\bns-name-snapshot.json` / `.prev.json`. Pass + them to `createIndexer({warmPaths})`. +- **Settings:** edit `policy.json` directly (`mode`, `power`, `economyWhen`, + `startAtBoot`), and run the task where `ariadne-ctl` does (mode -> + all-browsers, start-at-boot -> on). Task names: + - All users: `BNS Indexer`, `BNS Resolver Daemon` (unchanged, so 0.3.70's + panel still finds it); + - Just me: `BNS Indexer ()`, `BNS Resolver ()`. +- **Until step 3 ships:** Theseus 0.3.70's Ariadne panel only knows `BNS + Resolver Daemon`. In Theseus-only mode that task is Ready, not Running, so + the panel shows Ariadne as off. + +### Bundling Ariadne into the Theseus installer (designed, not wired) +- **Payload:** the Ariadne setup exe, as an `extraResources` file of + Theseus's NSIS build, so the two keep separate version lifecycles. +- **Running it:** from NSIS after Theseus's own files are in place: + `AriadneResolver-Setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART + /ALLUSERS|/CURRENTUSER /BROWSERMODE=`. + - Theseus's installer shows the two questions itself (one custom NSIS + page, the same pre-selection rule) and passes them on, so the user sees + one page, not two installers. + - An existing Ariadne keeps its scope unless the user changed it. +- **Elevation:** + - Theseus per-user + Ariadne All users: one UAC prompt, from Ariadne's + setup (`/ALLUSERS` respawns elevated). + - Theseus per-machine: already elevated, so no extra prompt. + - A declined prompt leaves Theseus installed and Ariadne absent. Theseus + then runs its own indexer (source 6) and offers the install again from + the Ariadne panel. +- **Updates:** Theseus's updater chains Ariadne's, as with the add-ons. The + manifest entry stays `ariadne-resolver` / `win-x64`, and the update passes + the installed scope (`/ALLUSERS` or `/CURRENTUSER`, read from which + uninstall key exists), so an update never asks the scope question. +- **Uninstalling Theseus** asks whether to keep Ariadne (default: keep in + All browsers mode) and runs Ariadne's `QuietUninstallString` only if not. +- **Portable Theseus** never bundles it (see open questions). + ## Open questions - **System-level fallback while the resolver restarts:**