diff --git a/README.md b/README.md index 6916b4db4..72bc91670 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,10 @@ SSO transport for local end-to-end work. See [Install the CLI](#install-the-cli) to get it, and the [`truapi-host-cli` guide](rust/crates/truapi-host-cli/README.md) for its commands and controls. +CLI reserved identities follow the selected network's dotNS suffix. Old account +and pairing stores are left unused as the CLI starts fresh under its +[versioned state directory](rust/crates/truapi-host-cli/README.md#state-directory). + `scripts/battery.sh` drives that CLI from source over every code-generated example and writes both committed compatibility reports: `explorer/diagnosis-reports/spa/signing-host-cli.md` from a direct signing-host diff --git a/android/truapi-host/README.md b/android/truapi-host/README.md index 80c2a2290..6718aa0d0 100644 --- a/android/truapi-host/README.md +++ b/android/truapi-host/README.md @@ -36,6 +36,12 @@ The package is public, so any authenticated GitHub identity can read it. In GitH The consuming app must declare `android.permission.INTERNET` — the localhost WebSocket bridge binds a `127.0.0.1` TCP socket, which requires it even for loopback. +`HostRuntimeConfig.networkSuffix` is required. Supply the bare TLD (`dot`, +`paseo`, or `testnet`) from the same network configuration used by onboarding +and the People/Bulletin genesis hashes. It must match the People chain's +`NetworkSuffix.NetworkSuffix`. Include this configuration update in the +embedding app's package upgrade. + ### Compatibility - **minSdk**: 29 (Android 10). Aligns with the polkadot-app-android-v2 floor. @@ -96,6 +102,7 @@ val runtime = TrUAPIHostRuntime( hostName = "My Chat Host", peopleChainGenesisHash = peopleChainGenesisHash, // exactly 32 bytes bulletinChainGenesisHash = bulletinChainGenesisHash, + networkSuffix = "dot", ), ) // Chat needs an active session; without one every Chat call answers `Denied`. @@ -332,6 +339,7 @@ val runtimeConfig = HostRuntimeConfig( hostIcon = "https://host.example/icon.png", peopleChainGenesisHash = ByteArray(32), bulletinChainGenesisHash = ByteArray(32), + networkSuffix = "dot", // Optional: activate a local signing session from host-held BIP-39 entropy // (no SSO pairing). Omit for the QR pairing flow. localSessionSecret = null, diff --git a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt index b0e89c929..a8b0faa62 100644 --- a/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt +++ b/android/truapi-host/src/main/kotlin/io/parity/truapi/TrUAPIHost.kt @@ -96,7 +96,10 @@ enum class ProductExecutionKind { /** * Immutable process-wide configuration shared by every product execution * opened from one [TrUAPIHostRuntime]. [peopleChainGenesisHash] and - * [bulletinChainGenesisHash] must each be exactly 32 bytes. + * [bulletinChainGenesisHash] must each be exactly 32 bytes. [networkSuffix] is + * the network's dotNS TLD without the leading dot (`dot`, `paseo`, `testnet`); + * the core derives the wallet's reserved identities under it (`uid.`, + * `peopl.`), the same person the app's own onboarding derives there. */ data class HostRuntimeConfig( val hostName: String, @@ -106,6 +109,7 @@ data class HostRuntimeConfig( val platformVersion: String? = null, val peopleChainGenesisHash: ByteArray, val bulletinChainGenesisHash: ByteArray, + val networkSuffix: String, val localSessionSecret: ByteArray? = null, val localSessionLiteUsername: String? = null, ) { @@ -119,6 +123,7 @@ data class HostRuntimeConfig( platformVersion = platformVersion, peopleChainGenesisHash = peopleChainGenesisHash, bulletinChainGenesisHash = bulletinChainGenesisHash, + networkSuffix = networkSuffix, localSessionSecret = localSessionSecret, localSessionLiteUsername = localSessionLiteUsername, ) @@ -133,6 +138,7 @@ data class HostRuntimeConfig( platformVersion == other.platformVersion && peopleChainGenesisHash.contentEquals(other.peopleChainGenesisHash) && bulletinChainGenesisHash.contentEquals(other.bulletinChainGenesisHash) && + networkSuffix == other.networkSuffix && localSessionSecret.contentEquals(other.localSessionSecret) && localSessionLiteUsername == other.localSessionLiteUsername } @@ -145,6 +151,7 @@ data class HostRuntimeConfig( result = 31 * result + (platformVersion?.hashCode() ?: 0) result = 31 * result + peopleChainGenesisHash.contentHashCode() result = 31 * result + bulletinChainGenesisHash.contentHashCode() + result = 31 * result + networkSuffix.hashCode() result = 31 * result + (localSessionSecret?.contentHashCode() ?: 0) result = 31 * result + (localSessionLiteUsername?.hashCode() ?: 0) return result diff --git a/docs/rfcs/0022-account-derivations.md b/docs/rfcs/0022-account-derivations.md index 8f9b2db25..ecdeab3c1 100644 --- a/docs/rfcs/0022-account-derivations.md +++ b/docs/rfcs/0022-account-derivations.md @@ -270,10 +270,18 @@ reserved product identities as their `productId`: | Migrating to a product soon | Game (DIM2) | `dim2.dot` | Governance-reserved 3–5 char name | | Migrating long-term / product-shaped | PoI (DIM1) | `poi.dot` | Governance-reserved 3–5 char name | | Migrating long-term / product-shaped | Funding | `fund.dot` | Governance-reserved 3–5 char name | -| Migrating long-term / product-shaped | Public light person identity | `uid.dot` | Governance-reserved 3–5 char name | -| Migrating long-term / product-shaped | Personhood | `peopl.dot` | Governance-reserved 3–5 char name | +| Migrating long-term / product-shaped | Public light person identity | `uid.` | Governance-reserved 3–5 char name | +| Migrating long-term / product-shaped | Personhood | `peopl.`| Governance-reserved 3–5 char name | | Not coercible to a product | Coinage | — | Deferred to a separate RFC (own layout today: `//pps//coin/{index}`, `//pps//ring-vrf/{index}`) | +A reserved `productId` is a dotNS name like any other, so it ends in the TLD of +the network the host runs against: `uid.dot` and `peopl.dot` on Polkadot, +`uid.paseo` and `peopl.paseo` on paseo-next-v2, `uid.testnet` and +`peopl.testnet` on previewnet. The TLD is the same network suffix the People +runtime scopes its product contexts with (`product/peopl./…`), so accounts, +contexts and keys agree on which network a person belongs to, and one seed is one +person per network. A host learns the suffix from the network it is configured +for (`SigningHostConfig::network_suffix` in the Rust core) and never assumes it. ### Well-known alias accounts The runtime defines well-known Account Contexts (`resources`, `score`, @@ -322,16 +330,22 @@ reserved product identity from the table above. `DerivationIndex` is the same 32-byte index format as product accounts, so each domain gets its own index space. -The personhood keys live under the `peopl.dot` domain: +The personhood keys live under the `peopl.` domain of the network: ```rust // Full personhood ring-VRF key -full_personhood_key = //peopl.dot//index_bytes(0) +full_personhood_key = //peopl.//index_bytes(0) // Light personhood ring-VRF key -light_personhood_key = //peopl.dot//index_bytes(1) +light_personhood_key = //peopl.//index_bytes(1) ``` +On Polkadot these are `//peopl.dot//index_bytes(0)` and +`//peopl.dot//index_bytes(1)`; on paseo-next-v2 the same seed yields the +`peopl.paseo` keys, a different pair. The `peopl.` domain is also what the +personhood product on that network derives from when it registers its keys +under RFC-0024, so the reserved keys and the product's own registry entries are +the same bytes. Existing keys migrate to these paths. Coinage's ring-VRF keys (recyclers/vouchers) are deferred to the coinage RFC. @@ -384,7 +398,7 @@ game_domain = "game" There are no production deployments of secret-component derivations or of the `u32`-index wire types; the selector change is wire-breaking for `ProductAccountId`, `ProductProofContext`, `PaymentTopUpSource`, and -`AllocatableResource`, and is made freely, with no migration path. Existing ring-VRF keys move to their `peopl.dot` +`AllocatableResource`, and is made freely, with no migration path. Existing ring-VRF keys move to their `peopl.` paths; deployed encryption keys are handled by the encryption RFC. ## Drawbacks diff --git a/docs/rfcs/0024-personhood-as-product.md b/docs/rfcs/0024-personhood-as-product.md index 6ca217b6c..595dca471 100644 --- a/docs/rfcs/0024-personhood-as-product.md +++ b/docs/rfcs/0024-personhood-as-product.md @@ -24,7 +24,7 @@ A proof is a bearer token for its context's alias and a signature is a bearer to **Personhood is welded into the Host.** RFC-0004 §"Host member-key selection" requires every Host to define the PoP ring collection internally, choose a member key corresponding to the requested `RingLocation`, fall back to the PoP key when correspondence is undeterminable, and tiebreak stably. `truapi-server` implements exactly that with the ring identities compiled in (`rust/crates/truapi-server/src/runtime/signing_host/ring_vrf.rs`: `FULL_PERSON_COLLECTION`, `LITE_PERSON_COLLECTION`, `enum PersonKey { Full, Lite }`). So every change to how a person key is derived, registered, renewed, or recovered is a Host release. -A personhood product must instead own the full and light keys — under RFC-0022, the `peopl.dot` domain of the ring-VRF tree — while telling the Host and Account Holder enough to keep serving the app's own personhood-dependent features, and lending its keys and aliases to other products. The binding constraint across all of it: **no consumer may know which key is used**, not the app and not a calling product. +A personhood product must instead own the full and light keys — under RFC-0022, the `peopl.` domain of the ring-VRF tree, `peopl.dot` on Polkadot — while telling the Host and Account Holder enough to keep serving the app's own personhood-dependent features, and lending its keys and aliases to other products. The binding constraint across all of it: **no consumer may know which key is used**, not the app and not a calling product. **The obstacle** is that the member keys serve three overlapping classes of work, and only one is not extractable: @@ -108,7 +108,7 @@ fn list_ring_vrf_keys( - **Registration declares intent, not membership.** It means "this is the key I will use for that ring", not "the user is a person"; membership is still discovered only by attempting a proof, which returns `NotMember` (RFC-0004). This keeps the registry from being a personhood oracle. - **The public key is owner-visible by default, permissioned cross-product**, because a member public key is linkable across every ring it appears in. -RFC-0022 already pins `//peopl.dot//index_bytes(0)` as the full personhood key and `index_bytes(1)` as the light one. Under this RFC those constants are the personhood product's own implementation detail, expressed to everyone else as two registry entries. +RFC-0022 already pins `//peopl.//index_bytes(0)` as the full personhood key and `index_bytes(1)` as the light one, under the TLD of the network. Under this RFC those constants are the personhood product's own implementation detail, expressed to everyone else as two registry entries. The examples below are written for Polkadot, where the product is `peopl.dot`; on paseo-next-v2 read `peopl.paseo` throughout. ### Proofs, aliases, and signatures take an explicit key handle diff --git a/ios/truapi-host/README.md b/ios/truapi-host/README.md index 370ebfc86..340c4092b 100644 --- a/ios/truapi-host/README.md +++ b/ios/truapi-host/README.md @@ -89,6 +89,12 @@ manifest PR to keep `main` current. SPM pins the resolved revision in the app's `Package.resolved`; update it with File > Packages > Update in Xcode or `xcodebuild -resolvePackageDependencies` after the tag is published. +`HostRuntimeConfig.networkSuffix` is required. Supply the bare TLD (`dot`, +`paseo`, or `testnet`) from the same network configuration used by onboarding +and the People/Bulletin genesis hashes. It must match the People chain's +`NetworkSuffix.NetworkSuffix`. Include this configuration update in the +embedding app's package upgrade. + Run the package tests against an iOS simulator (the xcframework has no macOS slice): ```bash @@ -140,7 +146,8 @@ let runtime = try TrUAPIHostRuntime( runtimeConfig: HostRuntimeConfig( hostName: "My Chat Host", peopleChainGenesisHash: peopleChainGenesisHash, // exactly 32 bytes - bulletinChainGenesisHash: bulletinChainGenesisHash + bulletinChainGenesisHash: bulletinChainGenesisHash, + networkSuffix: "dot" ) ) // Chat needs an active session; without one every Chat call answers denied. @@ -378,7 +385,8 @@ let runtimeConfig = HostRuntimeConfig( hostName: "My Host", hostIcon: "https://host.example/icon.png", peopleChainGenesisHash: Data(repeating: 0, count: 32), - bulletinChainGenesisHash: Data(repeating: 0, count: 32) + bulletinChainGenesisHash: Data(repeating: 0, count: 32), + networkSuffix: "dot" ) let runtime = try TrUAPIHostRuntime(bridge: bridge, runtimeConfig: runtimeConfig) try runtime.activateLocalSession(secret: entropyBytes, liteUsername: nil) diff --git a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift index 43456bfab..9a4559c2c 100644 --- a/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift +++ b/ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift @@ -29,6 +29,12 @@ public struct HostRuntimeConfig: Sendable, Equatable { public let platformVersion: String? public let peopleChainGenesisHash: Data public let bulletinChainGenesisHash: Data + /// The network's dotNS TLD without the leading dot (`dot`, `paseo`, + /// `testnet`). The core derives the wallet's reserved identities under it: + /// `uid.` for the identity account and `peopl.` for the + /// person ring-VRF keys, the same person the app's own onboarding derives + /// on that network. + public let networkSuffix: String public let localSessionSecret: Data? public let localSessionLiteUsername: String? @@ -40,6 +46,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: String? = nil, peopleChainGenesisHash: Data, bulletinChainGenesisHash: Data, + networkSuffix: String, localSessionSecret: Data? = nil, localSessionLiteUsername: String? = nil ) { @@ -50,6 +57,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { self.platformVersion = platformVersion self.peopleChainGenesisHash = peopleChainGenesisHash self.bulletinChainGenesisHash = bulletinChainGenesisHash + self.networkSuffix = networkSuffix self.localSessionSecret = localSessionSecret self.localSessionLiteUsername = localSessionLiteUsername } @@ -64,6 +72,7 @@ public struct HostRuntimeConfig: Sendable, Equatable { platformVersion: platformVersion, peopleChainGenesisHash: peopleChainGenesisHash, bulletinChainGenesisHash: bulletinChainGenesisHash, + networkSuffix: networkSuffix, localSessionSecret: localSessionSecret, localSessionLiteUsername: localSessionLiteUsername ) diff --git a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift index 00213a3fe..90291ed09 100644 --- a/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift +++ b/ios/truapi-host/Tests/TrUAPIWsBridgeTests.swift @@ -78,7 +78,8 @@ private extension TrUAPIWsBridgeTests { HostRuntimeConfig( hostName: "truapi-host-tests", peopleChainGenesisHash: Data(repeating: 0, count: 32), - bulletinChainGenesisHash: Data(repeating: 0, count: 32) + bulletinChainGenesisHash: Data(repeating: 0, count: 32), + networkSuffix: "paseo" ) } diff --git a/js/packages/truapi-host/README.md b/js/packages/truapi-host/README.md index a77780ddc..7cbd923ca 100644 --- a/js/packages/truapi-host/README.md +++ b/js/packages/truapi-host/README.md @@ -15,6 +15,14 @@ The package exposes tree-shakeable subpath exports — import only what your env | `@parity/truapi-host/worker-runtime` | Web Worker entrypoint (import with your bundler's `?worker` suffix) so the WASM core runs off the page main thread. | | `@parity/truapi-host/wasm/web` | The raw browser `wasm-bindgen` glue, if you need to instantiate the core yourself. | +The shipped WASM is built by `scripts/build-wasm.mjs` with +`--no-default-features`, so it excludes `WasmSigningHostRuntime`. +`ProductRuntimeConfig` configures the pairing host and requires no network +suffix. A custom build enabling the Rust `wasm-signing-host` feature exposes +the signing constructor, whose configuration requires +`runtimeConfig.networkSuffix`: the bare TLD (`dot`, `paseo`, or `testnet`) +matching the People chain and the wallet's onboarding configuration. + ## Bundler requirements The worker imports the WASM glue by a literal specifier, so every bundler diff --git a/rust/crates/truapi-host-cli/README.md b/rust/crates/truapi-host-cli/README.md index f045c1489..b948ca9a8 100644 --- a/rust/crates/truapi-host-cli/README.md +++ b/rust/crates/truapi-host-cli/README.md @@ -21,7 +21,7 @@ One binary, `truapi-host`: | --- | --- | | `pairing-host` | Seedless host: serves product frames, emits pairing deeplinks, and can run product scripts. | | `signing-host` | Wallet-local host: owns signer identity, can run product scripts, decodes copied pairing QR images or accepts deeplinks, registers statement allowance on-chain, signs. | -| `identity-check` | Probe the root and canonical `uid.dot` identity account for a registered username (read from the dotNS contracts on Asset Hub). | +| `identity-check` | Probe the root and the network's `uid.` identity account for a registered username (read from the dotNS contracts on Asset Hub). | | `register-name` | Register a full-person username via `DotnsGateway.register_name` on Asset Hub, linked to a lite username or standalone with a chat key. | | `alloc-check` | Diagnose (or `--submit`) on-chain statement-store allowance: ring membership, chosen slot, and the `set_statement_store_account` extrinsic. On a full period it prints each occupied slot's age and which one would be replaced. | | `pgas-check` | Diagnose (or `--submit`) an Asset Hub PGAS allowance claim: ring membership on People, whether Asset Hub has imported that ring revision, the day's first unclaimed slot, and the `Pgas.claim_pgas` extrinsic. | @@ -94,6 +94,21 @@ curl -fsSL https://raw.githubusercontent.com/paritytech/host-rust-core/main/scri serves a fake release over loopback, installs it with the real installer, and updates it. Nothing contacts GitHub. +### State directory + +Reserved identities derive under `uid.paseo` / `peopl.paseo` on +`paseo-next-v2`, and `uid.testnet` / `peopl.testnet` on `previewnet`. +All managed CLI state lives under `/v2`, including accounts, +sessions, pairings, core and product storage, managed scripts, and log +preferences. The CLI appends `v2` to both the default base path and a path set +through `--base-path` or `TRUAPI_HOST_BASE_PATH`. For example, +`--base-path ./truapi-host-paseo` uses `./truapi-host-paseo/v2`. + +The CLI leaves previous state outside `v2` untouched and unused, and starts +normal onboarding automatically. There is no state migration. Pair devices +again; sign out first on any paired host that still uses an old identity. +Existing `.dot` personhood membership does not transfer to the new keys. + ### Building from source A source build resolves the product-script runner from the checkout, so it also @@ -187,11 +202,11 @@ press Ctrl-V, use the terminal's paste shortcut, or drop an image file. You can also provide an image file or deeplink with `/pair `, run `/script`, or use `/help` to discover the available commands. It uses `--mnemonic` / `HOST_CLI_SIGNER_MNEMONIC` if set. -Otherwise it auto-selects or creates a stored account under `--base-path` (default -`$XDG_STATE_HOME/truapi-host` or `~/.local/state/truapi-host`), attests it -through the identity backend, waits for ring readiness, and rotates when the -current account exhausts Statement Store slots and no saved pairing depends on -its identity. A full period replaces the oldest slot past the runtime's +Otherwise it auto-selects or creates a stored account under `/v2` +(default `$XDG_STATE_HOME/truapi-host/v2` or `~/.local/state/truapi-host/v2`), +attests it through the identity backend, waits for ring readiness, and rotates +when the current account exhausts Statement Store slots and no saved pairing +depends on its identity. A full period replaces the oldest slot past the runtime's replacement cooldown, so rotation only happens when no slot is replaceable. ### Interactive terminal UI @@ -317,7 +332,7 @@ settings containing arguments, such as `EDITOR='code --wait'`, are supported. Managed sessions isolate signer accounts, product/core storage, and permissions. Once a signer identity is known, its public session name is the Lite username and its files live under -`//_signing_host`. Provisional named sessions +`/v2//_signing_host`. Provisional named sessions are promoted to that user-owned root, so an old name such as `pgtest` does not remain the durable namespace. The selected username is remembered per network but is not repeated in the status bar as a separate session field. @@ -330,7 +345,7 @@ old session, resets product WebSocket connections so clients reconnect against the new runtime, and restores every paired device saved for the target session. `/session --mnemonic ""` brings an already-onboarded account into the -session catalog. The host derives its `uid.dot` identity, reads any existing +session catalog. The host derives its `uid.` identity, reads any existing full or Lite username from dotNS, falls back to the identity backend's assigned username records when no dotNS mirror exists, and confirms its People or LitePeople ring membership. This lookup is read-only and never registers a new @@ -481,7 +496,7 @@ the selected id, so the newly selected product sees its own state. The next `/script` also receives the new id through `host.productId`. Pairing-host state follows the same identity rule under -`//_pairing_host`. Before the first identity is +`/v2//_pairing_host`. Before the first identity is known it uses the small `/pairing-host` bootstrap; connecting moves that bootstrap data to the first resolved user. After `/logout`, connecting as a different user swaps to that user's KV/core namespace instead of carrying @@ -609,7 +624,7 @@ are unavailable on the pairing host and in one-shot `exec` mode. Use the global `--log-level` option (`error`, `warn`, `info`, `debug`, or `trace`) before or after the subcommand, or `/log ` in the terminal UI. -`/log` saves the level under `--base-path`, so pairing and signing hosts restore +`/log` saves the level under `/v2`, so pairing and signing hosts restore it after restart. A one-off `--log-level` or `TRUAPI_HOST_LOG` value overrides the saved level for that process without changing it; otherwise the fallback is `info`. @@ -641,7 +656,7 @@ The real statement store enforces per-account allowance. Before pairing, the signing host grants it on-chain exactly as a real client does: it proves its personhood ring membership with a bandersnatch ring-VRF and submits an unsigned General (v5) `Resources.set_statement_store_account` extrinsic for each account -that submits statements — its RFC-0022 `uid.dot` identity account and the +that submits statements — its RFC-0022 `uid.` identity account and the pairing host's per-pairing device key. The shared native implementation lives in `truapi-server/src/runtime/statement_allowance/` (metadata-driven signed-extension encoding, ring fetch, slot scan, ring-VRF proof, extrinsic @@ -669,7 +684,7 @@ product may not: it reports the period as exhausted, because every entry in the table is one of this wallet's own products and reclaiming space belongs to the renewal pass. `alloc-check` prints both collections' member keys, ring indices and slot tables. Auto-managed accounts are stored in -`accounts.json` under `--base-path`; mnemonics are plaintext local test secrets +`accounts.json` under `/v2`; mnemonics are plaintext local test secrets and the file is written with `0600` permissions on Unix. `alloc-check` verifies membership and can submit a test registration. @@ -708,7 +723,7 @@ gets its own signer identity on the same machine. `HOST_CLI_IDENTITY_BACKEND_BASE` swaps only the identity backend (for a local one); `HOST_CLI_IDENTITY_BACKEND_TOKEN` supplies its bearer token instead of the CLI minting one. For username -registration, an injected token's subject must match the session's `uid.dot` +registration, an injected token's subject must match the session's `uid.` candidate account. The automatically minted token uses that identity; and `HOST_CLI_DOTNS_POP_CONTROLLER` overrides on-chain `DotnsPopController` discovery (see SPEC.md §21). Both also accept `--frame-listen
` diff --git a/rust/crates/truapi-host-cli/SPEC.md b/rust/crates/truapi-host-cli/SPEC.md index d66034066..fa45c9c83 100644 --- a/rust/crates/truapi-host-cli/SPEC.md +++ b/rust/crates/truapi-host-cli/SPEC.md @@ -285,7 +285,7 @@ Commands: The option is global and is accepted before or after a subcommand. `TRUAPI_HOST_LOG` supplies the same per-process override. Without either, the -CLI restores the level saved by `/log` under the selected base path, then falls +CLI restores the level saved by `/log` under `/v2`, then falls back to `info`. Command-line and environment overrides do not rewrite the saved level. @@ -305,7 +305,7 @@ truapi-host pairing-host [options] | `--script ` | none | Run one JS/TS product script and exit with its status. | | `--product-id ` | `headless-playground.dot` | Initial product scope. | | `--frame-listen ` | none | Opt into a TCP product WebSocket listener. When omitted, use a private per-process Unix socket. Port `0` selects an available TCP port. | -| `--base-path ` | section 12.1 | Root for network, identity, core, script, and product state. | +| `--base-path ` | section 12.1 | Base directory; managed state lives under its `v2/` subdirectory. | | `--network ` | `paseo-next-v2` | Select the complete endpoint/genesis preset (`paseo-next-v2`, `previewnet`). | | `--auto-accept` | off | Approve platform confirmations automatically. | @@ -344,7 +344,7 @@ truapi-host signing-host [options] [exec ''] | `--session ` | remembered session | Restore or create a managed session. | | `--lite-username-prefix ` | session-derived | Prefix for newly generated Lite username bases. | | `--reserved-username