Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Phase log 32: 2026-09-14 - Planning Phase 24 and 24.1–24.4

A planning session. No source file was changed. Only CLAUDE.md (gitignored) and this file were written.

Goal

The user sent twenty items - defects in shipped features, a red CI job, and five pieces of new surface - and asked for them to be planned as Phase 24, with the existing 24 onward pushed back by one, with questions asked to settle the concepts before writing the plan.

Current Progress

  • Renumbered. Old 24 (Storage) → 25, … old 38 (Pentest) → 39. A third renumbering map (2026-09-14) is in CLAUDE.md; "Why this order" and the dependency edges were rewritten against the new numbers. The next phase to allocate is 40.
  • Phase 24 planned as defects + lint + CI hygiene + docs site, with four sub-phases: 24.1 composite secrets, 24.2 secrets-grid legibility, 24.3 calendar feeds, 24.4 unique-ID registry. Full design in CLAUDE.md under Phase 24.
  • Three stale lines fixed in passing: version (said 0.20.0, is 0.23.0), Phase 23's section status (said steps 0–3, is all done), the next handoff number.
  • Review-01 §8 (the lint backlog) moved from Phase 26 into Phase 24, because it is what turned CI red.

The findings that mattered

Found while grounding the plan, ranked by consequence. None fixed - this was a planning session.

  1. Every 2FA code is blank on a remote vault - verified in source, confirmed by the user's symptom. entry_totp_code refuses unless the local SQLCipher key is held; on a remote session it is not, and liveCodeFor swallows the error. The user suspected a migration; it is not one. The whole Authenticator feature looks broken to anyone using envv-server.
  2. Probably every app export writes nothing while toasting success. downloadText clicks a blob <a download> and toasts unconditionally; 15 call sites; no dialog/fs plugin and no download handler in src-tauri. Reported only for ICS. Unverified - reproduce with two exports first.
  3. CI's lint exit code 2 is stale suppressions, not the 501 warnings. The diagnosis the user pasted blamed the warnings and recommended --suppress-all, which CLAUDE.md forbids. npx eslint --prune-suppressions alone turns CI green. The full backlog is 501 warnings plus 236 suppressed errors.
  4. Inject can never work. It requires the Add/Edit overlay open, and the Tools panel it lives in sits behind that overlay.
  5. Seed-only entries already save (primaryIsOptional), so "standalone 2FA" meant a flow, not a validation fix.
  6. There is no emoji-insertion code; the report is about the OS emoji picker not reaching the WebKitGTK webview.

Decisions made (with the user)

QuestionAnswer
Structure24 + sub-phases 24.1–24.4, the 22.2 pattern
Nested-secret namecomposite - template + parts
Placeholdersnamed {calendar_id}, never positional
Pool groupingone card that expands; copy uses the pool cursor
UID registry storagekeyed hash only
UID registry verbscheck at mint, lookup, register external; rate-limit check and lookup; prune before a date; no tombstones
ICS as a linkenvv-server + LAN, per-user token URL
Shareable docscommitted docs site on Pages (book/)
Standalone 2FA"Add 2FA" flow on the Authenticator panel, no new SecretType
* on env valuethe required marker ignores primaryIsOptional
Injectgenerator popover inside the form
Caps Lockask the OS, typed-letter derivation as fallback
Type togglesmulti-toggle chip bar

Decisions of my own, recorded in CLAUDE.md with rationale: composite parts reuse extra_vars; the composite template is printed, not masked; the registry lives in its own registry.db keyed from vault_meta; a locked server answers a feed with 503, never an empty calendar; removing the sidebar section is written as a reversal of Phases 22 and 22.2.

What Worked / What Didn't Work

  • Worked: reading the code before asking. Three of the four question rounds were shaped by something found in source (the local-key gate, the backdrop over Tools, the seed-only predicate), and the user's answer to "local or remote?" confirmed the A1 mechanism outright.
  • Worked: running npm run lint rather than trusting the pasted diagnosis.
  • Didn't: I could not reproduce anything in a real window (no vault password, no display). A3, A4, A6, A7, A9 and A10 are hypotheses and are labelled so.

Next Steps

  1. Phase 24, in the order written in CLAUDE.md: prune suppressions first.
  2. Before A3, reproduce: Timeline → export ICS, and a .env export, in cargo tauri dev.
  3. Before A7 and A6, a screenshot of the secrets sidebar and of a card holding a seed plus named variables.
  4. At the start of D, the user picks from the five handoff-integration options. Decided later the same session: decision records (see the continuation below).
  5. Only the human can do: enable Dependabot alerts/security updates, private vulnerability reporting and code scanning in repository settings.

