Orion vault — clean initial history
Knowledge vault (Orion/PARA) migrated from the pre-Orion 484vault on 2026-10-01. Deliberately orphaned: prior history contained a plaintext password and stays local-only on branch archive/pre-boilerplate-history. Secrets and live Hermes state are gitignored.
This commit is contained in:
commit
a66996ac10
233 changed files with 103810 additions and 0 deletions
244
1. Projects/Products/Keynctr/sources/keynctr.md
Normal file
244
1. Projects/Products/Keynctr/sources/keynctr.md
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
---
|
||||
project: keynctr
|
||||
status: active
|
||||
last-updated: 2026-09-25T23:00:00-05:00
|
||||
hermes-owned: true
|
||||
local-path: /home/avi/Projects/Keynctr/
|
||||
tech-stack: [Rust, Electron, React, TypeScript]
|
||||
---
|
||||
|
||||
# Keynctr
|
||||
|
||||
## Overview
|
||||
A desktop GUI for managing Nostr identities and publishing notes, with a focus on
|
||||
key security through vault encryption and a modular signer abstraction layer.
|
||||
Rust backend (JSON-lines IPC on stdio) + Electron/React frontend.
|
||||
|
||||
**Ultimate goal:** Keynctr itself never holds or transmits secret key
|
||||
material. In embedded mode keys live only in the encrypted local vault; in
|
||||
external-signer mode (the most secure of the three ways, and the current
|
||||
focus per Avi's directive) the keys live on the signer device — Amber on the
|
||||
phone — and Keynctr only ever sends signing *requests*, approving each use at
|
||||
the signer. The Rust core does all signing/relay work; the Electron UI never
|
||||
touches secret material. Usable as GUI *and* same-core CLI, and doubling as a
|
||||
NIP-46 bunker for other apps.
|
||||
|
||||
## Architecture
|
||||
- **Frontend:** Electron + React + TypeScript (`frontend/`); Prettier-formatted.
|
||||
- **Backend:** Rust crate `keynectr` (`src/`); `cargo build --release` binary.
|
||||
- **Storage:** a single encrypted vault file (`profiles_vault.json`) holding
|
||||
profiles, signer modes, NIP-46 connections, and encrypted connection secrets.
|
||||
- **Key files:**
|
||||
- `src/main.rs`: CLI entry point / JSON-lines IPC serve.
|
||||
- `src/ipc.rs`: dispatcher.
|
||||
- `src/signer/`: `Signer` trait (permission checks now async),
|
||||
`Signing` enum, `EmbeddedSigner`, `Nip46ClientSigner` (the active path),
|
||||
`permissions.rs`, `backend.rs` (`SigningBackend`, `VaultRef`, `SigningError`).
|
||||
- `src/bunker.rs`: legacy server-mode bunker (bunker:// host role).
|
||||
- `src/vault.rs`: vault load/save, migrations, encrypted `connection_secrets`.
|
||||
- `tests/nip46_e2e.rs`: in-process NIP-46 e2e test (mini relay + fake Amber).
|
||||
- `CHECKPOINT-encryption.md` (repo root): the standing, current checkpoint —
|
||||
always read this first for "where things are."
|
||||
|
||||
## Current Status
|
||||
**External signer (the most secure mode) works end-to-end, proven by test.**
|
||||
Headline commits since the vault was last updated:
|
||||
- `f917e5e` Amber-compatible handshake: `bunker://` URIs, deferred identity
|
||||
(URI key is a per-connection comms key, never identity; real identity learned
|
||||
via `get_public_key` after approval), 120s approval window, fail-closed while
|
||||
Connecting.
|
||||
- `c096705` vault-load rewrite fix (migration no longer re-saves every start).
|
||||
- `85756df` `bunker://` accepted frontend-side + async permission surface +
|
||||
`tests/nip46_e2e.rs` — full e2e: local relay, fake Amber with human-approval
|
||||
delay, identity assertions, `sign_event` verification, vault persistence
|
||||
(remote profiles store no secret material).
|
||||
- `38499d4`→`3d5302f` QR pairing (client-initiated `nostrconnect://` flow),
|
||||
IPC lazy-init fix, Electron allowlist fix, and real kind-0 display
|
||||
name/picture adoption for paired identities (3s-capped, falls back to label).
|
||||
|
||||
Verification at last check (2026-09-12): `cargo test` 200 + e2e green, clippy
|
||||
0 warnings, fmt clean, release build green; frontend 116 tests + typecheck +
|
||||
lint + build green.
|
||||
|
||||
### 2026-09-18 — pairing trace milestone
|
||||
- `21c522b` Durable pairing trace: every pairing decision point now appends
|
||||
a timestamped line to `~/Tools/keynctr-debug/pairing-trace.log` (started,
|
||||
inbound 24133, decrypt-fail with real NIP-44 error, exact unparsable
|
||||
payload, pre-handshake method, connect answered, identity adopted,
|
||||
session failed). Backend stderr only reached the Electron console and
|
||||
/tmp logs got cleaned, so failed live handshakes previously left no trace.
|
||||
- Found forensics contamination: pairing-capture.jsonl lines from Sep 18
|
||||
were the e2e harness's loopback fake-scanner events, not Amber — the
|
||||
capture path was hardcoded. Test events pruned (backup kept) and loopback
|
||||
pairings now skip the capture. Net: no real Amber scan has run against
|
||||
the `188b2eb` lenient parser yet; the next re-scan's trace.log tail will
|
||||
name exactly where the handshake stops, no terminal capture needed.
|
||||
- Verification at `21c522b`: cargo test 208 + 2 e2e green, clippy 0
|
||||
warnings, fmt clean, release rebuilt; frontend all green.
|
||||
|
||||
### 2026-09-16 — pairing debug milestone
|
||||
- `188b2eb` RawRequest deserializer rewritten as universal coercion: any
|
||||
valid JSON now parses (numeric/missing ids → text, object-shaped params,
|
||||
double-encoded request strings). The strict derive was still dropping
|
||||
Amber's connect request even after `aedde8f`. Decrypt failures now log
|
||||
the real NIP-44 error (HMAC vs padding vs wrong key) and the exact
|
||||
decrypted payload instead of a generic message.
|
||||
- Forensics: all 4 captured pairing frames (`~/Tools/keynctr-debug/
|
||||
pairing-capture.jsonl`, latest Sep 16 20:11) are 163-byte spec-valid
|
||||
NIP-44 v2 payloads (plaintext 65–96 bytes; the observed 89 fits) — so
|
||||
Amber's frames decrypt fine and the failure was JSON-shape parsing.
|
||||
- IMPORTANT: `/tmp/keynctr-el*.log` is gone (tmp-cleaner). To see pairing
|
||||
diagnostics launch from a terminal:
|
||||
`NOSTR_GUI_DEV_URL=http://localhost:5173 KEYNCTR_ENABLE_GPU=1 npx electron . 2>&1 | tee ~/Tools/keynctr-debug/el.log`
|
||||
then re-scan the QR in Amber.
|
||||
- Verification at `188b2eb`: cargo test 208 + 2 e2e green, clippy 0
|
||||
warnings, fmt clean, release rebuilt; frontend all green.
|
||||
|
||||
### 2026-09-27 — multi-account switching + permissions UI + updater fix
|
||||
- **Multi-account switching shipped (`c89b31a`, Option A):** pairing/connecting a second signer PARKS the live session (never revoked; row+secret+client key intact); `SelectProfile` re-dials the target profile's saved session (local-key profiles leave the session alone); new `nip46_cancel_pairing` IPC restores the parked session on QR cancel — both Add-profile and Signer Mode cancels use it. Verified: cargo test 216 + 6 e2e green (new two-fake-Amber switch test).
|
||||
- **Step 4 first slice (`adbc7c2`):** `Nip46Status` carries declared `perms=` list + expiry; Signer Mode shows a Permissions panel (grant rows, or a signer-side-enforcement note). Always-allow grants are now kind-scoped: a `sign_event` grant records the approved request's kind; legacy kind-less grants keep all-kinds meaning (`serde(default)`, never bricked existing vault rows); enforced in `bunker.rs` + `nip46_client.rs` via `has_signer_grant(peer, method, event_kind)`.
|
||||
- **Updater fix (`fa59b63`):** `updates.rs` `run()` augments the inherited PATH with `~/.cargo/bin`, `~/.local/bin`, mise/asdf shims, `/usr/local/bin` — Check-for-updates was dead under the desktop-launcher env; verified live under `env -i PATH=/usr/bin:/bin`.
|
||||
- Status at end of night: tree clean @ `118f5d3`, 7 ahead of origin (push needs per-use token); cargo test 219 + 6 e2e green, clippy 0, frontend 135 tests green, release rebuilt 22:43.
|
||||
- Open: live eyeball of Permissions panel (relaunch — running serve predates commits), approve kind-1 "Always allow" then send kind-3 and confirm it prompts; two-account live pass with real Amber; Step 5 KDF; Step 6 undo; Step 7 rename/hygiene.
|
||||
|
||||
### 2026-09-28 — Moi "workshop" themes + no-restart updater
|
||||
- **Workshop themes (`add956a`, `c18f59a`, `de804c8`):** user's "the theme I asked you to add" = the Moi project's Cybernetic Workshop look, NOT Cosmic Stardust. Pitfall: Moi's look isn't just palette tokens — `site.css` paints a 24px hairline graph-paper grid + mint/clay radial washes over the paper; matching requires the background stack, not just colors. `workshop-dark` accent moved to `#007AFF` per request; white logo on dark themes (invert filter).
|
||||
- **No-restart updater (`dcc701f`):** `app:selfupdate` IPC runs npm build + `cargo build --release` with augmented PATH, kills the backend child; the next request picks up the new binary — no app restart needed.
|
||||
- **Auto-naming fix:** pairing a new Amber connection showed generic "Amber"; persistent kind-0 identity backfill (`5b60ae3`) + stdin EAGAIN crash fix (`9770f46`). Suite at checkpoint `f905cbc`: 221 unit + 6 e2e, clippy 0, frontend 139 green.
|
||||
|
||||
## Active Tasks
|
||||
- [x] On-device Amber round-trip: pair from the real Amber app on the phone,
|
||||
sign a note, publish. The e2e test proves the protocol; this proves Amber.
|
||||
- [ ] Reroute kind-0 profile metadata publish through the external signer path.
|
||||
- [ ] Step 4: external-signer permissions UI (grants persisted AND enforced).
|
||||
|
||||
### 2026-09-26 — live publish CONFIRMED + forensics log cleaned
|
||||
- **End-to-end Amber signing verified on-chain:** kind:1 notes from `npub1qn0w4a…` (ids `5191172d01f9…`, `fe9a256f597f…`) confirmed accepted on primal/damus/snort/nos.lol at exactly the sign_event timestamps in el.log (Sep 25 ~19:50). Every named milestone of the pairing project is now live-verified except one optional scanless-restart eyeball.
|
||||
- **False alarms retired:** the scary recurring "restored signer answered as a different account" and stray "auto-name attempt" lines in pairing-trace.log were e2e TEST traffic (wrong-identity refusal test + loopback enrichment loop), not live Amber failures. Fixed by gating `fail()` + auto-name traces on `live_relays()` like every other trace site (`332ab64`); measured proof: an e2e run now leaves the trace file byte-identical.
|
||||
- **Live vault pruned again:** legacy keyless `fac852dc…` connection row removed (backup `profiles_vault.json.backup-cron-20260926`) so startup restore targets only the restorable `4148a9a1…` pairing. Serve smoke test on the real vault fires the restore with the persisted client key, no errors.
|
||||
- 216 unit + 5 e2e green; clippy 0; fmt clean; release rebuilt at `332ab64`. Checkpoint `1b4655c`.
|
||||
|
||||
### 2026-09-24/25 — session restore + live sign-in + auto-naming milestones
|
||||
- **Session restore shipped (`0982dad`, checkpoint `aa3c514`):** NIP-46 client key persisted in the vault; re-dial at startup/unlock — no re-scan after restart. `expected_identity` cross-account guard refuses a wrong-identity connection. e2e covers restart + wrong-identity refusal (216 unit + 5 e2e green, clippy 0). Pre-commit bug fixed: client-key re-keying was nested in the pairing-secret branch.
|
||||
- **11 profileless `nip46_connections` rows pruned** from the live vault (backup `profiles_vault.json.backup-prune-20260924`). Legacy connection rows predate client-key persistence — each needs one last fresh scan.
|
||||
- **LIVE Amber sign-in confirmed (Sep 25 PM)** through the new Add-profile flow; active remote profile `npub1qn0w4a…`. **Session restore live-verified against real Amber at `01ce5de`** (fix: restored sessions skip the connect secret re-echo — already-approved peers don't re-send it).
|
||||
- **Pairing label step removed** — one click → QR (seed label 'Amber'). Auto-name enrichment hardened to 4 retries × 20s; **confirmed end-to-end on the live account**: seed label shown at pairing, retry loop fetched kind-0 from nos.lol, vault row upgraded to 'web5osint' + picture. UI `fc2fe93`, backend `bc736ff`, checkpoint `704addc`.
|
||||
- **Repo renamed on Forgejo 2026-09-25:** `avi/Nostr_Keynctr` → `avi/Keynctr` (see [[Forgejo git.atitlan.io]]).
|
||||
- Open hardening item: account kind-0 still lives ONLY on nos.lol — publishing it to primal/damus (one Amber approval) remains open.
|
||||
|
||||
## Next Steps
|
||||
- [ ] Publish account kind-0 to primal/damus (currently only on nos.lol).
|
||||
- [ ] Remote picture/nip05 edits, KDF upgrade (Step 5), rename pass (Step 7).
|
||||
- [ ] Consider swapping `wss://relay.nostr.band` out of the user's
|
||||
enabled relays (settings.json) too — it hangs handshakes today and
|
||||
is pay-to-read.
|
||||
- [ ] Adopt [[Polaris Vault Workflow]] boilerplate conventions for the repo
|
||||
(directive 2026-09-25).
|
||||
|
||||
## Completed
|
||||
- [x] **LIVE PAIRING CONFIRMED + sign_event timeout fix
|
||||
(`f53bc56`, Sep 23):** two full live pairings against real Amber
|
||||
recorded in the trace log (15:17 + 15:37 CDT): `inbound 24133` ->
|
||||
secret echo -> `identity adopted: npub1f3tura… — CONNECTED`. The
|
||||
"Dev" profile row now exists in `profiles_vault.json`
|
||||
(`nip46_client` mode) — the original Sep problem (no profile row,
|
||||
no persisted connection) is CLOSED. Remaining failure diagnosed
|
||||
from el.log: valid Amber signatures arriving ~61s after
|
||||
publication were discarded ("stale/duplicate response: no
|
||||
waiter") because `sign_event` used the 30s ordinary-RPC leash;
|
||||
every sign needs a human tap in Amber. `sign_event` now has a
|
||||
120s `SIGN_TIMEOUT` (handshake leash). 213 unit + 4 e2e green,
|
||||
clippy 0, fmt clean, release rebuilt at `f53bc56`.
|
||||
- [x] **Feed author resolution + kind-0 via signer (`a76d8df`,
|
||||
`6f4dbb2`, Sep 23):** feed shows names/pictures from batched
|
||||
kind-0 lookups; "Publish name" routes through Amber.
|
||||
- [x] **First live pairing attempt diagnosed + relay-set fix
|
||||
(`c789cb4`, Sep 22):** trace log recorded the first real session
|
||||
(started 18:01:29 CDT, timed out 18:06:39, zero inbound 24133).
|
||||
Read-only relay sweep: the ephemeral key had NO 24133 anywhere.
|
||||
Anonymous write+readback canary (`canary-24133.py`, throwaway
|
||||
keys): purplepag.es blocks kind 24133, nostr.band hangs
|
||||
handshake; damus/primal/nos.lol accept+store, snort accepts for
|
||||
live push. New pairing set: damus.io, primal.net, nos.lol,
|
||||
snort.social, with a regression test pinning the blockers out.
|
||||
Also removed `inspect.py` from the debug dir (shadowed stdlib
|
||||
`inspect`, leaked stale output into terminals). 209 unit + 3 e2e
|
||||
green, clippy 0, fmt clean, release rebuilt.
|
||||
- [x] **Pairing forensics fully de-noised (`f6bf6a9`, Sep 21):** the
|
||||
`identity adopted — CONNECTED` trace line had no loopback gate, so
|
||||
every `cargo test` run appended fake CONNECTED entries to
|
||||
`pairing-trace.log` (Sep 18–21 evening entries were all test
|
||||
traffic, incl. this cron's own runs). Gated on `live_relays()`;
|
||||
verified by re-running the e2e suite and confirming the trace file
|
||||
stays untouched. Added a `paired: connection stored` handover trace
|
||||
line so a stall between the connect echo and `get_public_key`
|
||||
names itself. Full read-through audit of the QR pairing flow found
|
||||
no further defects; nothing left to fix without live Amber data.
|
||||
208+3 tests green, clippy 0, fmt clean, release rebuilt.
|
||||
- [x] **e2e vault-isolation bug found + fixed (`d52fa58`+`deeb4f9`, Sep 20):**
|
||||
the e2e tests seeded their isolation vault one directory too shallow
|
||||
(`$XDG_DATA_HOME/` instead of `$XDG_DATA_HOME/keynectr/`), so every
|
||||
e2e run silently migrated the legacy repo vault — the user's real
|
||||
profile with a plaintext secret key — into the test process.
|
||||
Forensic proof: two legacy-vault backups timestamped Sep 19 20:43:54
|
||||
+ 20:44:30, exactly the test runs around the root-cause commit. Also
|
||||
means the Sep 19 `identity adopted` trace lines were e2e traffic, NOT
|
||||
a live Amber scan — no live scan has happened against the fixed
|
||||
build yet (zero `pairing started` trace lines). Fix seeds the vault
|
||||
at the correct path and asserts emptiness after every e2e load so
|
||||
this can never pass silently again. Verified: repo legacy vault
|
||||
byte-identical, backup count unchanged, 208+3 tests green.
|
||||
- [x] **Amber pairing root cause found + fixed (`edd4e56`, Sep 19):** the
|
||||
pairing loop only accepted an inbound `{"method":"connect"}` *request*;
|
||||
NIP-46 says a nostrconnect:// signer sends a connect **response**
|
||||
(`{"id","result":"<secret>"}`, secret echo IS the handshake). Amber's
|
||||
approval was silently dropped as `pre-handshake '' ignored` — hence
|
||||
"Amber says connected, Keynctr shows nothing". Now the echo is
|
||||
verified directly, `ack` accepted, signer errors fail fast; locked by
|
||||
a new e2e test (`nip46_qr_pairing_connect_response_shape`, proven
|
||||
red-on-old/green-on-new). Awaiting live re-scan to confirm.
|
||||
- [x] Steps 1–3 (vault encryption, packaging, SigningBackend abstraction,
|
||||
vault-integrated connection secrets, IPC reroute, end-to-end external
|
||||
signing in publish + upload auth)
|
||||
- [x] Per-profile signer modes + persisted NIP-46 connections (`2c61830`)
|
||||
- [x] Fail-closed key export (`6eff510`), hash-chained audit log (`caed722`)
|
||||
- [x] Amber-compatible deferred-identity handshake (`f917e5e`)
|
||||
- [x] **Oct 1: Step 4 shipped — approval-time kind scope** (`d09c4ec`,
|
||||
checkpoint `38612a4`): "Always allow…" on a sign_event opens an inline
|
||||
kind editor prefilled with the request's kind; grants store the edited
|
||||
scope; legacy vault rows keep old meaning. Latent fix: legacy
|
||||
"Always allow" never recorded a grant (AppProvider dropped `always`).
|
||||
Suite: 227 unit + 6 e2e, clippy 0, frontend 148 green.
|
||||
- [x] **Oct 1 evening cron hygiene (`41d4a60`, checkpoint `2b91aea`, pushed):**
|
||||
lockfile refresh from the in-app update committed; pairing problem
|
||||
re-checked and still closed (3 nip46_connections, latest Sep 28 10:23);
|
||||
debug dirs cleaned up — no parse-failure evidence since closure.
|
||||
- [x] **Sep 28: pairing problem closed.** Vault now holds 3 persisted
|
||||
`nip46_connections` with matching `signer_mode: nip46_client` rows
|
||||
(latest Sep 28 10:23, active profile) — Amber connect events parse and
|
||||
persist; original failure no longer reproduces. Supporting fixes:
|
||||
stdin EAGAIN crash (`9770f46`), persistent kind-0 identity backfill
|
||||
for generic "Amber" labels (`5b60ae3`). Suite: 221 unit + 6 e2e,
|
||||
clippy 0, frontend 139 green (checkpoint `f905cbc`).
|
||||
- [x] Vault-load rewrite fix (`c096705`)
|
||||
- [x] NIP-46 e2e test harness + frontend bunker:// support (`85756df`)
|
||||
- [x] QR pairing + IPC/Electron wiring + real identity metadata (`38499d4`..`3d5302f`)
|
||||
- [x] Connect immediately after identity; kind-0 metadata fetched in
|
||||
background so the UI stops hanging on slow relays (`286bbca`)
|
||||
- [x] Always-allow grants: approvals gained an "Always allow" option;
|
||||
standing permission stored per (app pubkey, method) in the encrypted
|
||||
vault, listed with Revoke on the Signer screen (`93892fa`)
|
||||
|
||||
## Known Issues
|
||||
- `package.json` → `homepage` still reads `https://github.com/avi/Keynctr`
|
||||
(Step-7 hygiene item).
|
||||
- Legacy Python files + root `profiles_vault.json*` + dead stub
|
||||
`src/signer/nip46_external.rs` are hygiene leftovers (Step-7 pass).
|
||||
- Fixed since last update: `migrate_vault_signer_modes` no longer reports a
|
||||
change on every load (`c096705`).
|
||||
|
||||
## References
|
||||
- Local code: `/home/avi/Projects/Keynctr/`
|
||||
- Checkpoint: `/home/avi/Projects/Keynctr/CHECKPOINT-encryption.md`
|
||||
- [[00-HERMES/Current State]]
|
||||
- [[00-HERMES/Session Handoff]]
|
||||
Loading…
Add table
Add a link
Reference in a new issue