Phase log 49: Phases 33.1b to 33.7: the remaining parity gaps (2026-10-08)
Goal
Close what phase log 48 left in the capability map: online enrich, doctor's file checks, vendor import and archive UI, strict-write and entropy sources in the app, envv diff and the template library in the CLI, and the dead Tauri commands.
33.7 - dead commands
Deleted get_expiring, parse_totp_seed, totp_import_parse, load_settings, save_settings (and the now-unused settings_path), and their generate_handler! lines (get_expiring was registered twice). tests/ipc-contract.test.ts no longer lists the two TOTP ones. entropy_sources stayed because 33.4 wires it. The capability unused-ipc is gone; the gate's reverse rule (a command in unused-ipc that becomes used must move) has nothing left to guard until a command goes dead again, at which point re-add the capability.
33.6 - diff and templates in the CLI
secret-templates.json(repo root) generated from the TypeScript array with esbuild + node (not retyped);src/ts/templates.tsnow imports it;vault-core/src/templates.rs(list,find) includes it. One copy, so no parity fixture.envv template ls|show ID(template_cmd.rs, early-dispatched, no password) andenvv entry add NAME --preset ID: defaults are merged into the new entry, the entry keeps the name given, explicit flags override. The flag is--preset, not--template:--templatealready is a composite's{part}template (clap panics at startup on the duplicate id; found by running it).envv diff A B(diff_cmd.rs): same 14 fields and order as the Diff pane;api_key/api_secretare fingerprints unless--reveal; two different entries required. Test:envv-cli/tests/diff_template.rs.
33.4 - strict-write and entropy
- strict-write:
PUT /api/users/{id}/strict-writeand/api/classes/{id}/strict-write, owner only in both directions (turning it off widens writes), 404 for an unknown subject; Tauriset_strict_write;users.tsdispatcher maps it to REST for remote; checkbox in user and class detail (strictToggleHtml,wireStrictToggle). The CLI'suser strict-writeis still local-database only (it cannot target a remote); the app can now. Tests: the existinguser_and_class_routes_over_http_preserve_rbac_boundariesgained four assertions;tests/users.test.ts+1. - entropy: Tauri
entropy_fill(source, length)(hex, mixed with OS entropy exactly as the CLI viaentropy::fill, 1..8192 bytes);generators.tshassetEntropySource, an 8 KiB pool refilled in the background,fillRandom(OS CSPRNG when source isos),EntropyPoolEmptyandguardEntropy. An empty pool is a toast; it never falls back to the OS because that would claim a hardware source that was not used. New settingentropySource(defaultos), Settings -> Randomness populated fromentropy_sources;Settings._applycallssetEntropySourceonly when the value changed. Call sites wrapped: Tools secret/password/API-key panes and the form popover.tests/entropy-source.test.ts. Not done: bulkuid registerin the unique-ID pane.
33.3 - import-vault and archive in the app
import_vaults::plan_importextracted fromrun(pure; returns entries, counts, fingerprints); Tauriimport_vault_plan;src/ts/vendor-import-pane.ts(markup under the Import pane): choose vendor and file, preview with counts, Import replacesapi_keyswith the plan's entries and saves once.tests/vendor-import-pane.test.ts(faked file picker by spyingHTMLInputElement.prototype.click).backup::build_archive/open_archiveextracted (pure, shared); Tauribackup_archive_build(returns text; the pane saves it withsaveFile, 0600) andbackup_archive_restore(verifies first,lan_stop, zeroizes the key, writes the salt before the database, removes-wal/-shm, then the pane reloads the page). Round-trip/tamper/wrong-password unit testarchive_tests.tests/archive-rows.test.ts.- Caveat for the next agent: an archive is a byte copy of
vault.db. The vault is WAL; connections are opened and closed per command so the file is normally checkpointed, but an archive taken while another process holds a connection (LAN server, a runningenvv) can miss pages. The CLI has always had this; the Settings hint tells the user to lock other tools first. A proper fix is aVACUUM INTOcopy under the key, which would also drop the "needs the key" distinction between CLI and app. - A rescue slip: my first splice of
backup.rsduplicated functions (astr.indexfor the write call matchedexport()beforearchive()); it was restored from the index and redone. If agit statusshowsbackup.rsmodified by someone else, checkgit diffbefore assuming.
33.2b and 33.1b
doctor::file_findings(&Access)(integrity, storage hashes, salt pairing, permissions, audit); Tauridoctor_filebuildsAccess::Local(key)fromVaultStateafterset_paths(db, salt)(a OnceLock: the first call wins for the process). The Diagnose button runs it only when the store is not remote; remote says the rest is the server's.--fixis not in the app.enrich::issuer_for(entry)(cookies excluded), Taurienrich_online_targets(no request) andenrich_online(blocking probes inspawn_blocking). The pane: checkbox ->showConfirmlistingIssuer (count)and what the issuer will see -> only thenenrich_onlineon exactly the targeted entries. Live answers replace inferred proposals for the same field; a rejected key showsIssuer: rejectedwith its detail. Declining sends nothing (tested), but the final status line is overwritten by the "N entries with proposals" text; cosmetic.
Verification
npm run check green (76 files / 1388 tests); cargo clippy --workspace --all-targets -- -D warnings clean; cargo test --workspace all ok; cargo test -p envv-cli --test capabilities ok after every map change. Not run: ui-lab; any of the new panes or the archive/restore in a real Tauri window; fault injection on the new pane tests (only the capability gate and the earlier phases were fault-injected).
Not done
Bulk uid register in the pane; --fix in the app's doctor; strict-write on the CLI against a remote; the archive/WAL caveat above.