checkpoint: document SigningBackend + SigningError foundation (Step 3)
This commit is contained in:
parent
e6e49222ea
commit
a2307ac475
1 changed files with 53 additions and 39 deletions
|
|
@ -1,17 +1,42 @@
|
|||
# Checkpoint — Signer Modes + Fail-Closed Key Export + Packaging Icon (2026-09-03)
|
||||
# Checkpoint — Signer Abstraction (SigningBackend/SigningError) (2026-09-03)
|
||||
|
||||
## Where things are
|
||||
- Project: `/home/avi/Projects/Keynctr`
|
||||
- Branch: `master` @ **`114b343`** ("fix(packaging): restore Linux app icon so dist builds ship an icon").
|
||||
- Branch: `master` @ **`e6e4922`** ("feat(signer): add SigningBackend + SigningError + VaultRef").
|
||||
- Working tree: **effectively clean for tracked files.** No tracked file is modified or
|
||||
staged. The only untracked entries are the pre-existing hygiene leftovers and the
|
||||
source JPEG (all intentionally untracked — see "Still untracked"). Build artifacts
|
||||
(`release/`, `dist/`) are gitignored.
|
||||
|
||||
## What was completed (this session)
|
||||
|
||||
**Step 3 (signer abstraction) — foundation landed.** The approved API types are now in
|
||||
the codebase as a standalone, independently-testable commit (`e6e4922`), ahead of the
|
||||
vault-integration and IPC-rerouting sub-steps. `src/signer/backend.rs` (new) adds:
|
||||
|
||||
- `SigningBackend { Internal, Remote { vault_ref } }` — the per-profile choice of where
|
||||
user content is signed. `Remote` holds **only** an opaque `VaultRef`
|
||||
(`profile_npub` + `signer_pubkey`); the NIP-46 connection secret is **never** inlined
|
||||
(enforced by a test that serializes a `Remote` backend and asserts no secret appears).
|
||||
- `VaultRef` — an opaque, secret-free pointer into the vault's encrypted
|
||||
connection-secret store. Resolution to the decrypted secret happens only at the vault
|
||||
boundary, while the vault is unlocked (that wiring is the next sub-step).
|
||||
- `SigningError` — the closed set of signing failures (no active profile, internal key
|
||||
unavailable, remote connection missing, secret resolution, identity mismatch, not
|
||||
connected, permission denied, expired, revoked, rejected, timeout, invalid signature,
|
||||
network, storage, internal). Each carries a stable `ErrorKind`, a user-facing message
|
||||
(mirroring the existing `AppError` copy), and an optional technical detail.
|
||||
`From<SigningError> for AppError` converts at the IPC boundary; a best-effort
|
||||
`SigningError::from_app` lifts upstream `AppError`s back into the signing space.
|
||||
|
||||
The existing `Signer` trait, `Signing` enum, `EmbeddedSigner`, `Nip46ClientSigner`, and
|
||||
permission model are untouched by this commit — they are what the upcoming vault
|
||||
integration and IPC reroute will drive through `SigningBackend`.
|
||||
|
||||
---
|
||||
The previously-uncommitted working tree (27 modified + 10 untracked files, ~1852/692)
|
||||
was triaged into **three logical, independently-verifiable commits** in a prior session,
|
||||
and this session **landed the Step-2 packaging fix** on top. Order is
|
||||
and the packaging fix landed in `114b343`. Order is
|
||||
`a → c → b → (packaging)`: `ExportSecretKey` (b) reads the per-profile `signer_mode`
|
||||
field and the `external_signer_not_connected` error that the signer-mode commit (c)
|
||||
introduces, so (c) had to land first. The only uncommitted *tests* were the export
|
||||
|
|
@ -58,36 +83,20 @@ each feature's tests travel with it.
|
|||
## Commits added this session (newest first)
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `114b343` | fix(packaging): restore Linux app icon so dist builds ship an icon |
|
||||
| `e6e4922` | feat(signer): add SigningBackend + SigningError + VaultRef |
|
||||
|
||||
(The prior session's three feature commits and their checkpoint `d92371f` remain the
|
||||
parent chain: `6eff510` → `2c61830` → `caed722` → … → `d92371f` → `114b343`.)
|
||||
(The prior session's feature commits — `114b343` packaging icon, `d92371f` checkpoint,
|
||||
`6eff510` export, `2c61830` signer modes, `caed722` audit log — remain the parent chain.)
|
||||
|
||||
## Verification (run this session)
|
||||
Full suite per AGENTS.md, run on top of `114b343` (icon is a binary asset, no code
|
||||
change, so the suite is expected to hold):
|
||||
- **Rust**: `cargo test --release` → **186 passed**, 0 failed; `cargo clippy
|
||||
--all-targets` → clean (exit 0); `cargo fmt --check` → clean (exit 0); `cargo build
|
||||
--release` → Finished, exit 0.
|
||||
- **Frontend**: `npm test` → **116 passed** (16 files); `npm run typecheck` → clean
|
||||
(exit 0); `npm run lint` → clean (exit 0).
|
||||
- `npm run format:check` → **exit 1** on 5 files (`src/components/ExportSecretKeyModal.tsx`,
|
||||
`src/screens/SignerModeScreen.tsx`, `src/state/AppProvider.tsx`,
|
||||
`src/test/ExportSecretKey.test.tsx`, `src/test/fakeBackend.ts`). **Pre-existing** —
|
||||
those files are committed as-is at `6eff510` (prior session) and are clean in the
|
||||
working tree; this icon-only change touched none of them. Left for the Step-7
|
||||
hygiene/format pass; not silently auto-fixed here.
|
||||
- **Packaging (the point of this fix)**: `npm run dist` ran end-to-end. Note the script
|
||||
is `electron-builder --linux dir`, which builds only the unpacked dir; the declared
|
||||
`linux.target` (AppImage + deb) was also built directly to prove the icon lands:
|
||||
- `release/Keynctr-0.1.0.AppImage` — 135,749,547 bytes.
|
||||
- `release/keynectr_0.1.0_amd64.deb` — 105,810,972 bytes.
|
||||
- **Icon proven inside both artifacts**: the embedded icon is **byte-identical
|
||||
(md5 `b3e372f7`)** to the generated `build/icon.png` in all four locations checked —
|
||||
the deb's `usr/share/icons/hicolor/512x512/apps/keynectr.png`, the AppImage's hicolor
|
||||
png, the AppImage's `.DirIcon`, and `build/icon.png` itself — all 512x512 RGBA with
|
||||
real transparency (32,626 opaque px, 226,582 transparent, corners alpha 0), ink
|
||||
pure black. The deb `.desktop` reads `Icon=keynectr`, matching the hicolor name.
|
||||
Full suite per AGENTS.md, run on top of `e6e4922`:
|
||||
- **Rust**: `cargo test` → **196 passed**, 0 failed (186 prior + 10 new
|
||||
`signer::backend` tests); `cargo clippy --all-targets` → clean (exit 0);
|
||||
`cargo fmt --check` → clean (exit 0); `cargo build --release` → Finished, exit 0.
|
||||
- **Frontend**: not re-run this session — `e6e4922` is Rust-only (new
|
||||
`src/signer/backend.rs` + a module line in `src/signer/mod.rs`), so the frontend
|
||||
suite is unchanged from `114b343` (116 passed / typecheck / lint clean). Re-verified
|
||||
in full at the end of Step 3 (IPC reroute), where frontend handlers change.
|
||||
|
||||
## How to reproduce / exercise
|
||||
- Backend: `cargo run --release -- serve` (JSON-lines IPC on stdio) or the CLI in
|
||||
|
|
@ -121,18 +130,23 @@ change, so the suite is expected to hold):
|
|||
## Deferred / next steps (unchanged, plus new)
|
||||
- **Step 2 (packaging): DONE.** Icon restored, `npm run dist` + declared targets green,
|
||||
icon proven inside both artifacts, committed as `114b343`.
|
||||
- **Step 3 (signer abstraction)** — proposed for sign-off, NOT yet implemented. Current
|
||||
state that motivates the API: `src/signer/mod.rs` already defines a `Signer` trait and
|
||||
a `Signing` enum (`Local(Keys)` / `External{signer, profile_pubkey}`); `SignerMode`
|
||||
- **Step 3 (signer abstraction)** — foundation **landed** as `e6e4922`
|
||||
(`SigningBackend` + `VaultRef` + `SigningError` in `src/signer/backend.rs`, 10 tests,
|
||||
full Rust suite green). The `Remote { vault_ref }` variant holds **only** a `VaultRef`
|
||||
— the NIP-46 connection secret is never inlined. **Remaining sub-steps, in order:**
|
||||
1. **Vault integration** — encrypted connection-secret store keyed by `VaultRef`,
|
||||
`VaultRef` → decrypted-secret resolution at the vault boundary (only while
|
||||
unlocked), and removal of the inline `Nip46Connection.secret` field so secrets live
|
||||
only in the vault. This is where the `vault_ref` constraint becomes load-bearing.
|
||||
2. **IPC reroute** — drive `src/ipc.rs` handlers through `SigningBackend` (selected
|
||||
per-profile) so no inline `signer_mode`/handle-presence branching remains; convert
|
||||
handler results to `AppError` via `From<SigningError>`.
|
||||
Prior state that motivated the API: `src/signer/mod.rs` already had a `Signer` trait
|
||||
and `Signing` enum (`Local(Keys)` / `External{signer, profile_pubkey}`); `SignerMode`
|
||||
(`Embedded`/`Nip46Bunker`/`Nip46Client`) lives on `StoredProfile` (per-profile) *and*
|
||||
is duplicated on `App` (app-level), and `App` holds three separate signer handles
|
||||
(`embedded_signer`, `nip46_signer`, `nip46_bunker_signer`). The IPC dispatcher
|
||||
(`src/ipc.rs`) branches on `signer_mode`/handle presence in ~12 sites (see the
|
||||
grep list pasted to the user this session). The proposal promotes `src/signer` to the
|
||||
single `Signer` source of truth, introduces a `SigningBackend { Internal{..},
|
||||
Remote{..} }` per profile, and routes every IPC handler through the trait so no inline
|
||||
mode branching remains. **Awaiting the user's sign-off on the API before any
|
||||
implementation.**
|
||||
(`src/ipc.rs`) branches on `signer_mode`/handle presence in ~12 sites.
|
||||
- External-signer permissions (Step 4): wire `src/signer/permissions.rs` into the
|
||||
approval modal so grants (kinds, relays, expiry, rate) are persisted AND enforced in
|
||||
the UI, not just parsed.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue