UnENVerse Find a guide: importing, command line, nodes

All releases

UnENVerse 2026-10-08

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.

Phases 33.1b to 33.7: the remaining parity gaps (2026-10-08)

  • 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.

Phase 32.3 and Phase 33 (UI ⇄ CLI parity, mechanical half) (2026-10-08)

Phase 32.2: the last controls (2026-10-08)

  • tests/probe.ts: probeKinds accepts an async reset and an afterReset hook (lets a reset click "Run Scan" and then clear the toast it raised).
  • tests/efficacy-surfaces.test.ts + efficacy-surfaces-silent.json (empty): surfaces health (vault with a weak entry and an empty bundle; click #health-scan-btn; probe #health-results [data-action]), settings (sidebar sections minus tags, so "+ Show" exists; #settings-overlay [data-action]), icons (openIconPicker(); #icon-picker-overlay [data-action]), feeds (a RemoteVaultStore instance with
  • tests/efficacy-settings.test.ts + efficacy-settings-silent.json: for each of the 19 inputs/selects in the Settings overlay plus 5 segmented/theme groups, change it, press Save, and require Settings.getAll() to differ. Only settings-backup-pw is silent (an action's password field), allowed with that reason.
  • tests/efficacy-cards.test.ts: selector now includes #bundle-suggest [data-action]; reset clears dismissedBundleSuggestions (a Dismiss probe persisted into the next probe).
  • tests/efficacy-config.test.ts: extras(type) adds an env_file chunk (docker), a docker service with a ${Alpha} env field, a wg_peer with a vault-reference field, an nginx_key + nginx_server, and a certificate entry for example.com.
  • tests/honest-controls.test.ts +7 tests: banner Accept creates a bundle, banner Dismiss stores 'twin' (lower-cased), a ${ref} badge from a config view shows the entry, a health-scan finding for a filtered-out entry shows it, unlock and re-lock screens refuse an empty password visibly, Enter on the role=button card icon opens the icon picker.

Phase 32.1: efficacy audit of the dynamic controls (2026-10-08)

  • tests/probe.ts (new, shared): snapshot() (body HTML with inline style reduced to display/visibility, plus every input/textarea/select value/checked/disabled), clearTransient() (toast, every .open, and now every .tool-status line, so two probes ending in the same status text cannot hide each other), hadEffect(act, waitMs) (MutationObserver records OR snapshot diff), `probeKinds({reset, selector, sample,
  • tests/efficacy.test.ts: now uses the shared helpers (behaviour identical; 145 buttons, 143 clicked).
  • tests/efficacy-cards.test.ts + efficacy-cards-silent.json (empty): one entry per registered secret type (from registry()), plus TOTP seed, 2-key pool, 4-member bundle, same-provider twins, long descriptions; allExpanded; probes #card-grid [data-action]. 582 controls, 34 kinds, 0 silent, partial: bundle-order (first member cannot move up), bulk-toggle, bundle-slot-tab (active tab).
  • tests/efficacy-config.test.ts + efficacy-config-silent.json (empty): one project per type using the real starter factories (wireguard, docker, nginx, kubernetes, ssh_config, traefik, apache, haproxy, ansible, postgres), selected via st.currentSelectedProjectIds and render(). 346 controls, 122 kinds (type-prefixed), 0 silent after the fix below.
  • tests/efficacy-closers.test.ts: every allow-listed "Closes a dialog" id is pressed with its dialog open (.open on the nearest [role=dialog]/.modal-overlay) and must close it; settings-cancel needs openSettings() because its handler is assigned there (invariant 9), so an openers map exists for such cases. 19 tests.
  • tests/honest-controls.test.ts: +4 tests (chunk move refused with a toast, chunk move among peers works, pool card with a bundle present, icon apply with empty name).
  • 15 data-action kinds unreached; listed in the 32.2 row with what each needs.
  • <select>/checkbox/toggle controls not probed: most apply on Save. 32.2(b).
  • The probe waits 40-60 ms; a control whose effect lands later reads as silent.
  • State leaks between probes through module state not reset by resetState. Add to the reset rather than reordering.
  • Pre-existing uncommitted changes in the tree (including tests/fixtures/*, index.html) are not from this step.

Phase 32: "Does it actually do anything?" (2026-10-08)

  • tests/efficacy.test.ts (new): boots the real shell exactly like tests/vault-shell.test.ts (real index.html, mountToolsPanes(), import('../src/ts/vault'), keep the bound nodes, re-append per probe), seeds a 2-entry vault, and for each of the 145 button[id] (143 clicked; 2 disabled) resets the toast and every .open dialog, clicks, waits 60 ms, and records "silent" when neither a MutationObserver recor
  • tests/efficacy-silent.json (new): the allow-list, 40 entries, each with a reason (closers with no dialog open, controls hidden until a precondition, handlers bound only when a lock screen is shown, empty-input converters, native file picker).
  • tests/honest-controls.test.ts (new, 26 tests): 12 Copy buttons x (empty -> "Nothing to copy yet" err toast; full -> "Copied ✓" ok toast), bulk delete with nothing ticked, import confirm with nothing staged.
  • src/ts/tools.ts: copyOrExplain(v) replaces 11 if (v) void clipboardWrite(v); sites and the fmt-copy one. Bulk delete and import confirm toast when their precondition is unmet. src/ts/modals.ts: cookie split with no parseable cookies and the session-preset header copy with no preset now toast.
  • 32.1 (new row in AGENTS.md) holds the rest: [data-action] card controls, menu items, selects/toggles, role="button" divs, config-view handlers, closers with their dialog open, the audit-verify probe artefact.
  • The probe waits a fixed 60 ms per click; a control whose effect lands later (a slow IPC) is falsely silent. The 17 s runtime is mostly that wait.
  • Probes leak state across each other (entries deleted, settings changed) since only the vault and a few st fields are reset; order effects like the audit one are possible. Add to the reset rather than reordering.
  • Vitest here swallows console.log; debug by writing a file.
  • Pre-existing uncommitted work in the tree (many files in git status) is not from this session; stage explicitly.

Phase 31: the provider catalogue (2026-10-08)

  • vault-core/src/catalogue.rs (new, owns all rules): Provider, Catalogue, wire form {payload: <JSON string>, signature: <b64>} so the signature covers exact bytes. verify (Ed25519 against a key arg), sign (seed), store (verify, refuse rollback by generated_at, atomic write 0600), load_cached (re-verifies every call), cache_path (ENVV_CATALOGUE_FILE override, else `dirs::data_dir()/io.envvault/ca
  • vault-core/Cargo.toml: ed25519-dalek = "2" (already in Cargo.lock via ssh-key 0.6.7, no new crate downloaded; Cargo.lock got the dependency edge). vault-core/src/lib.rs: pub mod catalogue;.
  • envv-cli/src/catalogue_cmd.rs (new): CatalogueCmd = update [--url|--file], show, diff, export (compiled table as providers JSON), sign <in> --key-file -o. https only, build_public_client (the one allowed CA-validated client, per the guard test in tests/agent.rs), 4 MiB cap. Public reference data, so stdout is allowed (unlike emit).
  • envv-cli/src/main.rs: Commands::Catalogue, handled before run() like Describe so it never asks for a password; added to the no-op arm in dispatch. envv-cli/src/lib.rs, envv-cli/Cargo.toml (hex = "=0.4.3", same pin as vault-core).
  • envv-cli/src/enrich.rs: new owned Hit, catalogue() (OnceLock, loaded once per process), lookup() = longest matching prefix across cached catalogue and compiled SIGNATURES, tie to the catalogue; plan_entry uses it; bundled_providers() public for export/diff. AXIS_SIGNATURES (acts_as/exposure) is still compiled-only and matched by prefix equality, so a catalogue-only prefix gets type/icon/url/env/tag
  • src-tauri/src/lib.rs: catalogue_status, catalogue_update(url) (https only, 20 s, 4 MiB, then catalogue::store), registered in generate_handler!.
  • catalogue/providers.json (new, 331 lines): seed source, produced by envv catalogue export from the compiled table (NOT retyped). This is the file maintainers edit.
  • .github/workflows/docs.yml: daily cron + a "Sign the provider catalogue" step writing site/catalogue/catalogue.json; skips cleanly when the CATALOGUE_SIGNING_KEY secret is absent.
  • Default URL https://darthdemono.github.io/EnvVault/catalogue/catalogue.json is unverified against a live deployment (Pages enablement is still manual, Phase 24).
  • No UI: the IPC commands exist but no button or Settings row calls them (invariant 10 gap, record as such). tests/ipc-contract.test.ts only checks frontend->registered, so it does not notice.
  • Catalogue docs_url/rotate_url/revoke_url are carried and validated but nothing consumes them yet; axes (acts_as/exposure) are compiled-only.
  • The catalogue content is only the 48 compiled prefixes; "a large set of providers" needs real curation, with sources, outside this change.

Phase 30: storage, row-per-entry, schema v2

  • A delta API from clients (the remaining O(vault) CPU), chunk-level rows, history-per-revision rows, and indexes beyond the audit ones: nothing queries by them yet. load_entries_where exists in storage.rs and has no caller.
  • No visual check of the conflict dialog text in a real window.
  • time-based pruning of vault_saves: retention is "newest 2000 saves", which a vault saved thousands of times a day (a polling script) would burn through in hours, making an hour-old token a whole-vault conflict. If that bites, raise KEEP_SAVES (it is one constant) or prune by age.
  • The remote store over the pinned-HTTPS proxy surfaces no headers, so it still has no ETag there and writes unconditionally, as before.

Phase 29: the config compiler

  • Six designed checks, eight rule ids (two checks split into an error case and a softer one). All fire only on positive evidence; all skip ${ref}, disabled chunks and anything that could be satisfied somewhere this function cannot see.

| # | id | severity | fires when | silent when | | 1 | nginx-proxy-pass-unknown-service | warning | proxy_pass host is a bare name that is neither a docker service/container_name nor an nginx_upstream | the project has no docker_service chunk; host is dotted, an IP, localhost, a $variable, a unix socket or a ${ref} | | 2a | wireguard-allowed-ips-duplicate | error | two peers claim the same masked network | one peer repeating itself; ${ref} values; disabled peers | | 2b | wireguard-allowed-ips-overlap | warning | one peer's network strictly contains another peer's | a /0 default route; different families; disjoint networks | | 3 | traefik-middleware-missing | warning | a router middlewares item matches no traefik_middleware name (case-insensitive) | item contains @ (another provider) | | 4a | k8s-deployment-secret-missing | error | a Deployment's secretEnv/secretMounts names a Secret no k8s_secret chunk creates in that namespace | the project has no k8s_secret chunk; ${ref} names; a mount with no path | | 4b | k8s-ingress-service-missing | warning | Ingress serviceName (or its own name) has no k8s_service of that name in that namespace | the project has no k8s_service chunk | | 5 | compose-env-ref-unresolved | warning | a ${NAME} anywhere in a service value (or a ref_name) is neither an env_file key nor a vault entry name | ${NAME:-d}, -, :?, + operator forms; ${NAME}; chunk:/bundle: refs | | 6 | pg-host-network-unreachable | warning | pg_connection.host is a docker service S and a consumer C shares no network with it; consumer = lists S in depends_on, spells S in an env value, or reads ${chunk:<pg>/…} | no consumer; either side has network_mode; both on the implicit default network |

  • No ui-lab screen for the panel: the lab seeds a vault and does not run IPC, and the panel only exists under Tauri. The panel's markup and escaping are unit-tested; its visual layout in a real window is unchecked (WebKitGTK window tests remain a human task).
  • "A gate a node runs before apply" from the design: Nodes (Phase 34) do not exist yet.

Phase 28: escape by construction

  • Exporter functions and other string-building code that is not markup were not touched (by design).
  • esc/escAttr still exist and are exported; render.ts and tools.ts no longer import them.
  • The innerHTML = '' sites became setHtml(x, ''), not replaceChildren(); either is fine.
  • Static markup in index.html and tools-markup.ts is not templated (it has no interpolation).
  • A real WebKitGTK window was not run (A4/A9/A10 remain human tasks).

Phase 27, UI-lab triage (layout backlog 1,155 → 0)

CheckResult
npx vitest run58 files, 1,257 tests pass (82 s)
npm run typecheckclean
npx eslint ui-labclean
npx prettier --check ui-lab src/cssclean (after prettier --write src/css/a11y.css: I had written [role='button'], prettier wants double quotes)
  • (If the count changes, read SCREENS x VIEWPORTS in ui-lab/layout.spec.ts plus the single tests in layout.spec.ts and behaviour.spec.ts.)
  • This is the part that went wrong first and then right. Read the Dead ends section
  • before changing the rule again.
  • What the 19 actually were (detail strings from layout-findings.json):
  • #add-btn, #copy-all-btn, #copy-all-arrow "overlaps #settings-close /
  • button.settings-tab" on settings-* screens: header buttons sitting *under* the

Phase 26, outward redaction engine

  • Phase 26 is complete and uncommitted.
  • envv-cli/src/shield.rs builds one Aho-Corasick matcher from every vault value
  • the fail-closed CLI redactor treats as private. It uses the existing
  • sha256:<12 hex> fingerprints for replacements and reports.
  • envv shield -- <command> runs a child with piped stdout and stderr, shields
  • both streams independently, and returns the child's exit code. It keeps the
  • final max_secret_len - 1 bytes between reads, so a secret that crosses a
  • pipe read boundary cannot leak a prefix.
  • The wrapper refuses NUL-containing or invalid-UTF-8 output rather than
  • half-masking binary data. It drains the rest of that pipe before failing so a
  • child cannot block on a full pipe. --json is refused because the wrapper
  • deliberately forwards the child's ordinary output.

Phase 25, route coverage and permission-expression parity

  • Phase 25 is complete, but remains uncommitted with the rest of the Phase 24
  • worktree.
  • envv-server/src/lib.rs now exercises the actual build_router path across
  • and revocation.
  • tests/fixtures/parity/permex.json is the shared truth table for permission
  • expressions. vault-core/src/permex.rs and tests/permex.test.ts both read
  • it, so a semantic disagreement fails either suite.
  • The TypeScript evaluator gained the core's composition primitives
  • (combine, anyOf, requireAll). The permission editor preview now shows
  • The UI had fields for those values but could not receive them, so its former
  • preview could not model the server's effective access rule.

Phase 24 and 24.1–24.5, everything that can be built

·