Repository navigation
Conversation
c133edb to
eccd287
Compare
28c3c73 to
c1bfe83
Compare
eccd287 to
1ee6dc5
Compare
c1bfe83 to
b353bdf
Compare
…icit lock release, and refused-recovery copy
A cross-host change reviewed as one contract: the native nsec backup
uploader becomes observable so a caller can follow up on a specific backup
pass, and its locks release correctly.
Rust (`src-tauri/src/nsec_backup.rs`):
- `BackupStatus { seq, pass, builderlab, status }`, `status` one of `idle`,
`running`, `complete`, `waiting { reason: no_consent | no_session |
not_linked }`, `failed { kind: busy | signed_out | not_offered |
other_key_linked | invalid_service | refused | unreachable | unconfirmed |
storage }`. `builderlab` is true when a storage root exists and the
Builderlab source is allowed for this build. Published under the state
lock so pushes leave in `seq` order: a spawned pass publishes `running`
then its outcome; a queued rerun publishes `running` under the latest
ticket; a null handoff that retires a running pass publishes
`waiting{no_session}` numbered as the latest ticket, so a queued rerun's
ticket is settled too; `document_load` turns a running pass into `idle`.
- `classify`: 401 ⇒ `signed_out`; 403 ⇒ `other_key_linked` on upload,
`not_offered` on enrollment and on the `current` read; 404 ⇒
`not_offered`; 429, 5xx and transport ⇒ `unreachable`; other 4xx ⇒
`refused`; enrollment 409 ⇒ `waiting{not_linked}`; an upload response that
is not a stored, matching receipt ⇒ `unconfirmed`; another pass holding
the consent lock or this key's record lock ⇒ `busy`; other local I/O ⇒
`storage`. No response body crosses IPC.
- `trigger`/`session`/`retry` return the ticket (`Option<u64>`) of the pass
that starts after the call; a wake during a pass gets a ticket covered by
the single queued rerun. `nsec_backup_state` returns the snapshot;
`nsec_backup_watch(Channel)` replaces the single watcher slot and refuses
any window but main. Both are registered in `lib.rs`, `build.rs`,
`capabilities/default.json` and `browser_permissions_tests.rs`.
- Recovery: a 403 on the `current` read (no `buzz_users` capability) or on
recovery itself (not a private org, or no Buzz user role) answers the new
`REFUSED` string "Backup recovery is not offered for this account."
instead of the generic retry string; 401 keeps it, since a new sign-in
may clear a rejected session.
- Lock fix (`nsec_backup_choice.rs`, `nsec_backup/persistence.rs`): the
consent and per-identity record locks relied on closing the file to
release the `flock`; a child spawned through fork/posix_spawn shares the
open file description until exec, so the next `try_lock` saw contention
and a pass reported a spurious `busy`/`storage`. A `Lease` now unlocks
explicitly in `Drop` (the pattern `crates/credential-store` already uses).
Probe, 3000 trials: close-only 29–57 spurious holds, unlock-then-close 0.
TypeScript consumer:
- `createBackupStatus` (`backup.ts`), exposed as `identity.backup`
(`service.ts`): registers nothing until the first subscribe/`ready()`,
watches before reading the snapshot, keeps the higher `seq`, re-registers
after a failed watch. It stays out of `IdentitySnapshot`.
`handoffBackupSession` resolves to the ticket or null and never rejects.
- `backUpWith(identity, pubkey, credential, opts)`: the waiter a first-run
step will use — `ready()`, consent on, hand the credential to the
`builderlab` slot, wait for the first status with `pass >= ticket` and not
`running`, hand the slot back on every exit; `complete` resolves,
`failed{kind}` ⇒ that kind, `waiting{not_linked}` ⇒ `not_linked`, other
waits/idle ⇒ `interrupted`, 60 s ⇒ `timeout`; renderer-side failures are
`unknown`/`unavailable`/`interrupted`. `BackupError(kind)` carries the
kind, the table's message and `retryable`.
- `backup-failure.ts` (new): `BACKUP_FAILURES`, one entry per kind with
first-run copy (cause + instruction) and whether Try again can help. The
Settings `cause` variant and the seed's kind guard arrive with their
callers (PRs 08 and 05).
- `RECOVERY_UNAVAILABLE`/`RECOVERY_REFUSED`/`recoveryOff()` in `service.ts`
match the two native strings exactly; `RestoreIdentity` hides *Try
recovery again* when recovery is off; its `expectedPubkey`/`onImport`
props arrive with their caller (`KeyRestore`, PR 06). In
`identity.test.tsx` the two UNAVAILABLE rows move from the Try-again
`it.each` into a new no-retry `it.each` that adds a REFUSED row.
Tests: Rust `nsec_backup/tests.rs` (+541: running-then-outcome under the
returned ticket, the full waiting/failure table incl. busy from a held
consent or record lock, rerun tickets, supersede by key install, a null
handoff settling a rerun ticket, a 403 on `current` refusing recovery,
`document_load` with stale and new watchers, retry after timeout, flat
serialization, permissions) and `nsec_backup_choice/tests.rs` (a released
lease is free while a dup'd descriptor still shares it; a `dup` stands in
for the spawned child). TS `backup.test.ts` (new, 347: the store's lazy
registration and seq rule; the waiter's invoke order watch → state →
set_choice → session → session(null), each status → kind, stale pass
ignored, deadline, `builderlab: false`, null ticket, no native text
reaching the page), `backup-choice.test.ts` (creating an identity never
invokes watch/state), and the recovery hunks of `identity.test.tsx`: the
3-row `it.each` "offers no Try recovery again when %s answers %s" and
"matches native recovery's unavailable and refused messages exactly",
which reads `nsec_backup.rs?raw`. docs/identity.md: the observable status
paragraph and the refused-recovery line.
Stack: PR 03 of the onboarding stack; depends on nothing. 05–08 build on
it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…nted natively and accepted by the binding signer
Pure configuration plumbing; no first-run page uses it yet. A build-time
table `BUZZ_BUILDERLAB_TENANTS` — rows `{ relayHost, self?, loginOrigin }`
— says which relays each Builderlab deployment hosts and where its people
sign in, so a later first-launch screen can send a typed community URL's
sign-in to the right portal instead of one hard-coded Builderlab.
Frontend (`src/features/identity/builderlab-tenants.ts`, new):
`parseBuilderlabTenants` validates the table at Vite configuration time
(hostname shape, ASCII-only folding, 253-byte cap, unique hosts, unique
64-hex `self`, `loginOrigin` equal to its own canonical https origin, no
unknown fields) and fails without echoing the value;
`discoverAuthentication(found, tenants)` answers `{ kind: "builderlab",
origin }` or `{ kind: "none" }`: hostname match (exact, or a suffix at a
label boundary — `block.builderlab.xyz` covers `buzz.block.builderlab.xyz`
but not `notblock.builderlab.xyz` — default port only, longest host wins),
then NIP-11 `self` equality (a known deployment survives a DNS move; it can
only select an origin already in the table), else `none`.
`vite.config.ts` parses the table and defines
`VITE_BUZZ_BUILDERLAB_TENANTS`. `browserCredential(host, signal, bridge,
origin?)` (`oauth/browser.ts`) runs the whole OAuth round-trip —
`/v1/auth/login`, `/login/exchange`, `/auth/me` — at `oauthTarget(origin)`,
the tenant origin going through the same https/no-credential/no-query
check as the build URL; `nativeBridge` is exported for the next PR. The
Settings card is unchanged and always uses the build URL.
Native (`crates/plugin-manager/build.rs`, `src/lib.rs`): `build.rs` loads
`BUZZ_BUILDERLAB_TENANTS` beside `BUZZ_BUILDERLAB_URL` through
`build_env::load`, validates every row with the same rules as the frontend
parser (at most 16 origins), and emits `BUZZ_BUILDERLAB_ORIGINS` (build URL
origin first, then each distinct tenant origin); `builderlab_origins()`
feeds the `block.builderlab`/`bundled` grant's `network_origins`, and
`BUILDERLAB_ORIGIN` stays for `identity.rs`. Malformed input fails the
build with a fixed message. `serde_json` joins that crate's
`[build-dependencies]` (already in the lockfile; no Cargo.lock change).
`src-tauri/src/identity.rs`: the kind-24243 binding signer accepts a
challenge for production Builderlab or any login origin the build grants
(`builderlab_origins()`) instead of only production or the build URL;
unknown, case-changed, trailing-slash, port-changed and empty origins are
still refused.
Config and docs: `.env.example` and `docs/configuration.md` document the
input, the matching rules and the grant, with the three verified rows
(`communities.buzz.xyz` → `https://app.builderlab.xyz`,
`block.builderlab.xyz` → `https://block.builderlab.xyz`,
`test.blockstaging.build` → `https://test.blockstaging.build`, each with its
relay key). The repository ships no deployment default. The Builderlab
README's two grant sentences name the tenant origins.
Tests: `builderlab-tenants.test.ts` (the three seed rows, subdomain,
case/final-dot, lookalikes, non-default port, `self` fallback and its
limits, nested-row precedence, parser rejections without echo incl. an
over-long host and a Kelvin sign); `oauth/browser.test.ts` (an explicit
origin for the whole round-trip and the same refusal of `http://`);
the tenant lines of `browser-host/vite-config.test.mjs` (the define and the
non-echoing rejection); Rust `build.rs` tests (origin union, file-sourced
table, malformed rows), `host_request.rs` (every granted origin),
`identity/tests.rs` (granted, production, ungranted and malformed origins;
the IPC test signs and verifies for every granted origin and refuses a
foreign one).
Not in this PR: `src-tauri/src/identity.rs` hosted-communities binding for
other tenants stays as on main apart from the origin rule;
`src/bundled/hosted-communities/native.ts` still hard-codes the production
API.
Stack: PR 04 of the onboarding stack, on 02 (`builderlab-tenants.ts`
imports `type FoundCommunity` from `communities/find-community.ts`);
independent of 03. 05–07 build on it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
b353bdf to
6546483
Compare
1ee6dc5 to
1d6ceda
Compare
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account.
Please replace the internal deployment data identified inline. Optional inline suggestions cover configuration-limit parity and lossless origin serialization. Align the description’s origin cap with the enforced limit: 15 distinct login origins, including the build URL.
Rocket, Mantis and Groot supplied independent source-review lanes. Reviewed head 1d6cedae14db488ec7ef86ccf16325823128226c, base 6546483b07fd588ed2972beb47ab0029a6403836. Source-only: no tests, app execution, native sign-in, live services or CI verification; first-run UI consumers arrive in later PRs.
| # plugin-manager build, which grants every loginOrigin to the bundled Builderlab | ||
| # plugin, so set it where BUZZ_BUILDERLAB_URL is set. Public configuration; no | ||
| # deployment table is supplied by the repo. Rows: {"relayHost","self"?,"loginOrigin"}. | ||
| # BUZZ_BUILDERLAB_TENANTS='[{"relayHost":"communities.buzz.xyz","self":"12f6870117eff1a6318bd38c82a65d51dd19879b7489f57247114d0ee8a96de3","loginOrigin":"https://app.builderlab.xyz"},{"relayHost":"block.builderlab.xyz","self":"76aa9daa7edc9c2e1dc6a2a90dbce10c1bd52f060fa6506ba7bad4f50240fc01","loginOrigin":"https://block.builderlab.xyz"},{"relayHost":"test.blockstaging.build","self":"f7f4df9401ecf46cc608b0889a61d957b306721cdefe42cb24519cb5a473dcf1","loginOrigin":"https://test.blockstaging.build"}]' |
There was a problem hiding this comment.
🤖 P2 — Replace internal deployment data with synthetic examples. This newly added table publishes internal tenant/staging hostnames and their relay-key mappings in a public repository. The same data appears in docs/configuration.md, the new TS tenant tests, Rust build/signing fixtures, and commit prose; the PR description also uses the internal host in its matching example. Use reserved example domains and clearly synthetic 64-hex keys throughout these public surfaces, preserving the suffix/identity test cases and legitimate attribution trailers. This is deployment-information exposure, not a claim that public relay keys are credentials.
| parseCommunityAliases(aliases); | ||
| // Public too: the same table the native build grants to the Builderlab plugin. | ||
| const tenants = env.BUZZ_BUILDERLAB_TENANTS ?? ""; | ||
| parseBuilderlabTenants(tenants); |
There was a problem hiding this comment.
🤖 Optional P3 — Validate the same distinct-origin limit in Vite. parseBuilderlabTenants(tenants) checks rows but never counts login origins or includes BUZZ_BUILDERLAB_URL. Consequently 15 distinct tenant origins plus a different build URL pass Vite configuration, while crates/plugin-manager/build.rs:114–128 rejects that same configuration because its union exceeds 15. Check the same deduplicated union here and add boundary cases for a shared versus distinct build origin. Native enforcement already fails closed; this fixes the promised cross-host build-input parity.
| if origins.len() > MAX_ORIGINS { | ||
| return Err("Too many Builderlab login origins"); | ||
| } | ||
| Ok((origin, origins.join(","))) |
There was a problem hiding this comment.
🤖 Optional P3 — Preserve origins across serialization. Canonical HTTPS URL validation does not exclude commas in a hostname: the pinned url 2.5.8 host parser uses the WHATWG/IDNA URL deny list, which permits them. Joining a permitted origin such as https://a,b.example here and splitting it in builderlab_origins() turns it into https://a and b.example, so the compiled grant/signing list no longer represents the configured origin. Consider rejecting commas in both build-input validators or using an unambiguous encoding, with a round-trip case. This is trusted configuration handling, not an untrusted runtime bypass.
|
🤖 Closing: the tenant table is out of the onboarding stack. Release builds don't set |
…Builderlab hosts it Correction to `50bad0cdb`, whose message called sign-in "always at BUZZ_BUILDERLAB_URL" for a hosted URL as intended: for another deployment's relay that auto-sign-in was wrong. A public release (`app.builderlab.xyz`, bb-public) given a `block.builderlab.xyz` community URL signed in at once at bb-public, then linked the user's new or imported key to that bb-public account and offered to back it up there. `main` cannot do this (its key-setup gate never calls Builderlab); the auto-sign-in came in with `builderlab-hosted.ts` in `75127a4d4`, after #1817 closed saying a typed address always gets the connect choice. Rule: a known Builderlab relay signs in at once only when its owning portal is the origin of `VITE_BUZZ_BUILDERLAB_URL`. Host x build: typed host \ build app.builderlab block.builderlab test.blockstaging *.communities.buzz.xyz sign in key only key only *.block.builderlab.xyz key only sign in key only *.test.blockstaging.build key only key only sign in any other host Connect Connect Connect (was: every hosted cell signed in at the build portal). A relay publishing a listed `self` key follows its row; an FI or unconfigured build matches no portal, so every known host is key only. - `builderlab-hosted.ts`: each row names its portal; `builderlabHosting(found, build)` answers `{ portal, here }` or `undefined` (hostname first, then `self`; default port only). - `Welcome.tsx`: `onFound` signs in only when `here`; otherwise it keeps the other portal's host in `elsewhere` and pauses on the connect choice. A join admitted after a landing sign-in is not saved to this account when another Builderlab hosts it (`ProfileStepState.found` keeps `self` so the guard can use the relay-key fallback). - `ConnectChoice.tsx`: with `elsewhere`, the page names that portal and offers only a prominent "I already have a key" (and Back); title, host line, page key and `data-return-focus="import"` are unchanged. Shared `Button` only, no new tokens or variants. Tests: `builderlab-hosted.test.ts` asserts each accepted host's portal, the 3x3 host x build `here` matrix, empty/invalid/path-bearing build URLs and the `self` fallback. `identity.test.tsx`: the hosted journey is now a 3-case `it.each` with the build URL stubbed to each portal; a new 2-case journey (bb-block URL in a public build, bb-public URL in a Block build) shows the key-only page, returns focus to "I already have a key" from the import page's Back, imports, reaches Join Community with `connect` and `account` never called and exactly `identity_restore, identity_legacy_available, relay_http x2, identity_import, relay_http x2`, all to the typed origin. It fails on `50bad0cdb` (no "Connect to your community" heading: sign-in started) and passes now. A third journey joins a bb-block community after signing in at a public build and asserts nothing is saved to the account; it fails with the guard removed. Docs: `docs/identity.md` (portal per deployment, sign in only for this build's, key only for another's, no save) and one sentence in `docs/buzz-help.md`. Validation: `tsc --noEmit`; Biome on the changed files; `vitest run` of `identity.test.tsx`, `builderlab-hosted.test.ts`, `first-run-join-handoff.test.tsx` and `first-run-navigation.test.tsx` (4 files, 171 tests) pass. Not run: the full suite, hooks, `design:check`, and a rendered check of the key-only page (themes, 480/1100 px, 150 % text, keyboard and pointer). Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
…Builderlab hosts it Correction to `50bad0cdb`, whose message called sign-in "always at BUZZ_BUILDERLAB_URL" for a hosted URL as intended: for another deployment's relay that auto-sign-in was wrong. A public release (`app.builderlab.xyz`, bb-public) given a `block.builderlab.xyz` community URL signed in at once at bb-public, then linked the user's new or imported key to that bb-public account and offered to back it up there. `main` cannot do this (its key-setup gate never calls Builderlab); the auto-sign-in came in with `builderlab-hosted.ts` in `75127a4d4`, after #1817 closed saying a typed address always gets the connect choice. Rule: a known Builderlab relay signs in at once only when its owning portal is the origin of `VITE_BUZZ_BUILDERLAB_URL`. Host x build: typed host \ build app.builderlab block.builderlab test.blockstaging *.communities.buzz.xyz sign in key only key only *.block.builderlab.xyz key only sign in key only *.test.blockstaging.build key only key only sign in any other host Connect Connect Connect (was: every hosted cell signed in at the build portal). A relay publishing a listed `self` key follows its row; an FI or unconfigured build matches no portal, so every known host is key only. - `builderlab-hosted.ts`: each row names its portal; `builderlabHosting(found, build)` answers `{ portal, here }` or `undefined` (hostname first, then `self`; default port only). - `Welcome.tsx`: `onFound` signs in only when `here`; otherwise it keeps the other portal's host in `elsewhere` and pauses on the connect choice. A join admitted after a landing sign-in is not saved to this account when another Builderlab hosts it (`ProfileStepState.found` keeps `self` so the guard can use the relay-key fallback). - `ConnectChoice.tsx`: with `elsewhere`, the page names that portal and offers only a prominent "I already have a key" (and Back); title, host line, page key and `data-return-focus="import"` are unchanged. Shared `Button` only, no new tokens or variants. Tests: `builderlab-hosted.test.ts` asserts each accepted host's portal, the 3x3 host x build `here` matrix, empty/invalid/path-bearing build URLs and the `self` fallback. `identity.test.tsx`: the hosted journey is now a 3-case `it.each` with the build URL stubbed to each portal; a new 2-case journey (bb-block URL in a public build, bb-public URL in a Block build) shows the key-only page, returns focus to "I already have a key" from the import page's Back, imports, reaches Join Community with `connect` and `account` never called and exactly `identity_restore, identity_legacy_available, relay_http x2, identity_import, relay_http x2`, all to the typed origin. It fails on `50bad0cdb` (no "Connect to your community" heading: sign-in started) and passes now. A third journey joins a bb-block community after signing in at a public build and asserts nothing is saved to the account; it fails with the guard removed. Docs: `docs/identity.md` (portal per deployment, sign in only for this build's, key only for another's, no save) and one sentence in `docs/buzz-help.md`. Validation: `tsc --noEmit`; Biome on the changed files; `vitest run` of `identity.test.tsx`, `builderlab-hosted.test.ts`, `first-run-join-handoff.test.tsx` and `first-run-navigation.test.tsx` (4 files, 171 tests) pass. Not run: the full suite, hooks, `design:check`, and a rendered check of the key-only page (themes, 480/1100 px, 150 % text, keyboard and pointer). Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
…Builderlab hosts it Correction to `50bad0cdb`, whose message called sign-in "always at BUZZ_BUILDERLAB_URL" for a hosted URL as intended: for another deployment's relay that auto-sign-in was wrong. A public release (`app.builderlab.xyz`, bb-public) given a `block.builderlab.xyz` community URL signed in at once at bb-public, then linked the user's new or imported key to that bb-public account and offered to back it up there. `main` cannot do this (its key-setup gate never calls Builderlab); the auto-sign-in came in with `builderlab-hosted.ts` in `75127a4d4`, after #1817 closed saying a typed address always gets the connect choice. Rule: a known Builderlab relay signs in at once only when its owning portal is the origin of `VITE_BUZZ_BUILDERLAB_URL`. Host x build: typed host \ build app.builderlab block.builderlab test.blockstaging *.communities.buzz.xyz sign in key only key only *.block.builderlab.xyz key only sign in key only *.test.blockstaging.build key only key only sign in any other host Connect Connect Connect (was: every hosted cell signed in at the build portal). A relay publishing a listed `self` key follows its row; an FI or unconfigured build matches no portal, so every known host is key only. - `builderlab-hosted.ts`: each row names its portal; `builderlabHosting(found, build)` answers `{ portal, here }` or `undefined` (hostname first, then `self`; default port only). - `Welcome.tsx`: `onFound` signs in only when `here`; otherwise it keeps the other portal's host in `elsewhere` and pauses on the connect choice. A join admitted after a landing sign-in is not saved to this account when another Builderlab hosts it (`ProfileStepState.found` keeps `self` so the guard can use the relay-key fallback). - `ConnectChoice.tsx`: with `elsewhere`, the page names that portal and offers only a prominent "I already have a key" (and Back); title, host line, page key and `data-return-focus="import"` are unchanged. Shared `Button` only, no new tokens or variants. Tests: `builderlab-hosted.test.ts` asserts each accepted host's portal, the 3x3 host x build `here` matrix, empty/invalid/path-bearing build URLs and the `self` fallback. `identity.test.tsx`: the hosted journey is now a 3-case `it.each` with the build URL stubbed to each portal; a new 2-case journey (bb-block URL in a public build, bb-public URL in a Block build) shows the key-only page, returns focus to "I already have a key" from the import page's Back, imports, reaches Join Community with `connect` and `account` never called and exactly `identity_restore, identity_legacy_available, relay_http x2, identity_import, relay_http x2`, all to the typed origin. It fails on `50bad0cdb` (no "Connect to your community" heading: sign-in started) and passes now. A third journey joins a bb-block community after signing in at a public build and asserts nothing is saved to the account; it fails with the guard removed. Docs: `docs/identity.md` (portal per deployment, sign in only for this build's, key only for another's, no save) and one sentence in `docs/buzz-help.md`. Validation: `tsc --noEmit`; Biome on the changed files; `vitest run` of `identity.test.tsx`, `builderlab-hosted.test.ts`, `first-run-join-handoff.test.tsx` and `first-run-navigation.test.tsx` (4 files, 171 tests) pass. Not run: the full suite, hooks, `design:check`, and a rendered check of the key-only page (themes, 480/1100 px, 150 % text, keyboard and pointer). Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Matt Toohey <[email protected]>
Onboarding stack (merge in order, top to bottom):
Depends on: PR 02 (
builderlab-tenants.tsimportstype FoundCommunityfromcommunities/find-community.ts). Independent of 03. PRs 05–07 build on it.Diff: production +409/−73; tests +295/−28; docs +56/−2
What this layer is
BUZZ_BUILDERLAB_TENANTS([{relayHost, self?, loginOrigin}]) parsed identically on both hosts:builderlab-tenants.ts(parseBuilderlabTenants,discoverAuthentication(found, tenants)— hostname suffix at a label boundary, default port only, longest host wins, then NIP-11self, elsenone) andcrates/plugin-manager/build.rs(same rules, emitsBUZZ_BUILDERLAB_ORIGINS);builderlab_origins()feeds the bundled plugin'snetwork_origins;identity.rs's kind-24243 signer accepts any granted origin instead of only the build URL;browserCredential(host, signal, bridge, origin?)runs the whole OAuth round-trip at a tenant portal (nativeBridgeexported for the next layer). Pure config plumbing; no first-run page uses it yet.Review focus
build.rsaccepts,parseBuilderlabTenantsaccepts, and vice versa (ASCII-only folding, 253-byte host cap, unique hosts/self, canonical httpsloginOrigin, ≤16 origins). Both fail without echoing the value.discoverAuthentication: label-boundary suffix matching (notblock.builderlab.xyzmust not matchblock.builderlab.xyz), default port only, longest host wins,selfcan only select an origin already in the table.src-tauri/src/identity.rs: the signer's allow-list is nowbuilderlab_origins()(build URL + tenants) plus production; the tests cover granted, production, ungranted, case-changed, trailing-slash, port-changed and empty origins.oauth/browser.ts: the tenant origin goes through the sameoauthTargetcheck as the build URL; the Settings card is unchanged. The straddling files (vite.config.ts,vite-config.test.mjs,.env.example,docs/configuration.md, the README) carry only their tenant hunks here; their seed hunks land in PR 05.Checks run on this branch's tree
--no-verify; the hosted checks on this PR are the verification. The plan's checks for this layer were:cargo test -p buzzodz-plugins --locked,cargo test --manifest-path src-tauri/Cargo.toml --locked identity host_request,cargo fmt --check,vitest run src/features/identity/builderlab-tenants.test.ts src/bundled/builderlab browser-host/vite-config.test.mjs,tsc --noEmit.Native evidence
None on this tree. The native first-run recordings and the hands-on checks on the branch were made on the full tree, which only the DO-NOT-MERGE PR 09 reproduces (it adds the isolated debug Keychain slot). Treat every layer below 09 as unit/integration-tested only.
🤖 Generated with Claude Code