CLI and troubleshooting: genkan
This doc is the reference for the
genkanbinary's subcommands and the common troubleshooting paths. The CLI is the management surface over the core. Components and process boundaries are in architecture.md; on-disk paths are in architecture.md section 4.3.
Subcommand overview
genkan is a single binary with subcommand dispatch:
| Invocation | Mode | Description |
|---|---|---|
genkan (no arguments) | MCP server | Default mode, spawned by the MCP client. The first instance becomes the broker; later instances attach to it. |
genkan --native-host [--label <browser>] | native host | Thin bridge, spawned by the browser via the host manifest. Never invoked by hand. |
genkan doctor [--json] (alias status) | read-only diagnostics | Environment and connectivity self-check; changes nothing. --json prints the report as one versioned object. |
genkan doctor --list | read-only diagnostics | One line per known browser and scope: detection and registration state. |
genkan doctor --paths | read-only diagnostics | Prints the runtime dir and lock path this environment resolves to, creating neither. |
genkan doctor --fix | repair / install | Registers (or re-registers) this binary as the native-messaging host for your account. The only mutating form of doctor. |
genkan doctor --fix --system | repair / install (root) | The same, machine-wide: into the root-owned directories every account's browser reads. What the .deb runs after install. |
genkan uninstall [--system] | removal | Removes exactly the registrations this project wrote in that scope, nothing else. |
genkan pair [--reset] [--file-store] | enrollment | Mints the host key the extension pins, behind a confirmation typed on the terminal; the key lives in the OS credential store, or in a 0600 file with --file-store. |
genkan revoke <browser> | enrollment | Forgets that browser's enrolled authenticators; no proof needed, the browser enrolls again from its options page. |
genkan revoke --all | enrollment | Starts over: deletes the host key and the signed policy baseline, forgets every browser and every trusted client. A bare revoke is refused with the usage. |
genkan enclave-status [--json] | read-only | Prints the host key state, where it lives, and its fingerprint. |
genkan pair-client --name <label> (--this-parent | --hash <hex> | --signer <id>) | trusted clients | Adds an MCP-client harness to the trusted-client allowlist; presence-gated. |
genkan revoke-client --name <label> | trusted clients | Removes a client; a live broker drops it immediately. |
genkan list-clients | read-only | Prints the trusted-client allowlist. |
genkan kill | kill switch | Engages the global kill switch: halts ALL bridge activity until an explicit release. |
genkan unkill | kill switch | Releases the kill switch, after proof of user presence: a confirmation typed on an interactive terminal (a piped stdin is refused). |
genkan policy show [--json] | read-only | Prints the host-owned policy state and the effective policy. |
genkan policy set <field flags> [--json] | policy (grant lane) | Mints a fresh SIGNED policy baseline behind the typed terminal confirmation. Signature-only; refuses up front where no host key exists. |
genkan policy restrict <field flags> | policy (free lane) | Applies an unsigned restriction overlay; no prompt, because it can only remove capability. |
genkan policy history [--json] | read-only | Prints the superseded-revision ring. |
genkan policy rollback --revision <n> [--entry <id>] [--json] | policy | Re-derives a past revision's effective policy as a FRESH write, never a replay; --entry names one record where the revision appears more than once. |
genkan audit [--limit <n>] | read-only audit | Prints the on-disk audit trail, oldest first (default: the last 200 records). |
genkan lang [show | set <value>] | display language | Reads or sets the display language the options page shows; lang alone is show. |
genkan --help | help | Usage information. |
The options page offers the same actions. Terminal-only by design: uninstall (below), and the --system and --manifest-dir repair forms. The page's audit view widens with Show older up to the frame's cap; the whole trail is audit --limit <n>. The site allowlist, allow-all, and tab grouping stay on the page. They are browser-local extension storage (see the privacy policy), which no subcommand reads or writes.
doctor / status (read-only self-check)
doctor (with status as an equivalent alias) is a read-only subcommand: it does not bind the socket, does not write the lock file, and does not spawn any child process. It only probes the current environment and prints its conclusions, to answer the question "why can't I connect".
It reports:
- Version / platform: the binary version (Cargo is the source) and the running platform.
- Lock file: whether the bridge lock file exists in the runtime directory, and the endpoint and pid recorded in it.
- Server reachability: a passive connect-and-drop probe against our own bridge socket (no bytes sent), reporting
reachable/not reachable. - Kill switch: engaged, clear, or unreadable.
doctorexits non-zero while the switch is engaged or its state cannot be read. - Native-host registrations: for each known browser (chrome, chromium, brave, edge, vivaldi, opera), whether it looks present on this machine and the state of its registration for
com.vivswan.genkan.hostin each scope,userandsystem:ok,missing,stale(ours, but its launch path dangles), or not ours. - The verdict follows the browser's lookup order: the per-user entry when one exists, the system one only in its absence. The diagnosis comes from the same resolver
--fixrepairs with, so what doctor reports is exactly what--fixproduces.
The options page's Host registration section shows the same rows (lock file, server, kill switch, policy baseline, the verdict), worded by the host, and its identity section shows where the host key lives, as enclave-status prints it.
doctor --json prints the same report as one JSON object on stdout, with the same exit code. Check its v field first and refuse a newer value before reading anything else (fail closed), as with every --json report of this binary.
How to interpret "server not reachable"
"Server not reachable" means doctor read the endpoint from the lock file, but the probe failed. Common causes:
- No MCP server is running. The server is spawned by the MCP client (such as Claude Code) inside its session, so with no client session up nothing is listening and "not reachable" is the expected state. Confirm the client has the genkan server configured and a session open.
- Stale lock file. A previous broker exited abnormally and left the lock file behind. The next server instance detects and replaces a stale lock at startup; just start a new client session.
doctoronly probes; it does not repair. It will not kill processes, delete the lock file, or restart the server. When you see "not reachable", re-establish the session from the MCP client side rather than intervening in processes by hand.
If a registration is missing or stale for a browser you use, that browser cannot spawn the native host. Run genkan doctor --fix, then restart the browser.
doctor --fix / uninstall (native-messaging registration)
The CLI below registers the native-messaging host from a terminal through one engine (registration/). It needs nothing but the host binary itself, on desktops, headless machines, and CI alike.
genkan doctor --fix (re-)registers the binary you invoke it from as the native-messaging host: for each targeted browser it writes the com.vivswan.genkan.host.json manifest where that browser looks for it, and beside it the extension pointer (below).
- Idempotent re-registration: on a fresh machine
--fixis also the first registration, and after moving the binary it refreshes a stale one. - Nothing built, downloaded, or copied: the manifest points at this binary's own resolved path, through a small per-browser wrapper script on macOS/Linux.
- That wrapper bakes in
--native-host, because Chrome's manifest format has noargsfield, plus--label <browser>when one browser alone launches the manifest (run-host-<browser>.sh); a manifest several browsers read gets the unlabeledrun-host.sh(the rule isTarget's inregistration/mod.rs). - Overwrites a manifest another tool wrote at our host id (the report names what it launched), refuses one it cannot read, and refuses a foreign pointer;
uninstallleaves a foreign manifest.
Selecting browsers:
genkan doctor --fix # every browser detected for this user
genkan doctor --fix --browser chrome,brave
genkan doctor --fix --all # every known browser, detected or not
genkan doctor --fix --manifest-dir DIR # exact NativeMessagingHosts dir
# (absolute; repeatable), for a Chromium
# variant we do not know by name
sudo genkan doctor --fix --system # machine-wide, for every account (root only)
genkan doctor --list # read-only: detection + registration stateThe scope is the command's: --system writes the directories every account's browser reads (/etc/opt/chrome/native-messaging-hosts, /Library/Google/Chrome/NativeMessagingHosts, HKLM) and needs root, while without it a root shell is refused, since root has no browser of its own.
Opera, and Brave on macOS and Linux, read Chrome's system directory rather than one of their own, and Brave on macOS reads Chrome's per-user directory as well. For them doctor --fix registers Chrome's manifest in that scope and doctor reports it on their rows as Chrome's.
- No label on the shared manifest: either browser may launch it, so its connections take the broker's default slot, as a
--manifest-dirregistration's do. - Own pointer per browser, per user on macOS: Chrome and Brave each keep their own extension pointer there, so both prompt to enable the extension (machine-wide, macOS has one pointer directory for every browser).
Known browser keys: chrome, chromium, brave, edge, vivaldi, opera. "Detected" means the browser is actually installed, as far as a cheap local check can tell:
| Platform | The detection check | What it means |
|---|---|---|
| macOS | the application bundle under /Applications or ~/Applications | a leftover per-user config directory alone does not count (uninstalled browsers keep those forever, and some dev tools create them); a freshly installed browser counts before its first run |
| Linux | the per-user config directory; with --system, the vendor package's install directory (/opt/google/chrome, /usr/lib/chromium, and the like) | a per-user repair registers the browsers this account has run; the .deb's post-install, from root, registers the ones installed for every account |
| Windows | the per-user profile directory | the best cheap signal there |
- A non-standard install on macOS reads as "not detected"; it can still be registered explicitly with
--browser <key>or--manifest-dir. - Plain
doctorcounts only detected browsers, so a healthy explicit registration for a non-standard install keeps the summary below "OK" even though the bridge works - the per-browser lines tell the real story. - Nothing detected:
--fixrefuses and asks for an explicit selection instead of guessing, exiting 3 rather than 1 so an installer can tell "no browser yet" from a failure. - The options page's Host registration section repairs the same two ways, for this account: every detected browser, or one named browser from its row.
--manifest-dirand--systemstay in the terminal: a directory is typed, and root is held, where the page has neither.
genkan uninstall reverses exactly what this project registers (via --fix) in one scope: the per-browser manifests, the extension pointers, and the wrapper scripts. Re-pass any --manifest-dir you registered, and --system (as root) for a machine-wide registration.
Before deleting a manifest or pointer it verifies the content is ours (our host id and description marker; the Web Store update url alone). Anything else, or anything it cannot read, is reported and left in place as a warning, never a failure, so a package removal completes; the other artifacts of ours beside it still go, and only one of ours that cannot be removed fails the command.
It never touches this binary or your browsers. A browser drops the extension it installed from the pointer on its next start; an unpacked extension is yours to remove.
uninstall has no options-page twin by design. The frame asking for it would delete the manifest that launched the very host answering it.
The extension pointer, beside each manifest:
| OS | Where --fix writes it | What the browser does with it |
|---|---|---|
| macOS | <user data dir>/External Extensions/<extension id>.json, naming the Web Store; with --system, /Library/Application Support/Google/Chrome/External Extensions/ for every browser (Chromium's one machine-wide directory) | asks "Enable Genkan?" on its next start |
| Windows | HKCU\<vendor>\Extensions\<extension id>, value update_url; HKLM with --system | the same prompt |
| Linux | nothing; doctor prints pointer n/a | it would install from a pointer silently, which the threat model refuses: add the extension from the Web Store yourself |
Chrome's own locations come from its documentation. The other vendors are derived from the same user-data root and registry root they keep their manifests under, and Edge is pointed at the Chrome Web Store too (a residual: unverified on those browsers).
The pointer informs doctor and never decides its verdict: the bridge works with an unpacked extension and no pointer. It is written for the browsers --fix names or detects; a --manifest-dir registration gets none, since its browser cannot be named.
Whether the listing exists yet, and what to load until it does, is quickstart.md step 4's.
Platform notes:
- Linux AppImage / temp paths: a registration pointing into an AppImage's FUSE mount (or any temp dir) breaks when that path disappears.
--fixwarns when it detects this. Copy the binary to a stable location first, for example~/.local/lib/genkan/genkan, and rundoctor --fixfrom there. - Windows: registration is an
HKCUregistry key per browser plus a manifest file under%LOCALAPPDATA%\genkan(with--system,HKLMand%ProgramFiles%\genkan). The code path compiles and mirrors what the retiredinstall.ps1script did, but it has not yet been verified on a real Windows machine; treat Windows registration as best-effort until then. Browser detection on Windows (per-user profile directories; Opera under the roaming profile) carries the same caveat.
Enrollment: pair / revoke / enclave-status
The host-key ceremony gives the extension one host identity to pin:
genkan pairasks for a confirmation typed on the terminal (a piped stdin is refused before any prompt), mints a P-256 host key, keeps it in the OS credential store (the Keychain, the Credential Manager, or the Secret Service), and prints the key's SHA-256 fingerprint. Compare that fingerprint with the one the extension shows on its enrollment screen; a mismatch means something sits between them.genkan pair --file-storekeeps the key in a 0600 file in the runtime directory instead, for a machine with no usable credential store. The choice is explicit: a store failure is reported, never silently redirected to the file.genkan pair --resetasks for the confirmation first, then removes the previous key (from whichever store holds it) and mints a fresh one; the extension must re-pin. Browser enrollments and client pairings stay.- When the credential store does not answer, a
--file-storereset proceeds with a warning that an entry the store may hold stays behind. Runpair --resetagain once the store answers;revoke --allwould also forget every browser and client. genkan enclave-status [--json]reports the current state read-only: whether a key is present, which store holds it, and its fingerprint.
User presence for the browser's own acts (releasing the kill switch, enrolling a second browser) is a WebAuthn tap on the browser's authenticator, verified by the host. The options page's identity section enrolls the authenticator; its kill panel, its policy editor, and its trusted-clients form each answer the host's presence request with the proof the kill-switch table below describes.
Forgetting is friction-free, because it only removes capability:
genkan revoke <browser>forgets every authenticator enrolled under that label. The browser's acts fall back to the confirmation window until it enrolls again from its options page; when it was the last enrolled browser, the next enrollment is first-time again.- The label is the browser's host manifest
--label(brave,chrome), ordefaultfor every browser on a shared unlabelled manifest (Windows, the shared Chrome manifest), sorevoke defaultforgets all of them. An unknown label is refused with the labels the record holds. - The options page offers the same for its own browser: Forget this browser, in the identity section's authenticator block. It acts on that host's label, so browsers sharing a manifest are forgotten together.
genkan revoke --allstarts over in one step: the host key is deleted, the policy record goes (the signed baseline and any restriction overlay), every browser is forgotten, and every trusted client is revoked, so a paired machine admits no client untilpair-clienttrusts one again. The kill switch is not touched; release it withunkill.- After
revoke --alla connected extension fails closed either way: by the revocation push when the credential store confirmed the key gone and the record write landed, otherwise at its next key verification.revoke <browser>leaves the host key and the pin alone. - A bare
genkan revokenames neither and is refused with the usage.
The CLI never raises that prompt: its own grants (pair, pair-client, unkill, policy set) are confirmed by the typed phrase on a real terminal.
Trusted clients: pair-client / revoke-client / list-clients
By default (unenrolled), any process that spawns the server is served, and every start logs that open posture at ERROR level. Creating the trusted-client allowlist closes it:
genkan pair-client --name claude-code --this-parent
genkan pair-client --name codex --hash <sha256-hex>
genkan pair-client --name claude-desktop --signer <signer-id>
genkan list-clients
genkan revoke-client --name codex--this-parentmeasures the process that spawned this CLI invocation (run it from inside the client you want to trust). Unix only: on Windows the server keys a harness on the creator of its stdin pipe, which a console command has none of, so pair with--hashor--signerusing the values the server logs at startup while unenrolled.- Authorization keys on the attested anchor, never the
--namelabel, which labels logs and revocation. What each platform measures is on the trust boundaries page. - Hash anchors change when the client updates; re-run
pair-clientwith the same name to replace the entry (the re-pair path). - Adding a client is a capability grant, so it is presence-gated: a confirmation typed on an interactive terminal, with a piped stdin refused. Revoking is friction-free by design; a live broker drops the revoked client and refuses its re-attach.
- The options page's Trusted MCP clients section pairs a client the same way (a name plus a hash or signer anchor) behind this browser's presence proof;
--this-parentexists only on the CLI, because a page has no parent process to measure.
Once the allowlist exists, anything unmatched fails closed, including an identity that cannot be measured and an unreadable allowlist. The Windows measurement is in SECURITY.md.
Kill switch (kill / unkill)
genkan kill is the emergency brake: one command that stops every MCP client from driving every connected browser, at once.
- Live browser connections are severed within about a second, and new ones are refused. In-flight tool calls fail fast with
CONNECTION_LOST. - Every subsequent tool call, from every attached client, is refused with the stable
BRIDGE_KILLEDerror code. Clients stay connected so they can show you the refusal instead of dying silently. - The state is persisted (in
trust.json, next to the lock file) and survives restarts, reconnects, and reboots. - The extension's options page shows the state; engaging the switch works from any surface. Releasing it from the options page is answered by the host with a presence request, which the page settles with the WebAuthn tap (or the software confirmation it offers only on a browser with no enrolled credential); a web page cannot see or touch any of it.
Nothing releases the switch on its own. Release demands proof of user presence on either surface:
| Surface | The proof a release demands |
|---|---|
genkan unkill on the CLI | an explicit confirmation typed on a real terminal; a piped stdin is refused outright, so no script or background program can quietly reopen the bridge through the CLI |
| the extension's options page | a WebAuthn assertion from a credential enrolled under that browser; the browser's confirmation window only when the browser has no enrolled credential |
Every release attempt is audited: a granted release with the auth path that decided it (auth=tty, auth=webauthn:<fingerprint>, auth=confirm_window), a refusal at the presence gate with the presence error, and a refusal by an unwritable record after presence passed with both.
If either command reports that the trust record is unreadable, see the recovery steps; until then, everything keeps failing closed.
doctor prints the kill state and exits non-zero while the switch is engaged or its state is unreadable.
Host-owned policy (policy)
genkan policy is the host-owned policy surface. The concepts (the signed baseline, the unsigned restriction overlay, the extension-side ratchet) are in architecture.md section 11.3; the policy baseline: doctor row is read on the troubleshooting page.
genkan policy show [--json] # read-only: store state + effective policy
genkan policy set <field flags> [--json] # GRANT lane: sign a fresh baseline (terminal confirmation)
genkan policy restrict <field flags> # FREE lane: unsigned restriction overlay
genkan policy history [--json] # read-only: superseded revisions
genkan policy rollback --revision <n> [--entry <id>] [--json]Field flags. set and restrict share one flag per policy field, spelled as the kebab-case of its camelCase wire name: --cdp-mode, --file-upload, --handle-dialog, --page-eval, --confirm-high-risk-click, --confirm-page-eval, --presence-confirm, --confirm-tab-close, --warn-precise-snapshot, --eval-mask, --host-reverify-ms, --confirm-grace-ms, --click-toast-timeout-ms, --eval-toast-timeout-ms, and --disabled-tools.
| Flag kind | Value |
|---|---|
| boolean flags | on or off |
the four *-ms flags | a non-negative integer |
--disabled-tools | a comma-separated tool list that states the WHOLE disabled set (keep the tools already in it when adding one) |
--disabled-tools ""is the empty set: empty entries are dropped, so this is a full clear, which on thesetlane is a relaxation and costs the tap like any other.- A tool name containing a comma or surrounding whitespace cannot ride the comma-joined transport faithfully: every write seam refuses such a name outright rather than signing a silently mangled list.
- Parsing is strict: an unknown subcommand, a stray argument, a repeated flag, or a malformed value is an error, never a guess, and
set/restrictdemand at least one field flag.
The two lanes are deliberately asymmetric.
policy setis the grant lane: it folds the edits over the current baseline (untouched fields carry baseline values, never effective ones), embeds the touched-field set in the document, and signs the exact document bytes with the host key once the typed terminal confirmation passes.- No host key, no grant: on a machine that has not run
pairthe CLI refuses UP FRONT, before any prompt could appear, so no baseline can exist that the extension's pin could not verify. policy restrictis the free lane: no prompt, no signature, and the seam's direction check refuses any edit that would relax the effective policy, so a scripted or forged restriction is at worst a denial of service against your own bridge.- The options page's Security policy section runs the same two lanes: a tightening applies at once, a loosening signs a fresh baseline behind this browser's presence proof, and a keyless host refuses it up front with the same words. Before any baseline exists every edit there is a grant, since there is nothing to restrict yet.
Rollback never replays. policy rollback --revision <n> re-derives that revision's effective policy, diffs it against the current one, and applies the difference as a FRESH write.
- A rollback that only tightens rides the free restrict lane with no prompt.
- One that relaxes anything is one fresh terminal confirmation and signature, exactly like any other grant.
- The old signed artifact is never written back: a lower revision must keep failing the extension's ratchet, which is the anti-replay property, not a limitation.
- One revision, several records: every restriction made while a revision was current pushed a record at that revision, so
policy historylists each record'sentryid and--entry <id>names the one to restore; a bare--revisionis refused where it is ambiguous. The page's roll-back buttons name the record the same way. - A store that moves under a rollback is refused: the diff was planned over one read of the store, so a write another surface lands between that read and the rollback's own (a restriction from the options page, say) refuses as a conflict with nothing written, and "already there" is confirmed the same way. Run the rollback again over the new state.
- The options page's Previous revisions list shows the same ring as
policy historyand rolls back the same way, taking the lane the direction decides; each record carries the policy it held, and the page shows the CLI'seffective=line for it with the fields a roll-back would change marked.
--json contracts. show, history, set, and rollback accept --json, which swaps the prose for a versioned report on stdout (and, for the write lanes, a versioned error object on refusal). Check the v field first and refuse a newer value before reading anything else (fail closed).
Every policy transition is audited with the surface and, for grants, the presence path that authorized the signature: auth=tty from the CLI, auth=webauthn:<fingerprint> or auth=confirm_window from the options page. store_tests.rs and presence/tests.rs pin the three spellings.
Display language (lang)
The extension's display language is shared state the host keeps (lang.json in the runtime directory) and pushes to every connected browser, so one choice reaches them all. The options page sets it with the Display language picker in its header; the terminal twin is:
genkan lang # the current value (same as `lang show`)
genkan lang set zh_TW # one of: auto, en, zh_CN, zh_TW- Language is not policy: not signed, not ratcheted, and unable to affect any security decision, which is why it needs no confirmation on either surface.
- A value outside the list is refused at argv and at the page's frame alike, and the previous value stands; setting the current value changes nothing and pushes nothing.
- A connected browser swaps on the host's next push (within its poll interval); an offline one adopts the value when it next connects.
Logging and audit (GENKAN_LOG / GENKAN_LOG_FORMAT)
Diagnostics in both modes go to stderr (stdout carries protocol frames). Two environment variables control the output:
| Variable | Values | Effect |
|---|---|---|
GENKAN_LOG | error | warn | info (default) | debug | Log threshold. info and above print audit lines; set warn/error to silence auditing. |
GENKAN_LOG_FORMAT | text (default) | json | Format of audit lines. json emits one JSON object per line, convenient for machine collection. |
Audit events (stderr): every security decision emits one audit line: tool calls (with req, tool, outcome, and on error the stable code from ERROR_SPECS, plus dur_ms), harness admissions and refusals, client pairing and revocation, host-key revocations, kill-switch transitions, WebAuthn enrollments and presence verdicts, policy writes, and the extension's confirmation and enrollment decisions (forwarded over the port).
The same events are appended as strict JSON records to a durable, size-capped audit.log (0600, in the runtime directory next to the lock file), which survives the short-lived processes that write it. Each record names its event in event_kind; the JSON stderr form wraps the record in a "kind":"audit" envelope, so a collector keys on kind and reads the event from event_kind.
- No sensitive content is recorded: no page text, cookie or storage values, eval return values, or form fill values; masking happens on the extension side (trust boundaries).
- Correlation: a tool-call line carries its request id (
req) and the generation of the browser connection the call was routed to (conn); the generation increments on every re-attach, so a reconnect starts a newconn. - Two extension-local kinds never reach
audit.log:policy_refusedandpolicy_compromisedstay in the extension's own audit ring by design, outside the forwarding whitelist; the host records every policy transition aspolicy_write.
# GENKAN_LOG_FORMAT default (text)
[AUDIT] 2026-10-03 23:12:44.302Z kill_engage surface=cli outcome=ok
# GENKAN_LOG_FORMAT=json
{"kind":"audit","v":1,"ts_ms":1791069164310,"event_kind":"kill_engage","surface":"cli","outcome":"ok"}Read the durable trail with the read-only subcommand:
$ genkan audit --limit 20
2026-07-17 19:04:11.201Z kill_engage surface=cli outcome=ok
2026-07-17 19:04:12.480Z tool_call tool=tab_list outcome=error code=BRIDGE_KILLED dur_ms=0
2026-07-17 19:05:02.913Z kill_release surface=cli outcome=okA record the reader cannot parse is shown as UNRECOGNIZED RECORD and counted, never guessed at; a dropped=n field marks records lost to a failed write (a full disk, for example). Recording never blocks or fails an operation: the trail observes decisions, it does not gate them.
The options page reads the same trail: its Recent activity section lists the host trail (the default page above, widened by Show older up to the frame's cap, the host's own words per line) beside this browser's ring of local decisions.
Error codes and the error taxonomy are in architecture.md section 11.1.
Related
- Install and first use: quickstart.md.
- Connection lifecycle and disconnect/reconnect semantics: architecture.md section 5.2.
- Error taxonomy (
NOT_CONNECTED/ disconnect class): architecture.md section 11.1.