feat(desktop): locally stored NIP-49 encrypted key backup - #2937
feat(desktop): locally stored NIP-49 encrypted key backup#2937tlongwell-block wants to merge 24 commits into
Conversation
Local-only ncryptsec backup of the identity key (plan: PLANS/NIP49_LOCAL_BACKUP_PLAN.md Rev 3, approved 9/10 by Wren). - key_backup.rs: NIP-49 codec (nostr nip49, log_n 18), one-artifact-per- action create with decrypt-verify against the live pubkey, atomic 0o600 write + reread/byte-compare, EFF-wordlist passphrase generation, ncryptsec import recovery, stale-backup cleanup on identity change. - commands/identity.rs: create_ncryptsec_backup (whole body under identity_mutation; webview can never supply canonical blob bytes), save_ncryptsec_copy (dialog selection only + secret-file write, never mutates app state), generate_backup_passphrase, import_identity now accepts ncryptsec1 with a passphrase (raw-nsec path byte-for-byte unchanged). - egress_guard.rs: the backup must NEVER be transmitted to a relay — fail-closed runtime guard rejecting ncryptsec1 at all 8 relay egress boundaries (submit funnel, 3x relay.rs, huddle STT, both engram submitters, native websocket send loop). Scope is ncryptsec1 only: pairing intentionally carries the raw nsec inside its encrypted NIP-AB session. - Tests: injection test per boundary (8), /events inventory-completeness tripwire, ncryptsec source-allowlist scan, spec vector, NFKC cross-form, 0600/atomicity/lifecycle, recovery-mode gating, concurrent identity swap vs backup, boot-reset wipes the backup. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Frontend half of PLANS/NIP49_LOCAL_BACKUP_PLAN.md Rev 3 (D3): - BackupStep: encrypted by default. The default path never invokes get_nsec — the webview only ever sees the finished ncryptsec1 blob returned by create_ncryptsec_backup. The raw key path survives behind an explicit 'Show raw key instead' click with its previous loading/error/skip semantics. - EncryptedBackupCreator (shared by onboarding + settings): generated 6-word passphrase default with cannot-be-recovered copy, 'choose my own' = min 12 chars + confirmation, create -> masked display + copy + 'Save a copy…' via the native dialog. - encryptedBackup.ts: pure reducer/validation model, unit-tested without a DOM; keyImportInput.ts: HRP classification + submit gating. - NsecMaskedDisplay: kind='nsec'|'ncryptsec' drives labels/aria/testids; existing nsec call sites unchanged. - NostrKeyImportForm: ncryptsec1 paste switches to encrypted mode (passphrase field, no npub preview possible), decrypt errors surface; raw nsec flow untouched. import_identity plumbs the optional password through OnboardingFlow/MachineOnboardingFlow/KeyringLockedScreen. - ProfileSettingsCard: 'Encrypted backup' row alongside the raw reveal. - ncryptsecSourceScan.test.mjs: TS-side source-allowlist tripwire mirroring the Rust scan (defense-in-depth per plan D4). - e2e: mock bridge learns the three backup commands + ncryptsec import; onboarding-backup.spec.ts covers the encrypted happy path (asserting get_nsec is never called), custom-passphrase validation, raw fallback, and error/retry; onboarding.spec.ts adds encrypted-import happy path including a wrong-passphrase rejection. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
…r inventory, uppercase bech32 Blocker 1: import_identity persisted-before-cleanup ordering. New commit_imported_identity helper runs durable persistence FIRST; a failed different-key import now leaves both the old in-memory identity and its valid canonical identity.ncryptsec intact. Stale-backup cleanup runs after the durable commit and is deliberately best-effort (logged, not surfaced as a half-applied-import error). Regression tests cover the failure path and prove cleanup cannot precede persist. Blocker 2: the /events inventory tripwire is now site-granular. Each inventoried file pairs an expected non-comment /events occurrence count with an expected egress-guard call count, so an unguarded ninth site in an already-listed file (or a deleted guard call) fails the scan. The scan core is pure over (path, content) pairs, with mutation-style tests demonstrating all three drift classes. Hardening: bech32 permits an all-uppercase encoding, so the egress guard now rejects NCRYPTSEC1... too (mixed case stays unblocked - it cannot decode), and both import classifiers (Rust recover_keys_from_input, TS classifyKeyImportInput/isPlausibleNcryptsec) route uppercase-valid blobs to the encrypted path consistently. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
* origin/main: (70 commits) feat(relay): make Postgres pool size configurable, default 50 (#3191) Publish symbol-bearing debug relay images (#3250) feat(tracing): add datastore tracing plumbing (#2760) fix(buzz-acp): accept id-keyed config options when resolving model switch (#2795) fix(desktop): probe legacy Goose install dir on Windows (#3248) refactor(desktop): extract install command execution into install_exec (#3251) Polish composer activity layout and transitions (#3151) feat(invites): add use-limited invite links (#3141) fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor (#3218) fix(desktop): preserve thread anchor through layout reflow (#3212) feat(search): parse from:/in:/after:/before: and pass them in the filter (#2871) fix(desktop): fetch join policies through native networking (#2862) fix(desktop): republish agent identity records when a persona rename propagates (#2607) fix(desktop): keep project Inbox previews compact (#3193) Inbox refactor (#2045) Fix composer selection formatting and drop overlay (#3172) Refine pending message status (#3153) feat(admin): show reported message content in report detail (#3149) fix(desktop): recover full local storage on startup (#3182) Replace mobile reconnect banners with skeleton shimmer (#3143) ... Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Tyler's spec names the settings entry "Password Backup". Rename the row title and align its description (password, not passphrase) plus the two doc comments that referenced the old row name. No behavior change; test ids unchanged. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
|
Freshened vs main + spec naming pass (channel buzz-nip49-password-backup):
Per Wren's ruling in-channel: no artifact-model reshape — the flow (type password → encrypt → OS save dialog via Verified at exact
Max's independent clean-room battery + live manual pass runs next at this SHA. |
Max's clean-room pass found the wipe -> import-from-file leg unreachable: the file input accepted only .key,text/plain (macOS greys out the identity.ncryptsec our own save dialog emits), and the first-launch spotlight variant rendered no file input at all, leaving paste-only recovery exactly where a returning user holds a backup file. Accept .ncryptsec in the shared hidden file input, hoist it out of the default-variant branch, and give the spotlight variant an 'Import from a file' button wired to the same picker. Drop-zone behavior in the default variant is unchanged. E2E covers the spotlight file path end to end (accept attribute asserted explicitly, since setInputFiles bypasses it) through decrypt-and-continue. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
|
Blocker fix: saved
Fix ( Verified at exact |
…kup step Merging main (7a6a2cd) brought in two smoke specs written against the old raw-key backup page, which this branch replaces with an encrypted-by-default step: Next stays disabled until a backup exists, and the raw nsec (nsec-reveal-toggle) sits behind an explicit 'Show raw key instead' click. Both specs timed out in CI at 72f00af. - harness-management.spec.ts (More-harnesses nav, from #3093): route through the existing passThroughBackupStep helper instead of clicking a disabled Next. - onboarding-docked-cta-screenshots.spec.ts: click backup-show-raw-key before the reveal toggle; the raw-key card is the surface the screenshot exists to capture. Test-only change; no product code touched. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
* origin/main: chore(release): release Buzz Desktop version 0.5.0 (#3213) Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
|
CI fix receipt — head The two red Desktop Smoke shards at
Also merged Verified locally at exact head |
**Category:** improvement **User Impact:** Users can create a password-protected key backup, save it locally, and prove that it works before finishing onboarding or signing out. **Problem:** Tyler's NIP-49 foundation made encrypted local backup possible, but the end-to-end ceremony still needed clearer progression, trustworthy verification, and a Settings treatment consistent with the rest of Identity. **Solution:** This stack adds a guided create/download/test flow, verifies the actual saved backup through Rust without returning secret key material, minimizes password lifetime in the webview, and restores warm, plain-language presentation across onboarding and Settings. > [!NOTE] > This PR is intentionally stacked on #2937 (`eva/nip49-local-backup`). Review only the 19 commits in this stack. <details> <summary>File changes</summary> **desktop/src-tauri/src/commands/identity.rs** Adds off-thread verification of NIP-49 backups and returns public identity details only. **desktop/src-tauri/src/commands/identity_key_backup_tests.rs** Covers verification, identity matching, and failure behavior at the command boundary. **desktop/src-tauri/src/egress_guard_tests.rs** Keeps the NIP-49 source inventory aligned with the new verification path. **desktop/src-tauri/src/key_backup.rs** Supports real backup decryption for verification while zeroizing submitted passwords. **desktop/src-tauri/src/key_backup_tests.rs** Exercises valid, wrong-password, damaged, and different-identity backups. **desktop/src-tauri/src/lib.rs** Registers the verification command with the desktop runtime. **desktop/src/features/communities/ui/WelcomeSetup.tsx** Routes community onboarding through the revised backup ceremony. **desktop/src/features/onboarding/lib/encryptedBackup.test.mjs** Covers the hardened backup state model and password-clearing behavior. **desktop/src/features/onboarding/lib/encryptedBackup.ts** Models creation and verification with opaque request correlation and short-lived passwords. **desktop/src/features/onboarding/ui/BackupStep.tsx** Presents the backup choice clearly and advances into the dedicated download step. **desktop/src/features/onboarding/ui/BackupTestFlow.tsx** Adds the polished select-file, enter-password, verification, error, and success experience. **desktop/src/features/onboarding/ui/CommunityOnboardingFlow.tsx** Connects community onboarding to the updated backup steps. **desktop/src/features/onboarding/ui/DownloadKeyStep.tsx** Adds the dedicated encrypted-backup download step and pre-creation skip escape hatch. **desktop/src/features/onboarding/ui/EncryptedBackupCreator.tsx** Guides password generation, backup creation, native saving, safe retry, and re-download. **desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx** Sequences chooser, download, exact-file test, and setup progression. **desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx** Aligns encrypted-key import behavior with the backup flow. **desktop/src/features/onboarding/ui/NsecMaskedDisplay.tsx** Improves masked/revealed key presentation and safely wraps long private keys. **desktop/src/features/onboarding/ui/OnboardingChrome.tsx** Supports the revised onboarding layout and transitions. **desktop/src/features/onboarding/ui/SetupStep.tsx** Integrates the completed backup ceremony with final setup. **desktop/src/features/onboarding/ui/onboardingFlowSteps.test.mjs** Updates flow-level assertions for the new step sequence. **desktop/src/features/settings/ui/EncryptedBackupRow.tsx** Adds sibling Create and Test rows that match the surrounding Identity settings rhythm. **desktop/src/features/settings/ui/ProfileSettingsCard.tsx** Places password-backup controls in the Identity card. **desktop/src/features/settings/ui/SignOutSection.tsx** Uses explicit backup self-attestation plus the existing wipe phrase without requiring a raw-key reveal first. **desktop/src/shared/api/tauriIdentity.ts** Exposes typed backup verification to the frontend. **desktop/src/shared/lib/ncryptsecSourceScan.test.mjs** Updates the frontend source allowlist for the verification path. **desktop/src/testing/e2eBridge.ts** Models creation, saving, and verification for browser coverage. **desktop/tests/e2e/onboarding-backup.spec.ts** Covers backup creation, exact-file testing, errors, retries, and completion. **desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts** Updates docked CTA visual coverage for the revised progression. **desktop/tests/e2e/onboarding.spec.ts** Keeps broader onboarding coverage aligned with the backup ceremony. **desktop/tests/e2e/profile-nsec-reveal.spec.ts** Covers Settings create/test behavior and updated success copy. **desktop/tests/e2e/signout-confirmation.spec.ts** Covers backup self-attestation, wipe phrase confirmation, and reset behavior. **desktop/tests/helpers/fileDrag.ts** Adds reusable file-drop support for backup test coverage. **desktop/tests/helpers/onboarding.ts** Routes tests through chooser, download, and skip behavior consistently. </details> ## Reproduction steps 1. Start fresh onboarding and choose the password-protected backup path. 2. Continue to **Backup your key**, create a password, save the `.ncryptsec` file, and confirm that **Next** remains gated until testing succeeds. 3. Select that exact file, enter its password, and verify the **Your backup works!** success state. 4. Open **Settings → Identity** and confirm that **Create password backup** and **Test password backup** appear as sibling rows. 5. Test a valid backup, then test wrong-password and different-identity cases to confirm clear, non-secret-bearing outcomes. 6. Open sign out and confirm that backup self-attestation plus typing `wipe all my data` remain required. ## Screenshots ### Onboarding flow | 1. Creating your key | 2. Key created | 3. Create backup password | |---|---|---| | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/fe5ebf60-02ad-4584-8e64-ff137bafac02" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/0b8c7c6c-2ae5-494e-bb81-0a7b47ea1b66" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/9fa8f6c0-832a-46ec-9af8-55dcae0adbb8" /> | | **4. Select the saved backup** | **5. Enter its password** | **6. Verification succeeds** | | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/7ed1528c-6a1f-4411-bd3e-c57413320cdf" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/088ec3ed-2735-4b9a-a0c5-6d176b3f06c5" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/7081ce32-9371-46b2-9692-23ff77c39d35" /> | ### Settings flow | Create and Test tools | Tested backup success | |---|---| | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/978ab672-5510-46d8-9116-6df4f1bda34c" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/56a1764d-3c3e-40e2-9f4b-962c5e96a36c" /> | ## Verification - Desktop typecheck - Full desktop JS: 3,733/3,733 - `pnpm check` - Rust desktop lib: 1,862 passed, 14 ignored; diagnostic: 3/3 - Focused Playwright: 16/16 with one worker Native save/cancel remains the residual smoke-test risk: browser E2E mocks the KDF/native save-dialog boundary, while real decrypt and identity matching are covered in Rust. --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Conflict resolutions: - relay.rs: took main's submit refactor (submit_signed_event_at_with_keys funnel replaces the removed submit_signed_event); the persona flush loop on main already publishes through the guarded funnel. - egress_guard boundary 3 retargeted to the pre-signed funnel entry, and the /events inventory rows updated for main's relay.rs / sharing.rs layout (sharing.rs site is its in-file mock-relay test fixture; its production publish goes through the guarded boundary-1 funnel). - personas/snapshot/import.rs + e2eBridge.ts: kept both sides' additions. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
- Replace the standalone password-backup step with copy and encrypted-file options inside the identity-key stage. - Add a midnight security theme, return-to-onboarding control, and downward transition back into the core yellow flow. - Restore seven-page onboarding pagination and keep backup progress available when users revisit the security view. - Update onboarding E2E coverage and screenshots for the inline options, direct setup path, and password-backup return behavior. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Replace the password-backup surface's onboarding Skip and Next actions with a dedicated Back control. - Keep Backup key and Back in the existing bottom-docked CTA location while preserving inline settings behavior. - Route Back to Backup options with the correct backward transition instead of advancing onboarding. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Track whether the active identity is stored in the system keyring, local fallback file, environment, or ephemeral memory. - Expose storage metadata through the Tauri identity response and shared TypeScript identity model. - Add a responsive first information card beside the two backup actions without revealing boot-time identity creation. - Show keychain password education only when secure storage succeeded and accurate neutral copy for fallback states. - Extend identity persistence tests and split storage types into focused modules to preserve file-size limits. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Add generated compact light and dark noisy surfaces with reusable tone and size options for cards, popovers, and confirmation dialogs. - Keep the dark backup option and password panels unboxed, reduce their padding, and top-align parallel card titles for a clearer visual hierarchy. - Explain identity keys and password-protected backup files in plain language while preserving distinct device, password-manager, and portable-backup choices. - Normalize onboarding primary, secondary, and icon controls and add reduced-motion-safe transitions between backup creation and verification stages. - Update focused onboarding E2E coverage for current navigation, compact texture contracts, title alignment, and narrow viewport geometry. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Use the resolved identity storage backend to name the system keychain in the initial backup guidance. - Keep local-file and unknown storage states accurate without exposing boot-time identity creation. - Explain in the backup options card that the computer may request the user's password when Buzz needs to read the key. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Give the spotlight password field a white background, subtle border, rounded corners, and dark password and placeholder text. - Keep the saved-password mask legible against the light input surface. - Render password generator controls in the standard popover surface instead of the dark textured security treatment. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Increase contrast for the spotlight password field border and focus ring against its white surface. - Apply dark default and hover colors to the password generator and visibility controls. - Remove the obsolete dark-surface icon treatment from the light password field. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Add a key-to-password-to-lock timeline to the onboarding backup form. - Animate four connector dots in a downward pulse with reduced-motion support. - Preserve the compact settings variant and hide decoration in short windows. - Keep balanced spacing and semantic security-theme colors across the illustration. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Give shared in-step primary actions a white background with dark labels. - Add coordinated hover colors that preserve contrast across onboarding surfaces. - Update the shared CTA documentation to describe the inverted treatment. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Restore the standard primary CTA treatment for general onboarding steps. - Add a dedicated inverted CTA style for dark backup-security surfaces. - Apply the white button treatment only to backup creation, file selection, verification, and password-reset actions. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Keep eager NIP-49 encryption silent without changing the password form or advancing the backup flow. - Commit the encrypted payload for saving only after the user clicks the download button. - Preserve queued downloads during encryption and show the rotating progress ticker until the payload is ready. - Update reducer coverage for silent background encryption while leaving end-to-end tests unchanged. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Present backup verification as an optional post-download step with contextual headings and a Skip for now action. - Match the verification password field to the backup password input and move its primary action into the onboarding footer. - Add an upward-pulsing file-to-password illustration with reduced-motion support and a stronger selected-file affordance. - Remove saved filesystem paths from the verification view to avoid exposing local details. - Reset encrypted-backup state whenever users re-enter the creation flow. - Confirm before leaving only when an unfinished pre-download password or encryption would be discarded. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
- Focus the backup password input on entry and submit downloads with Enter, including queued encryption. - Remove the pre-download discard confirmation so Back immediately resets and exits the optional flow. - Give successful verification dedicated page copy and a primary Finish action. - Add an explicit visibility toggle for the unlocked private key while keeping key material masked until requested. - Preserve the existing verification success message with a minimal, lightly blurred key treatment. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Reconcile focused backup assertions with Tyler’s post-main behavior: default generator surfaces, optional verification copy, hidden saved paths, and fresh re-entry state. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Summary
Adds a locally stored NIP-49 encrypted key backup (
ncryptsec) to the desktop app, per the plan reviewed in buzz-development (Rev 3, approved 9/10 by Wren; implementation also reviewed and approved 9/10).Two-artifact design — canonical bytes originate entirely in Rust:
create_ncryptsec_backupruns under theidentity_mutationlock: encrypt → decrypt-verify against the live pubkey → atomic0o600write to{app_data_dir}/identity.ncryptsec→ reread/byte-compare → return the exact persisted bytes. The frontend never re-derives or re-encrypts.save_ncryptsec_copywrites a portable copy via the save dialog (parse-gated, secret-file semantics) and never mutates canonical state.generate_backup_passphrase: 6 words from the EFF short wordlist viaOsRng(custom passphrases min 12 chars).ncryptsec1with optional password; the raw-nsecpath is untouched. Different-pubkey import and sign-out wipe the app-managed backup (post-commit, best-effort — a failed import can never destroy the still-live identity's backup; regression-tested).Never-relay guarantee (egress guard + tripwires):
egress_guard.rsfail-closed at all 8/eventssubmission boundaries (relay submit funnel, 3×relay.rs, huddle STT, both engram submitters, native WS choke point), rejectingncryptsec1/NCRYPTSEC1in text and binary frames. Scope is deliberately ncryptsec-only: pairing intentionally carries raw nsec inside its encrypted session./eventsinventory tripwire: per-file (/eventscount, guard-call count) pairs; unlisted files expect zero. Mutation-style tests prove a ninth site in an existing file, a removed guard, and a new unlisted file all fail the scan.Frontend: onboarding
BackupStepis encrypted-by-default — the default path never invokesget_nsec(e2e asserts the command log). Raw-nsec export stays behind an explicit click with prior semantics. SharedEncryptedBackupCreatorpowers onboarding + a new settings row; the import form auto-switches to encrypted mode onncryptsec1paste (case-insensitive HRP).Open product call for @tlongwell-block: onboarding default is encrypted in this PR; flipping to raw-default is a small change either way (documented in the plan).
Review history: plan Rev 3 and the implementation were both iterated with Wren to 9/10 (two blockers from round 1 — import ordering, inventory granularity — plus an uppercase-bech32 hardening gap, all fixed in
dde37183e). Thread: buzz-development.Related issue
Follow-up to the direction explored in #385 (NIP-PB, closed) — this ships local NIP-49 (the standard) instead of a new NIP. No open duplicate found.
Testing
All at exactly
dde37183e(same shell, HEAD verified):cargo test— 1680 passed / 0 failed / 14 ignored (includes a deliberate ~70s log_n-18 NIP-49 round trip, spec vector, wrong-password, NFKC, uppercase-vector decrypt, injection test per egress boundary, inventory mutation tests, import-ordering regression tests)cargo clippy --all-targets -- -D warnings— clean;cargo fmt --check— cleanpnpm typecheck— clean; JS unit suite 3529/3529; biome (repo-pinned 2.4.16) cleanonboarding-backup/onboarding/onboarding-agent-defaults/profile-nsec-reveal— 86 passed, 1 known avatar-reservation flake (passed on rerun; untouched by this diff).passThroughBackupStepnow exercises the encrypted default, so every downstream onboarding spec covers the new path.Latest onboarding integration
The current head adds an additive
IdentityInfo.storagefield (ephemeral,system-keyring,local-file, orenvironment) so onboarding can accurately explain where the active identity is protected. It surfaces storage metadata only—never key material—and leaves the existing lost/keyring-locked recovery behavior intact.