Notes for whoever picks this up

  • CLAUDE.md and the session records/ are gitignored; there is nothing to commit from this session.
  • The open questions for 24.1, 24.3 and 24.4 were answered in the continuation below; three of the answers are recommendations the user asked to have explained and has not yet confirmed, and are marked so in CLAUDE.md.
  • A1's fix removes a gate. Audit the sibling commands with the same "Vault is locked" check before deciding which ones to change; some really do need the local database.

Continued, same day - research pass: bundles, sessions, types, registry numbers

Decisions added (with the user)

QuestionAnswer
2FA on remote vaultsshow codes; no warning about seed and password in one vault
ICS builder(recommended) one Rust builder over IPC; TS builder deleted; fixture kept
Composite encoding(recommended) store raw, encode by URL zone, warn on pre-encoded
basic copy of a composite(recommended) rendered value only; parts from extended up
Registry uniquenessglobal, across every namespace
Handoffs in docsdecision records (ADRs) with a citation-checking test
Merged cardsbundle; members stay real entries; one bundle per entry
Cookiesgeneralised to a web session: cookies + storage tokens + header recipe + bindings + presets
New typesall three sets - 16 types
Key metadataorthogonal axes, prefix auto-fill
CXFimport + export
Placement24.1 = composite + bundle; new 24.5 = registry of types, new types, sessions, taxonomy, CXF

Findings from this pass

  1. A11 - older builds downgrade unknown types on Save. modals.ts:670 sets the type select to a value it has no option for (reads back ""), and modals.ts:252 falls back to 'api_key'. Must ship in a release before 24.1.

  2. filter_vault_for_user rebuilds the vault from three known keys, so any new top-level array (bundles) disappears for sub-users; the write merge preserves it. Recorded as B6/B7.

  3. Registry benchmark (SQLCipher, rusqlite 0.31 - the pinned version - WAL, 16-byte HMAC key, WITHOUT ROWID, date index; this 4-core desktop):

    RowsBulk insert/s (1000/tx)Single-commit insert/sLookup/sSizePrune 10%
    1M (plain SQLite, for contrast)70,98633,660152,85959.7 MB0.59 s
    1M24,78012,09556,41361.1 MB3.28 s
    10M17,50510,66337,475610 MB45.7 s in one transaction

    HMAC-SHA256 ≈ 756k/s - never the bottleneck. 61 bytes per row, flat from 1M to 10M. The 45.7 s single-statement prune holds the writer lock the whole time, which is why prune is now chunked in the plan.

Sources

Not verified this session and marked so in CLAUDE.md: Reddit/TikTok/Discord session cookies, Stripe prefixes, which managers ship a CXF export button.

Registry benchmark, second run

Pinned to 2 cores (taskset -c 0,1), PRAGMA cache_size=-32000, chunked prune (10,000 rows per transaction), 10M rows: bulk 24,429/s, one commit per ID 11,971/s, lookups 55,416/s, 610 MB, prune of 1M rows 36.2 s total with the worst single chunk 0.38 s. The no-index (month-partition) variant was stopped before finishing at the user's request; single table + date index was chosen without it. Killing it with pkill -f also killed the invoking shell (exit 144) - the Pitfalls entry about pkill -f matching its own command line, hit again.


Continued again - bundles as scopes (the Discord bot config)

The user asked for composites that reference bundle siblings and for a bundle to be able to hold a whole config file, using an old Discord bot config (all values revoked) as the example. It is a Python module, not a dotenv: f-strings, a hex colour, snowflake ids, markdown links, and api_key assigned twice.

QuestionAnswer
Reference syntax inside a bundle{slot.field} for a member, {name} for a local
Small non-secret valuesbundle-local variables
Derived values on exportrendered by default, per format (f-strings in Python, template literals in JS)
Value kindsall four groups: core + colour, templates, large ids + bitfields, dev formats

Design change made because of it: a bundle is now an entry of secretType: 'bundle' whose extra_vars are its local variables, replacing the top-level VaultData.bundles array written earlier the same day. It gets RBAC, masking, history and env naming for free, older builds can read it, and Phase 25 has no bundle table to migrate. Recorded in CLAUDE.md as a same-day revision.

Findings from the example, each now an edge case with a rule: Discord snowflakes exceed 2^53 and silently round as JS numbers (B15, large_id); the duplicate api_key binds positionally in Python, so the importer suffixes and rewrites later references rather than letting the last one win; a composite over siblings must carry taint so a URL containing api_key stays masked while the invite link does not (resolution rules, B8).