Decompose: Feature: "Little Black Book" mode — locked private vault for discreet contacts #26
Labels
No labels
blocked:depends-p1
blocked:gate-research
blocked:lint-failed
blocked:needs-decision
blocked:needs-human
blocked:verify-failed
bug
data-loss
documentation
enhancement
feature-request
in-progress
needs-decompose
needs-triage
pwa
quickfix
security
security/high
security/medium
test
test-coverage
triage-approved
vault
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/kimi_agent_kin_app_sync#26
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Decompose: Feature: "Little Black Book" mode — locked private vault for discreet contacts
Context
Parent: #26. Decision (2026-06-28): unlock method is PIN or biometric, user-pickable, PIN set on first setup. Implementation shipped at #121 (commit
de8436f): unlock UI,vault-crypto.tskey derivation, 5-attempt lockout / 30s cooldown. Auto-exit gesture closed at #77.What's still missing against the original #26 spec: enrollment flow (first-launch), separate Dexie
kin_vaultDB (currently a shared DB with aninVaultflag), contact-isolation plumbing (move in/out + exclusion from main views), discreet entry point, auto-lock-on-background timer, and forensic-trace verification.Repo: Mark/kimi_agent_kin_app_sync. Stack: React + Capacitor + Dexie + Zustand. Vault surface:
src/vault/Vault.tsx,src/vault/vault-crypto.ts,src/db.ts,src/types.ts.Out of scope (do NOT absorb into these sub-issues)
Per-phase ordering rule
Data dependency, not feature size. Phase N consumes Phase N-1's output. Pipeline can dispatch each phase independently once the previous lands.
P1: Vault DB + store split (separate Dexie instance)
Goal: Stand up
kin_vaultas a distinct Dexie database. Vault data NEVER lives in the mainkin_db. The main DB has zero knowledge of vault contacts. Future readers cannot infer vault existence from main-DB shape.Approach candidates:
src/vault/vault-db.tsexporting avaultDbinstance with namekin_vault, version 1. Schema mirrors main Person / Interaction / Gift / Date tables but scoped to the vault.vaultDbis constructed lazily insideVaultStore, never imported bydb.tsorschemas.ts.Outputs:
src/vault/vault-db.tswith tables:vaultPeople,vaultInteractions,vaultGifts,vaultDates,vaultNotes.src/vault/VaultStore.tsskeleton wrapping all vault queries; rejects reads unless unlock state is "unlocked" in-memory.Acceptance criteria:
kin_vaultDexie DB created with namekin_vault, separate fromkin_db.vault-dbfromsrc/db.ts,src/schemas.ts, or any main-DB module.VaultStoreexportsisUnlocked(): boolean; any query called while locked throwsVaultLockedError.kin_db(Inspect) has novaultPeopletable;kin_vault(Inspect) has nopeopletable.Files touched:
src/vault/vault-db.ts(new),src/vault/VaultStore.ts(new),src/db.ts(untouched, verify no import path), new unit tests undersrc/vault/__tests__/.P2: Vault enrollment + move-in/move-out plumbing
Goal: First-launch path enrolls a PIN (or biometric) before the vault is usable. Existing contacts can be moved in/out via PersonDetail. Main DB people flagged vault-only via a foreign-key reference to the vault row id; the main Person row never carries vault data.
Approach candidates:
vault.primaryFactor = "pin" | "biometric"in app settings.VaultStore.moveFromMain(personId)/VaultStore.moveToMain(personId). Move is a real cross-DB write, not a flag flip.Outputs:
Vault.tsx.vault.primaryFactorsetting with defaultpin.VaultStore.moveFromMain(personId)andmoveToMain(personId).Acceptance criteria:
kin_vaultand deletes fromkin_db.kin_vaulttokin_dband deletes the vault row.Files touched:
src/vault/Vault.tsx(enrollment sub-screen),src/vault/VaultStore.ts(move methods),src/screens/PersonDetail.tsx(move actions).P3: Contact isolation — keep vault contacts out of main UI
Goal: Vault contacts, their interactions, gifts, and dates must not appear in the main app's People page, search, Dashboard, or analytics. No visible hint that the vault exists while locked.
Approach candidates:
mainPersonQuery()helper. The helper itself reads onlykin_db; it cannot reach intokin_vaulteven if the vault is unlocked.interactionCount/giftCountderived from vault tables.Outputs:
src/db.tsquery helpers (or wrapper) that guarantee vault data is unreachable from the main app.src/screens/People.tsx,src/screens/Search.tsx,src/screens/Dashboard.tsx,src/screens/Analytics.tsxif any of them aggregated over interactions.Acceptance criteria:
Files touched: query layer in
src/db.ts, the four surface screens.P4: Discreet entry point + auto-lock timer
Goal: Vault entry is not labeled "Little Black Book" or "Vault." Auto-lock when the app goes to background, with configurable delay.
Approach candidates:
App.addListener("appStateChange", …)) records background timestamp. On resume, if elapsed > delay and vault is unlocked, lock it.vault.autoLockDelay = "immediate" | "1min" | "5min". Default1min.Outputs:
Appbackground/resume wiring insrc/vault/lifecycle.ts.src/screens/Settings.tsx(delay radio).Acceptance criteria:
immediate→ background → resume → vault locked.5min→ background 30s → resume → vault still unlocked.appStateChangeevents with faked timestamps; verify unlock-state transitions.Files touched:
src/screens/Settings.tsx,src/App.tsx(lifecycle wiring),src/vault/lifecycle.ts(new).P5: Forensic-trace verification + close-out
Goal: Verify and harden the "no trace of vault existence" claim. Close out #26 once all phases P1-P4 land and this verification passes.
Approach candidates:
vault-named key appears inPreferences/ localStorage while vault is locked.kin_dbschema does not contain vault tables (re-verifies P1).Outputs:
src/vault/__tests__/forensic-trace.test.ts(new).Acceptance criteria:
Files touched: new test file only; verification run as a script
scripts/verify-vault-trace.sh.Per-phase deliverable shape
Each phase ships as one PR with:
npm run lintclean)⏸ Needs Decision
Blocking factors:
Scope: medium Risk: medium Spec: aligned Type: feature
Summary: Add a PIN/biometric-locked private vault for discreet contacts
Suggestion: Implement a separate IndexedDB database for vault contacts, add auth screens, and isolate vault UI from main app
Rationale: Medium scope due to new auth flow, separate data layer, and UI isolation. Medium risk because it touches core data paths and requires careful isolation. Aligned with project goals of privacy and relationship management. Feature type as it adds new functionality. Suggested approach focuses on data isolation and auth first, then UI integration.
Action: Review each blocking factor above. Remove
blocked:needs-decisionlabel to route back to triage.GSD VERIFY phase found no changes after implementation.
Worker output: ]11;?\API call failed after 3 retries: HTTP 429: Rate limit exceeded for GLM-5. Retry in 11s.
Decision (2026-06-28)
Vault unlock method: PIN or biometric, user-pickable. The unlock screen must support both, with whichever the user selects as the primary factor and the other as a fallback if registered.
Existing
vault-crypto.tsandVault.tsxneed an unlock UI tied to this decision. Implementation issue to be filed next.Feature: "Little Black Book" mode — locked private vault for discreet contactsto Decompose: Feature: "Little Black Book" mode — locked private vault for discreet contactsThis issue has been decomposed into the following sub-issues:
kin_vaultas a distinct Dexie database. Vault data NEVER liEach sub-issue has been labeled with
triage-approvedand is ready for queue routing.