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
|
## Where things are
|
||||||
- Project: `/home/avi/Projects/Keynctr`
|
- 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
|
- 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
|
staged. The only untracked entries are the pre-existing hygiene leftovers and the
|
||||||
source JPEG (all intentionally untracked — see "Still untracked"). Build artifacts
|
source JPEG (all intentionally untracked — see "Still untracked"). Build artifacts
|
||||||
(`release/`, `dist/`) are gitignored.
|
(`release/`, `dist/`) are gitignored.
|
||||||
|
|
||||||
## What was completed (this session)
|
## 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)
|
The previously-uncommitted working tree (27 modified + 10 untracked files, ~1852/692)
|
||||||
was triaged into **three logical, independently-verifiable commits** in a prior session,
|
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`
|
`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)
|
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
|
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)
|
## Commits added this session (newest first)
|
||||||
| Hash | Message |
|
| 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
|
(The prior session's feature commits — `114b343` packaging icon, `d92371f` checkpoint,
|
||||||
parent chain: `6eff510` → `2c61830` → `caed722` → … → `d92371f` → `114b343`.)
|
`6eff510` export, `2c61830` signer modes, `caed722` audit log — remain the parent chain.)
|
||||||
|
|
||||||
## Verification (run this session)
|
## Verification (run this session)
|
||||||
Full suite per AGENTS.md, run on top of `114b343` (icon is a binary asset, no code
|
Full suite per AGENTS.md, run on top of `e6e4922`:
|
||||||
change, so the suite is expected to hold):
|
- **Rust**: `cargo test` → **196 passed**, 0 failed (186 prior + 10 new
|
||||||
- **Rust**: `cargo test --release` → **186 passed**, 0 failed; `cargo clippy
|
`signer::backend` tests); `cargo clippy --all-targets` → clean (exit 0);
|
||||||
--all-targets` → clean (exit 0); `cargo fmt --check` → clean (exit 0); `cargo build
|
`cargo fmt --check` → clean (exit 0); `cargo build --release` → Finished, exit 0.
|
||||||
--release` → Finished, exit 0.
|
- **Frontend**: not re-run this session — `e6e4922` is Rust-only (new
|
||||||
- **Frontend**: `npm test` → **116 passed** (16 files); `npm run typecheck` → clean
|
`src/signer/backend.rs` + a module line in `src/signer/mod.rs`), so the frontend
|
||||||
(exit 0); `npm run lint` → clean (exit 0).
|
suite is unchanged from `114b343` (116 passed / typecheck / lint clean). Re-verified
|
||||||
- `npm run format:check` → **exit 1** on 5 files (`src/components/ExportSecretKeyModal.tsx`,
|
in full at the end of Step 3 (IPC reroute), where frontend handlers change.
|
||||||
`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.
|
|
||||||
|
|
||||||
## How to reproduce / exercise
|
## How to reproduce / exercise
|
||||||
- Backend: `cargo run --release -- serve` (JSON-lines IPC on stdio) or the CLI in
|
- 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)
|
## Deferred / next steps (unchanged, plus new)
|
||||||
- **Step 2 (packaging): DONE.** Icon restored, `npm run dist` + declared targets green,
|
- **Step 2 (packaging): DONE.** Icon restored, `npm run dist` + declared targets green,
|
||||||
icon proven inside both artifacts, committed as `114b343`.
|
icon proven inside both artifacts, committed as `114b343`.
|
||||||
- **Step 3 (signer abstraction)** — proposed for sign-off, NOT yet implemented. Current
|
- **Step 3 (signer abstraction)** — foundation **landed** as `e6e4922`
|
||||||
state that motivates the API: `src/signer/mod.rs` already defines a `Signer` trait and
|
(`SigningBackend` + `VaultRef` + `SigningError` in `src/signer/backend.rs`, 10 tests,
|
||||||
a `Signing` enum (`Local(Keys)` / `External{signer, profile_pubkey}`); `SignerMode`
|
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*
|
(`Embedded`/`Nip46Bunker`/`Nip46Client`) lives on `StoredProfile` (per-profile) *and*
|
||||||
is duplicated on `App` (app-level), and `App` holds three separate signer handles
|
is duplicated on `App` (app-level), and `App` holds three separate signer handles
|
||||||
(`embedded_signer`, `nip46_signer`, `nip46_bunker_signer`). The IPC dispatcher
|
(`embedded_signer`, `nip46_signer`, `nip46_bunker_signer`). The IPC dispatcher
|
||||||
(`src/ipc.rs`) branches on `signer_mode`/handle presence in ~12 sites (see the
|
(`src/ipc.rs`) branches on `signer_mode`/handle presence in ~12 sites.
|
||||||
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.**
|
|
||||||
- External-signer permissions (Step 4): wire `src/signer/permissions.rs` into the
|
- 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
|
approval modal so grants (kinds, relays, expiry, rate) are persisted AND enforced in
|
||||||
the UI, not just parsed.
|
the UI, not just parsed.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue